Skip to content

Docker Deployment

Deploy FraiseQL in Docker containers for development and production use.

FROM ghcr.io/fraiseql/fraiseql:latest
WORKDIR /app
# Copy schema and config
COPY schema.py fraiseql.toml ./
# Expose port
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/health/live || exit 1
# fraiseql run compiles the schema and starts the HTTP server
CMD ["fraiseql", "run"]
version: '3.8'
services:
# PostgreSQL database
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: fraiseql
POSTGRES_PASSWORD: dev-password
POSTGRES_DB: fraiseql_dev
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U fraiseql"]
interval: 10s
timeout: 5s
retries: 5
# FraiseQL API
fraiseql:
build: .
environment:
DATABASE_URL: postgresql://fraiseql:dev-password@postgres:5432/fraiseql_dev
JWT_SECRET: dev-secret-key-change-in-production
FRAISEQL_ENV: development
FRAISEQL_LOG: debug
ports:
- "8080:8080"
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health/live"]
interval: 30s
timeout: 10s
retries: 3
volumes:
postgres_data:
Terminal window
# Build and start
docker-compose up -d
# View logs
docker-compose logs -f fraiseql
# Stop
docker-compose down
Terminal window
# Check if API is running
curl http://localhost:8080/health/live
# Response: 200 OK
# Run a test query
curl -X POST http://localhost:8080/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ users { id name } }"}'

For production deployments, use a multi-stage build for smaller image size:

# Stage 1: Compile schema
FROM ghcr.io/fraiseql/fraiseql:latest AS builder
WORKDIR /app
COPY schema.py fraiseql.toml ./
RUN fraiseql compile schema.py
# Stage 2: Runtime — minimal image with compiled schema only
FROM ghcr.io/fraiseql/fraiseql:latest
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/* \
&& useradd -r -u 1000 -s /sbin/nologin fraiseql
COPY --from=builder /app/schema.compiled.json .
COPY fraiseql.toml .
USER fraiseql
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
CMD curl -f http://localhost:8080/health/live || exit 1
CMD ["fraiseql", "run", "schema.compiled.json"]

For production with proper secrets management:

version: '3.8'
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_NAME}
ports:
- "${DB_PORT}:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./backups:/backups # For automated backups
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
fraiseql:
build:
context: .
dockerfile: Dockerfile.prod
environment:
DATABASE_URL: postgresql://${DB_USER}:${DB_PASSWORD}@postgres:5432/${DB_NAME}
JWT_SECRET: ${JWT_SECRET}
ENVIRONMENT: production
LOG_LEVEL: ${LOG_LEVEL:-info}
LOG_FORMAT: json
CORS_ORIGINS: ${CORS_ORIGINS}
RATE_LIMIT_REQUESTS: ${RATE_LIMIT_REQUESTS:-10000}
RATE_LIMIT_WINDOW_SECONDS: ${RATE_LIMIT_WINDOW_SECONDS:-60}
PGBOUNCER_MIN_POOL_SIZE: 5
PGBOUNCER_MAX_POOL_SIZE: 20
ports:
- "${FRAISEQL_PORT}:8080"
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health/ready"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
restart: unless-stopped
deploy:
resources:
limits:
cpus: '1'
memory: 1G
reservations:
cpus: '0.5'
memory: 512M
# Optional: nginx reverse proxy with SSL
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./certs:/etc/nginx/certs:ro
depends_on:
- fraiseql
restart: unless-stopped
volumes:
postgres_data:

Create .env.production file (never commit to git):

Terminal window
# Database
DB_USER=fraiseql_prod
DB_PASSWORD=strong-password-min-32-chars
DB_NAME=fraiseql_production
DB_PORT=5432
# FraiseQL
JWT_SECRET=long-random-secret-min-32-chars
FRAISEQL_PORT=8080
CORS_ORIGINS=https://app.example.com,https://api.example.com
LOG_LEVEL=info
# Rate limiting
RATE_LIMIT_REQUESTS=10000
RATE_LIMIT_WINDOW_SECONDS=60
# Backup schedule (cron format)
BACKUP_SCHEDULE="0 2 * * *" # Daily at 2 AM

Load with Docker Compose:

Terminal window
docker-compose --env-file .env.production up -d

For production with SSL termination:

upstream fraiseql {
server fraiseql:8080;
}
server {
listen 80;
server_name api.example.com;
# Redirect HTTP to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name api.example.com;
# SSL certificates (use Let's Encrypt for free)
ssl_certificate /etc/nginx/certs/cert.pem;
ssl_certificate_key /etc/nginx/certs/key.pem;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Logging
access_log /var/log/nginx/fraiseql-access.log;
error_log /var/log/nginx/fraiseql-error.log;
# Proxy to FraiseQL
location / {
proxy_pass http://fraiseql;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Buffer settings
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
}
# Health check endpoint (don't log)
location /health/ {
access_log off;
proxy_pass http://fraiseql;
}
}

Run migrations on container start:

# In Dockerfile, add:
COPY scripts/init-db.sh /docker-entrypoint-initdb.d/
RUN chmod +x /docker-entrypoint-initdb.d/init-db.sh
# In docker-compose.yml postgres service:
volumes:
- ./scripts/init-db.sh:/docker-entrypoint-initdb.d/init-db.sh

Example scripts/init-db.sh:

#!/bin/bash
set -e
echo "Running database migrations..."
fraiseql migrate
echo "Seeding database (optional)..."
psql "$DATABASE_URL" -f seeds/initial-data.sql
echo "Database initialization complete"
fraiseql:
volumes:
- ./schema.py:/app/schema.py # Hot-reload schema changes
- ./fraiseql.toml:/app/fraiseql.toml

For multiple instances:

version: '3.8'
services:
fraiseql:
build: .
environment:
DATABASE_URL: postgresql://user:pass@postgres:5432/db
deploy:
replicas: 3 # Run 3 instances
resources:
limits:
cpus: '1'
memory: 1G
restart_policy:
condition: on-failure
update_config:
parallelism: 1 # Rolling update
delay: 10s
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- fraiseql

Start with Docker Swarm:

Terminal window
docker swarm init
docker stack deploy -c docker-compose.yml fraiseql
docker stack services fraiseql # View running services
Terminal window
# All services
docker-compose logs
# Specific service
docker-compose logs fraiseql
# Follow logs
docker-compose logs -f fraiseql
# Last 100 lines
docker-compose logs --tail=100 fraiseql
# Timestamps
docker-compose logs --timestamps fraiseql
fraiseql:
logging:
driver: "splunk" # or awslogs, gcplogs, awsfirelens
options:
splunk-token: "${SPLUNK_HEC_TOKEN}"
splunk-url: "${SPLUNK_HEC_URL}"
splunk-insecureskipverify: "true"
Terminal window
# Check logs
docker-compose logs fraiseql
# Common issues:
# 1. PORT_ALREADY_IN_USE: Change ports in docker-compose.yml
# 2. DATABASE_NOT_RUNNING: Ensure postgres service started first
# 3. ENVIRONMENT_VARIABLES_MISSING: Check .env file exists
Terminal window
# Verify services are running
docker-compose ps
# Test database connectivity from fraiseql container
docker-compose exec fraiseql \
pg_isready -h postgres -U fraiseql -d fraiseql_dev
Terminal window
# Check Docker disk usage
docker system df
# Clean up unused images/containers
docker system prune
# Clean up volumes (CAREFUL - deletes data!)
docker volume prune

Increase memory limit in docker-compose.yml:

fraiseql:
deploy:
resources:
limits:
memory: 2G # Increase from 1G

Create scripts/backup.sh:

#!/bin/bash
set -e
BACKUP_DIR="./backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/fraiseql-backup-$TIMESTAMP.sql"
mkdir -p "$BACKUP_DIR"
echo "Starting backup..."
# Dump database
docker-compose exec -T postgres pg_dump \
-U "${DB_USER}" \
"${DB_NAME}" > "$BACKUP_FILE"
# Compress
gzip "$BACKUP_FILE"
# Keep only last 7 days
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +7 -delete
echo "Backup complete: $BACKUP_FILE.gz"

Schedule with cron:

Terminal window
# Add to crontab
0 2 * * * cd /home/user/fraiseql && ./scripts/backup.sh
Terminal window
# Decompress
gunzip backups/fraiseql-backup-20240115_020000.sql.gz
# Restore
docker-compose exec -T postgres psql \
-U fraiseql_prod fraiseql_production < \
backups/fraiseql-backup-20240115_020000.sql
echo "Restore complete"
  1. Use read-only root filesystem:

    fraiseql:
    read_only: true
    tmpfs: ["/tmp", "/var/tmp"]
  2. Don’t run as root:

    RUN useradd -m -u 1000 fraiseql
    USER fraiseql
  3. Network isolation:

    networks:
    backend: # Only for backend services
    frontend: # Only for frontend access
    fraiseql:
    networks:
    - backend
  4. Secrets in production (use Docker Secrets):

    Terminal window
    echo "${JWT_SECRET}" | docker secret create jwt_secret -
    # In docker-compose.yml:
    secrets:
    jwt_secret:
    external: true
    fraiseql:
    secrets:
    - jwt_secret

Adjust based on expected load:

fraiseql:
deploy:
resources:
limits:
cpus: '2' # 2 CPUs max
memory: 2G # 2GB max
reservations:
cpus: '1' # Reserve 1 CPU
memory: 1G # Reserve 1GB

FraiseQL’s Rust runtime is async and uses all available CPU cores automatically — no worker count tuning required. Increase container CPU allocation (cpus: '4') to scale throughput.

postgres:
environment:
POSTGRES_INITDB_ARGS: "-c max_connections=200 -c shared_buffers=256MB"

Scaling & Performance

Learn how to scale beyond a single Docker host with Kubernetes and auto-scaling. Scaling Guide

AWS Deployment

Move to a fully managed AWS infrastructure with ECS, Fargate, and RDS. AWS Guide

Deployment Overview

Compare all deployment options and review the production checklist. Deployment Overview