Skip to content

Deployment

This guide covers deploying FraiseQL to production environments, including Docker, cloud platforms, and operational best practices.

[project]
name = "my-api"
version = "1.0.0"
[server]
host = "0.0.0.0"
port = 8080
[server.cors]
origins = ["https://app.example.com"]
credentials = true
[database]
url = "${DATABASE_URL}"
pool_min = 10
pool_max = 100
connect_timeout_ms = 5000
idle_timeout_ms = 600000
ssl_mode = "require"
[security.error_sanitization]
enabled = true
hide_implementation_details = true
sanitize_database_errors = true
[security.rate_limiting]
enabled = true
requests_per_second = 100
burst_size = 200

JWT authentication is configured via environment variables, not fraiseql.toml (see Environment Variables below). There is no [auth] TOML section.

Required environment variables for production:

Terminal window
# Database
DATABASE_URL=postgresql://user:pass@host:5432/dbname?sslmode=require
# JWT authentication
JWT_SECRET=your-256-bit-secret-key # required if using JWT auth
JWT_ALGORITHM=HS256 # HS256 | RS256 | ES256 (default: HS256)
# Logging (standard Rust env var)
RUST_LOG=info
# OpenTelemetry tracing (standard OTel env vars)
OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp.example.com:4317
OTEL_SERVICE_NAME=fraiseql-api

FraiseQL is distributed as a pre-built Docker image — no Rust toolchain required. Use the official image as your base and copy in your schema:

FROM ghcr.io/fraiseql/fraiseql:latest
WORKDIR /app
# Copy schema source and config
COPY fraiseql.toml .
COPY schema.py .
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
CMD wget -qO- http://localhost:8080/health || exit 1
EXPOSE 8080
CMD ["fraiseql", "run"]

For CI/CD pipelines where you want to pre-compile the schema to a static artifact:

# Stage 1: compile schema to schema.compiled.json
FROM ghcr.io/fraiseql/fraiseql:latest AS builder
WORKDIR /app
COPY fraiseql.toml .
COPY schema.py .
RUN fraiseql compile
# Stage 2: minimal runtime image with compiled schema
FROM ghcr.io/fraiseql/fraiseql:latest
WORKDIR /app
COPY --from=builder /app/schema.compiled.json .
COPY fraiseql.toml .
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
CMD wget -qO- http://localhost:8080/health || exit 1
EXPOSE 8080
CMD ["fraiseql", "run", "schema.compiled.json"]
version: "3.8"
services:
api:
build: .
ports:
- "8080:8080"
environment:
- DATABASE_URL=${DATABASE_URL}
- JWT_SECRET=${JWT_SECRET}
- RUST_LOG=info
depends_on:
- postgres
deploy:
replicas: 3
resources:
limits:
cpus: "2"
memory: 1G
reservations:
cpus: "0.5"
memory: 256M
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:8080/health"]
interval: 30s
timeout: 3s
retries: 3
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: mydb
POSTGRES_USER: user
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d mydb"]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres_data:
apiVersion: apps/v1
kind: Deployment
metadata:
name: fraiseql-api
labels:
app: fraiseql-api
spec:
replicas: 3
selector:
matchLabels:
app: fraiseql-api
template:
metadata:
labels:
app: fraiseql-api
spec:
containers:
- name: api
image: your-registry/fraiseql-api:latest
ports:
- containerPort: 8080
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: fraiseql-secrets
key: database-url
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: fraiseql-secrets
key: jwt-secret
resources:
requests:
cpu: "500m"
memory: "256Mi"
limits:
cpu: "2000m"
memory: "1Gi"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: fraiseql-api
spec:
selector:
app: fraiseql-api
ports:
- port: 80
targetPort: 8080
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: fraiseql-api
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts:
- api.example.com
secretName: fraiseql-tls
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: fraiseql-api
port:
number: 80
apiVersion: v1
kind: Secret
metadata:
name: fraiseql-secrets
type: Opaque
stringData:
database-url: postgresql://user:pass@postgres:5432/mydb
jwt-secret: your-256-bit-secret # injected as JWT_SECRET env var

Task Definition:

{
"family": "fraiseql-api",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "1024",
"memory": "2048",
"containerDefinitions": [
{
"name": "api",
"image": "your-ecr/fraiseql-api:latest",
"portMappings": [
{
"containerPort": 8080,
"protocol": "tcp"
}
],
"environment": [
{
"name": "RUST_LOG",
"value": "info"
}
],
"secrets": [
{
"name": "DATABASE_URL",
"valueFrom": "arn:aws:secretsmanager:region:account:secret:fraiseql/database-url"
},
{
"name": "JWT_SECRET",
"valueFrom": "arn:aws:secretsmanager:region:account:secret:fraiseql/jwt-secret"
}
],
"healthCheck": {
"command": ["CMD-SHELL", "wget -qO- http://localhost:8080/health || exit 1"],
"interval": 30,
"timeout": 5,
"retries": 3
},
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/fraiseql-api",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "ecs"
}
}
}
]
}
  1. Test migrations locally against a copy of production data
  2. Create a backup before migrating
  3. Run migrations during low-traffic periods
  4. Monitor for errors after migration
Terminal window
# Create backup
pg_dump $DATABASE_URL > backup-$(date +%Y%m%d).sql
# Run migrations (use your SQL migration tool: Flyway, Liquibase, sqitch, golang-migrate, etc.)
# FraiseQL does not bundle a migration runner — manage schema changes separately

For zero-downtime deployments:

  1. Add columns as nullable first
  2. Deploy code that handles both old and new schema
  3. Backfill data for new columns
  4. Add constraints after data is backfilled
  5. Remove old code paths

FraiseQL exposes health endpoints:

EndpointDescription
/healthOverall health status
/health/liveLiveness probe (is process running)
/health/readyReadiness probe (can accept traffic)
Terminal window
curl http://localhost:8080/health
# {"status": "healthy", "database": "connected", "version": "1.0.0"}

FraiseQL is stateless and scales horizontally:

# Kubernetes HPA
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: fraiseql-api
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: fraiseql-api
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80

For high-scale deployments, use external connection pooling:

# PgBouncer sidecar
- name: pgbouncer
image: edoburu/pgbouncer:1.21.0
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: fraiseql-secrets
key: database-url
- name: POOL_MODE
value: transaction
- name: MAX_CLIENT_CONN
value: "1000"
- name: DEFAULT_POOL_SIZE
value: "20"
  • TLS enabled for all traffic (via load balancer or [server.tls])
  • JWT secrets are strong (JWT_SECRET env var, 256+ bits)
  • Database credentials use secrets management
  • Network policies restrict pod-to-pod traffic
  • Rate limiting enabled ([security.rate_limiting] in fraiseql.toml)
  • Error sanitization enabled ([security.error_sanitization] in fraiseql.toml)
  • CORS origins explicitly listed ([server.cors] in fraiseql.toml)
  • SQL injection prevented (parameterized queries — FraiseQL does this automatically)
# ServiceMonitor for Prometheus Operator
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: fraiseql-api
spec:
selector:
matchLabels:
app: fraiseql-api
endpoints:
- port: http
path: /metrics
interval: 30s
MetricDescriptionAlert Threshold
fraiseql_requests_totalTotal requests-
fraiseql_request_duration_secondsRequest latencyp99 > 500ms
fraiseql_db_pool_sizeConnection pool size> 80% of max
fraiseql_errors_totalError count> 10/min

Performance Optimization

Optimize FraiseQL for production load with query tuning and caching. Performance Guide

Troubleshooting

Debug production issues including connection errors and slow queries. Troubleshooting Guide

Security Hardening

Additional security configuration for production deployments. Security Guide

Cloud Deployment Guides

Step-by-step guides for AWS, GCP, and Azure deployments. Deployment Overview