Scaling & Performance
Learn how to scale beyond a single Docker host with Kubernetes and auto-scaling. Scaling Guide
Deploy FraiseQL in Docker containers for development and production use.
DockerfileFROM ghcr.io/fraiseql/fraiseql:latest
WORKDIR /app
# Copy schema and configCOPY schema.py fraiseql.toml ./
# Expose portEXPOSE 8080
# Health checkHEALTHCHECK --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 serverCMD ["fraiseql", "run"]docker-compose.ymlversion: '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:# Build and startdocker-compose up -d
# View logsdocker-compose logs -f fraiseql
# Stopdocker-compose down# Check if API is runningcurl http://localhost:8080/health/live# Response: 200 OK
# Run a test querycurl -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 schemaFROM ghcr.io/fraiseql/fraiseql:latest AS builder
WORKDIR /appCOPY schema.py fraiseql.toml ./RUN fraiseql compile schema.py
# Stage 2: Runtime — minimal image with compiled schema onlyFROM 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 checkHEALTHCHECK --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):
# DatabaseDB_USER=fraiseql_prodDB_PASSWORD=strong-password-min-32-charsDB_NAME=fraiseql_productionDB_PORT=5432
# FraiseQLJWT_SECRET=long-random-secret-min-32-charsFRAISEQL_PORT=8080CORS_ORIGINS=https://app.example.com,https://api.example.comLOG_LEVEL=info
# Rate limitingRATE_LIMIT_REQUESTS=10000RATE_LIMIT_WINDOW_SECONDS=60
# Backup schedule (cron format)BACKUP_SCHEDULE="0 2 * * *" # Daily at 2 AMLoad with Docker Compose:
docker-compose --env-file .env.production up -dFor 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.shExample scripts/init-db.sh:
#!/bin/bashset -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.tomlfraiseql: volumes: [] # All data lives in the container (ephemeral) # Database state lives in PostgreSQL volumefraiseql: volumes: # For SQLite or file-based uploads - fraiseql_data:/app/data - fraiseql_uploads:/app/uploads
volumes: fraiseql_data: driver: local fraiseql_uploads: driver: localFor 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: - fraiseqlStart with Docker Swarm:
docker swarm initdocker stack deploy -c docker-compose.yml fraiseqldocker stack services fraiseql # View running services# All servicesdocker-compose logs
# Specific servicedocker-compose logs fraiseql
# Follow logsdocker-compose logs -f fraiseql
# Last 100 linesdocker-compose logs --tail=100 fraiseql
# Timestampsdocker-compose logs --timestamps fraiseqlfraiseql: logging: driver: "splunk" # or awslogs, gcplogs, awsfirelens options: splunk-token: "${SPLUNK_HEC_TOKEN}" splunk-url: "${SPLUNK_HEC_URL}" splunk-insecureskipverify: "true"# Check logsdocker-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# Verify services are runningdocker-compose ps
# Test database connectivity from fraiseql containerdocker-compose exec fraiseql \ pg_isready -h postgres -U fraiseql -d fraiseql_dev# Check Docker disk usagedocker system df
# Clean up unused images/containersdocker system prune
# Clean up volumes (CAREFUL - deletes data!)docker volume pruneIncrease memory limit in docker-compose.yml:
fraiseql: deploy: resources: limits: memory: 2G # Increase from 1GCreate scripts/backup.sh:
#!/bin/bashset -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 databasedocker-compose exec -T postgres pg_dump \ -U "${DB_USER}" \ "${DB_NAME}" > "$BACKUP_FILE"
# Compressgzip "$BACKUP_FILE"
# Keep only last 7 daysfind "$BACKUP_DIR" -name "*.sql.gz" -mtime +7 -delete
echo "Backup complete: $BACKUP_FILE.gz"Schedule with cron:
# Add to crontab0 2 * * * cd /home/user/fraiseql && ./scripts/backup.sh# Decompressgunzip backups/fraiseql-backup-20240115_020000.sql.gz
# Restoredocker-compose exec -T postgres psql \ -U fraiseql_prod fraiseql_production < \ backups/fraiseql-backup-20240115_020000.sql
echo "Restore complete"Use read-only root filesystem:
fraiseql: read_only: true tmpfs: ["/tmp", "/var/tmp"]Don’t run as root:
RUN useradd -m -u 1000 fraiseqlUSER fraiseqlNetwork isolation:
networks: backend: # Only for backend services frontend: # Only for frontend accessfraiseql: networks: - backendSecrets in production (use Docker Secrets):
echo "${JWT_SECRET}" | docker secret create jwt_secret -
# In docker-compose.yml:secrets: jwt_secret: external: truefraiseql: secrets: - jwt_secretAdjust 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 1GBFraiseQL’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
Troubleshooting
Diagnose and fix common Docker deployment issues. Troubleshooting Guide
Deployment Overview
Compare all deployment options and review the production checklist. Deployment Overview