Production Deployment Guide¶
Status: ✅ Production Ready Audience: DevOps, SREs, Infrastructure Engineers Reading Time: 20-30 minutes Platforms: Kubernetes, Docker Compose, bare metal
FraiseQL is a Python (FastAPI) application. You deploy it the same way you deploy
any ASGI app: install the package with pip/uv, then run it under an ASGI server
such as uvicorn or gunicorn with the uvicorn worker. The GraphQL schema is built
in memory at application startup from your Python decorators — there is no separate
build step and no generated artifact to ship.
Prerequisites¶
Required Knowledge:
- Docker containerization and image management
- Kubernetes fundamentals (Pods, Deployments, Services, ConfigMaps, Secrets)
- Linux/Unix system administration
- PostgreSQL/database administration
- TLS/SSL certificate management
- Basic networking (DNS, ports, firewalls)
Required Software:
- Python 3.13+ and the
fraiseqlpackage (install viapiporuv) - Docker 20.10+ (if using containers)
- kubectl 1.24+ (if using Kubernetes)
- PostgreSQL 14+ client tools
- OpenSSL for certificate management
Required Infrastructure:
- PostgreSQL 14+ database (managed or self-hosted)
- Kubernetes cluster 1.24+ or Docker host
- Container registry (Docker Hub, AWS ECR, Google GCR, etc.)
- Domain name with DNS records
- TLS/SSL certificates
Recommended Tools:
- Helm 3+ (for Kubernetes package management)
- kube-ops (for operational dashboards)
- ArgoCD (for GitOps deployments)
- Prometheus + Grafana (for monitoring)
Time Estimate: 2-4 hours for initial deployment
Overview¶
This guide covers deploying FraiseQL to production environments with:
- Security hardening - TLS, mTLS, view-based security, rate limiting
- Performance optimization - Connection pooling, caching, APQ
- High availability - Multi-replica deployments, health checks, graceful shutdown
- Observability - Prometheus metrics, OpenTelemetry tracing, structured logging
- Compliance - Audit logging, security headers, access controls
- Kubernetes - Native integration with HPA, Pod Security Standards, Network Policies
Pre-Deployment Checklist¶
Infrastructure Requirements¶
- PostgreSQL 13+ instance (managed or self-hosted)
- SSL/TLS certificates for domain
- Container registry (Docker Hub, ECR, GCR, or self-hosted)
- Kubernetes cluster (1.24+) or Docker Compose environment
- Monitoring infrastructure (Prometheus, Grafana)
- Logging infrastructure (optional: Loki, ELK)
- Tracing backend (optional: Jaeger, Zipkin)
Credentials & Secrets¶
- PostgreSQL connection credentials
- Auth provider credentials (Auth0, JWT, etc.)
- TLS certificates (fullchain.pem, private.key)
- API keys for external services
- Environment-specific configuration
Application Configuration¶
- Database schema migrated to target environment
- Graphql queries registered (if using APQ in REQUIRED mode)
- Security profiles configured (STANDARD/REGULATED/RESTRICTED)
- Rate limiting thresholds set
- APQ storage backend configured
Configuration Management¶
Environment Variables¶
FraiseQLConfig is a Pydantic settings object: every field can be set with a
matching FRAISEQL_-prefixed environment variable, or passed directly as a keyword
argument to create_fraiseql_app(...).
# Application
FRAISEQL_ENVIRONMENT=production
# Database
FRAISEQL_DATABASE_URL=postgresql://user:pass@db.example.com:5432/fraiseql
# GraphQL
FRAISEQL_INTROSPECTION_POLICY=disabled
FRAISEQL_ENABLE_PLAYGROUND=false
# Security
FRAISEQL_AUTH_ENABLED=true
FRAISEQL_AUTH_PROVIDER=auth0
FRAISEQL_RATE_LIMIT_ENABLED=true
FRAISEQL_RATE_LIMIT_REQUESTS_PER_MINUTE=100
FRAISEQL_COMPLEXITY_MAX_SCORE=1000
# APQ
FRAISEQL_APQ_MODE=required
FRAISEQL_APQ_STORAGE_BACKEND=postgresql
FRAISEQL_APQ_RESPONSE_CACHE_TTL=600
# Caching
FRAISEQL_CACHE_TTL=300
FRAISEQL_TURBO_ROUTER_CACHE_SIZE=1000
Kubernetes ConfigMap¶
apiVersion: v1
kind: ConfigMap
metadata:
name: fraiseql-config
namespace: default
data:
FRAISEQL_ENVIRONMENT: "production"
FRAISEQL_INTROSPECTION_POLICY: "disabled"
FRAISEQL_RATE_LIMIT_REQUESTS_PER_MINUTE: "100"
FRAISEQL_APQ_MODE: "required"
FRAISEQL_APQ_STORAGE_BACKEND: "postgresql"
Kubernetes Secret¶
apiVersion: v1
kind: Secret
metadata:
name: fraiseql-secrets
namespace: default
type: Opaque
stringData:
FRAISEQL_DATABASE_URL: "postgresql://user:pass@db:5432/fraiseql"
AUTH0_DOMAIN: "your-tenant.auth0.com"
AUTH0_API_IDENTIFIER: "https://api.example.com"
Container Deployment¶
Docker Image¶
FraiseQL ships as a normal Python wheel. The optional fraiseql_rs acceleration
extension is published as a prebuilt wheel, so installing fraiseql does not
require a Rust toolchain in your image.
Building the Image¶
# Multi-stage build
FROM python:3.13-slim AS builder
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential libpq-dev && rm -rf /var/lib/apt/lists/*
# Install dependencies (fraiseql and your application requirements)
COPY pyproject.toml requirements.txt ./
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
FROM python:3.13-slim
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
libpq5 curl && rm -rf /var/lib/apt/lists/*
COPY --from=builder /install /usr/local
COPY . /app
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
# app:app is your module exposing the FraiseQL FastAPI app from create_fraiseql_app(...)
CMD ["gunicorn", "app:app", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:8000"]
Building & Pushing¶
# Build
docker build -t myregistry/fraiseql-app:1.0.0 .
docker build -t myregistry/fraiseql-app:latest .
# Push
docker push myregistry/fraiseql-app:1.0.0
docker push myregistry/fraiseql-app:latest
# Scan for vulnerabilities
trivy image myregistry/fraiseql-app:1.0.0
Hardened Image¶
For high-security environments, build on a distroless or hardened base image with:
- Non-root user (UID: 65532)
- Reduced attack surface
- CVE fixes
- Read-only root filesystem compatible
- No shell access
docker build -f Dockerfile.hardened -t myregistry/fraiseql-app:hardened .
Kubernetes Deployment¶
Standard Deployment¶
apiVersion: apps/v1
kind: Deployment
metadata:
name: fraiseql
namespace: default
labels:
app: fraiseql
version: "1.0"
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: fraiseql
template:
metadata:
labels:
app: fraiseql
version: "1.0"
spec:
serviceAccountName: fraiseql
securityContext:
runAsNonRoot: true
runAsUser: 65532
fsGroup: 65532
seccompProfile:
type: RuntimeDefault
containers:
- name: fraiseql
image: myregistry/fraiseql-app:1.0.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8000
protocol: TCP
# Environment from ConfigMap and Secret
envFrom:
- configMapRef:
name: fraiseql-config
- secretRef:
name: fraiseql-secrets
# Resource constraints
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "1000m"
# Health checks (/health is built in; /health/ready is your custom check)
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 30
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health/ready
port: http
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 2
startupProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 30 # 150 seconds total
# Security
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 65532
capabilities:
drop: [ALL]
# Volume mounts for temp files
volumeMounts:
- name: tmp
mountPath: /tmp
- name: var-run
mountPath: /var/run
volumes:
- name: tmp
emptyDir: {}
- name: var-run
emptyDir: {}
# Pod disruption budget
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values: [fraiseql]
topologyKey: kubernetes.io/hostname
terminationGracePeriodSeconds: 30
Service¶
apiVersion: v1
kind: Service
metadata:
name: fraiseql
namespace: default
labels:
app: fraiseql
spec:
type: ClusterIP
selector:
app: fraiseql
ports:
- name: http
port: 8000
targetPort: http
protocol: TCP
sessionAffinity: None # Round-robin, no sticky sessions
Horizontal Pod Autoscaler¶
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: fraiseql
namespace: default
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: fraiseql
minReplicas: 3
maxReplicas: 20
metrics:
# CPU-based scaling
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
# Memory-based scaling
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
# Custom GraphQL metrics (if metrics exposed)
- type: Pods
pods:
metric:
name: graphql_requests_per_second
target:
type: AverageValue
averageValue: "100" # Scale at 100 req/s per pod
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 50
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 15
- type: Pods
value: 4
periodSeconds: 15
selectPolicy: Max
Pod Disruption Budget¶
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: fraiseql
namespace: default
spec:
minAvailable: 2
selector:
matchLabels:
app: fraiseql
Network Policies (Zero Trust)¶
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: fraiseql
namespace: default
spec:
podSelector:
matchLabels:
app: fraiseql
policyTypes:
- Ingress
- Egress
ingress:
# Allow from ingress controller
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- protocol: TCP
port: 8000
# Allow from Prometheus scraper
- from:
- namespaceSelector:
matchLabels:
name: monitoring
ports:
- protocol: TCP
port: 8000
egress:
# Allow to PostgreSQL
- to:
- podSelector: {}
ports:
- protocol: TCP
port: 5432
# Allow DNS
- to:
- namespaceSelector: {}
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
# Allow to external APIs (if needed)
- to:
- namespaceSelector: {}
ports:
- protocol: TCP
port: 443
Ingress¶
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: fraiseql
namespace: default
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/rate-limit: "100"
nginx.ingress.kubernetes.io/rate-limit-window: "1m"
nginx.ingress.kubernetes.io/ssl-protocols: "TLSv1.2 TLSv1.3"
nginx.ingress.kubernetes.io/add-headers: "true"
nginx.ingress.kubernetes.io/configuration-snippet: |
more_set_headers "X-Frame-Options: DENY";
more_set_headers "X-Content-Type-Options: nosniff";
more_set_headers "X-XSS-Protection: 1; mode=block";
spec:
ingressClassName: nginx
tls:
- hosts:
- api.example.com
secretName: fraiseql-tls
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: fraiseql
port:
number: 8000
Database Configuration¶
PostgreSQL Setup¶
-- Create database
CREATE DATABASE fraiseql
WITH ENCODING 'UTF8'
LC_COLLATE 'en_US.UTF-8'
LC_CTYPE 'en_US.UTF-8'
TEMPLATE template0;
-- Create application user
CREATE USER fraiseql_app WITH PASSWORD 'secure_password';
-- Grant permissions
GRANT CONNECT ON DATABASE fraiseql TO fraiseql_app;
GRANT USAGE ON SCHEMA public TO fraiseql_app;
GRANT CREATE ON SCHEMA public TO fraiseql_app;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO fraiseql_app;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO fraiseql_app;
-- Set default privileges for future objects
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO fraiseql_app;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO fraiseql_app;
Connection Pool Configuration¶
# Optimal pool settings for production
pool_config = {
"host": "db.example.com",
"port": 5432,
"database": "fraiseql",
"user": "fraiseql_app",
"password": os.getenv("DB_PASSWORD"),
"min_size": 20, # Maintain minimum connections
"max_size": 100, # Scale up to 100
"max_idle_time": 60, # Recycle idle connections
"max_lifetime": 1800, # Renew connections every 30 min
"command_timeout": 30, # 30 second query timeout
"timeout": 30, # 30 second connection timeout
"ssl": "require", # Require SSL/TLS
"ssl_certificate": "/etc/ssl/certs/ca-bundle.crt"
}
Indexing Strategy¶
-- User-related indexes
CREATE INDEX idx_user_id ON users(id);
CREATE INDEX idx_user_email ON users(email) WHERE deleted_at IS NULL;
CREATE INDEX idx_user_created_at ON users(created_at DESC);
-- Order-related indexes (composite)
CREATE INDEX idx_orders_user_date ON orders(user_id, created_at DESC) WHERE status != 'deleted';
CREATE INDEX idx_orders_status ON orders(status) WHERE created_at > now() - interval '90 days';
-- JSONB indexes
CREATE INDEX idx_user_metadata ON users USING gin(metadata);
CREATE INDEX idx_user_metadata_tags ON users USING gin(metadata->'tags');
-- Full-text search
CREATE INDEX idx_products_content ON products USING gin(to_tsvector('english', name || ' ' || description));
-- Soft delete queries
CREATE INDEX idx_active_records ON (table_name) WHERE deleted_at IS NULL;
Security Hardening¶
Introspection Control¶
# Disable introspection in production
config = FraiseQLConfig(
introspection_policy=IntrospectionPolicy.DISABLED
)
Introspection Policies:
DISABLED- No introspection (recommended for production)AUTHENTICATED- Only authenticated users can introspectPUBLIC- Anyone can introspect (development only)
Rate Limiting¶
config = FraiseQLConfig(
rate_limit_enabled=True,
rate_limit_requests_per_minute=100, # 100 req/min = ~1.67 req/sec
rate_limit_requests_per_hour=5000, # 5000 req/hour
rate_limit_burst_size=10, # Allow 10 concurrent
rate_limit_window_type="sliding", # More accurate
rate_limit_whitelist=["internal.ips"],
rate_limit_blacklist=["malicious.ips"]
)
Query Complexity Limits¶
config = FraiseQLConfig(
complexity_enabled=True,
complexity_max_score=1000, # Reject complex queries
complexity_max_depth=10, # Prevent deep nesting
complexity_default_list_size=10,
complexity_field_multipliers={
"users": 2, # More expensive
"orders": 3,
"analytics": 10, # Very expensive
"reports": 15
}
)
TLS/mTLS Configuration¶
# Connection string with TLS
DATABASE_URL = "postgresql://user:pass@db.example.com:5432/fraiseql?sslmode=require"
# Kubernetes pod with client certificates
containers:
- name: fraiseql
env:
- name: SSL_CERT_FILE
value: /etc/ssl/certs/ca-bundle.crt
- name: SSL_CLIENT_CERT_FILE
value: /etc/ssl/certs/client-cert.pem
- name: SSL_CLIENT_KEY_FILE
value: /etc/ssl/private/client-key.pem
volumeMounts:
- name: tls-certs
mountPath: /etc/ssl/certs
readOnly: true
- name: tls-keys
mountPath: /etc/ssl/private
readOnly: true
volumes:
- name: tls-certs
secret:
secretName: fraiseql-tls-certs
- name: tls-keys
secret:
secretName: fraiseql-tls-keys
Security Headers¶
# Nginx Ingress annotations
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
more_set_headers "X-Frame-Options: DENY";
more_set_headers "X-Content-Type-Options: nosniff";
more_set_headers "X-XSS-Protection: 1; mode=block";
more_set_headers "Referrer-Policy: strict-origin-when-cross-origin";
more_set_headers "Permissions-Policy: geolocation=(), microphone=(), camera=()";
more_set_headers "Strict-Transport-Security: max-age=31536000; includeSubDomains";
CORS Configuration¶
config = FraiseQLConfig(
cors_enabled=False # Disable by default, handle at Ingress
)
# Or if enabling at application level:
cors_enabled=True
cors_origins=["https://app.example.com", "https://admin.example.com"]
cors_methods=["GET", "POST"]
cors_headers=["Content-Type", "Authorization"]
cors_allow_credentials=True
cors_max_age=3600
Performance Optimization¶
APQ (Automatic Persisted Queries)¶
For bandwidth optimization:
config = FraiseQLConfig(
apq_mode=APQMode.REQUIRED, # Only persisted queries
apq_storage_backend="postgresql", # Persistent storage
apq_cache_responses=True, # Cache responses
apq_response_cache_ttl=600 # 10 minutes
)
Register queries at deploy time:
# Move GraphQL files to directory
FRAISEQL_APQ_QUERIES_DIR=/app/graphql/queries
# Queries in /app/graphql/queries/*.graphql are auto-registered
Expected performance:
- Payload reduction: 95%+
- Cache hit rate: 85-95%
- Bandwidth savings: 10-50x
Caching Strategy¶
# Multi-level caching
config = FraiseQLConfig(
cache_ttl=300, # Query cache TTL: 5 minutes
apq_response_cache_ttl=600, # APQ response cache: 10 minutes
turbo_router_cache_size=1000 # TurboRouter cache size
)
FraiseQL also ships a PostgreSQL-backed result cache with cascade invalidation
(PostgresCache, ResultCache, CachedRepository) for caching query results
directly in your database — see the caching reference for details.
Expected hit rates:
- Stable APIs: 95%+
- Dynamic queries: 80-90%
- Admin interfaces: 70-85%
Connection Pooling¶
FraiseQL manages an async PostgreSQL connection pool (psycopg) internally. Tune it
through FraiseQLConfig (or the matching FRAISEQL_ environment variables):
config = FraiseQLConfig(
database_url=database_url,
database_pool_size=20, # Connections to maintain
database_pool_timeout=30, # Seconds to wait for a free connection
database_pool_recycle=1800, # Recycle connections after 30 min
)
Monitoring & Observability¶
Prometheus Metrics¶
from fraiseql.monitoring import setup_metrics, MetricsConfig
# app is the FastAPI app returned by create_fraiseql_app(...)
setup_metrics(app, MetricsConfig(
enabled=True,
namespace="myapp",
metrics_path="/metrics"
))
OpenTelemetry Tracing¶
from fraiseql.tracing import setup_tracing, TracingConfig
setup_tracing(app, TracingConfig(
enabled=True,
service_name="fraiseql-api",
export_format="otlp",
export_endpoint="jaeger:4317",
sample_rate=0.1 # 10% sampling in production
))
Health Checks¶
create_fraiseql_app(...) automatically registers a built-in GET /health
endpoint that returns a process-level liveness status — point your Kubernetes
liveness, readiness, and startup probes at it.
For deeper checks (database connectivity, pool stats), build a composable
HealthCheck and expose it on your own route:
from fraiseql.monitoring import HealthCheck, check_database, check_pool_stats
health = HealthCheck()
health.add_check("database", check_database)
health.add_check("pool", check_pool_stats)
@app.get("/health/ready")
async def readiness():
return await health.run_checks()
Available endpoints:
GET /health- Built-in process liveness checkGET /health/ready- Readiness with customHealthCheck(example above)
Deployment Automation¶
GitOps Workflow¶
# 1. Create feature branch
git checkout -b feature/new-feature
# 2. Make changes and commit
git add .
git commit -m "feat: new feature"
# 3. Push and create PR
git push -u origin feature/new-feature
gh pr create
# 4. CI/CD pipeline runs:
# - Build Docker image
# - Scan for vulnerabilities
# - Run tests
# - Push to registry
# - Update Kubernetes manifests (ArgoCD)
# 5. PR merged to main
# 6. ArgoCD syncs changes to production
Helm Chart Deployment¶
# Install
helm install fraiseql ./helm-chart \
--namespace default \
--values values-prod.yaml
# Upgrade
helm upgrade fraiseql ./helm-chart \
--namespace default \
--values values-prod.yaml
# Rollback
helm rollback fraiseql 1
Disaster Recovery¶
Backup Strategy¶
-- Daily backup
pg_dump fraiseql > /backups/fraiseql_$(date +%Y%m%d).sql
-- With compression
pg_dump -Fc fraiseql > /backups/fraiseql_$(date +%Y%m%d).dump
-- With parallel jobs
pg_dump -Fd -j 4 fraiseql > /backups/fraiseql_$(date +%Y%m%d)_parallel/
Restore Procedure¶
-- From SQL dump
psql fraiseql < /backups/fraiseql_20250111.sql
-- From custom format dump
pg_restore -d fraiseql /backups/fraiseql_20250111.dump
-- Verify
SELECT COUNT(*) FROM users;
SELECT MAX(created_at) FROM audit_events;
Database Replication¶
-- Primary-Replica setup
-- On primary:
CREATE PUBLICATION fraiseql FOR ALL TABLES;
-- On replica:
CREATE SUBSCRIPTION fraiseql CONNECTION 'postgresql://primary:5432/fraiseql' PUBLICATION fraiseql;
-- Monitor replication lag
SELECT now() - pg_last_wal_receive_lsn()::text::pg_lsn / 1000000 AS replication_lag_seconds;
Scaling Recommendations¶
Single-Region (Recommended Starting Point)¶
3 fraiseql pods (minimum)
1 PostgreSQL instance (managed)
Prometheus (1 instance)
Capacity:
- Up to 1,000 requests/second
- Sub-100ms P95 latency
- Database: 100-500GB
Multi-Region¶
3 fraiseql pods per region (3+ regions)
PostgreSQL primary + replicas
Cross-region failover
Capacity:
- Up to 10,000+ requests/second
- <50ms P95 latency globally
- Database: 500GB-10TB
Troubleshooting¶
High CPU Usage¶
- Check query complexity scores
- Enable slow query logging
- Analyze database query plans
- Scale up horizontally
Memory Leaks¶
- Check cache configuration
- Monitor pool connections
- Review APQ storage size
- Increase memory limits
Database Connection Pool Exhaustion¶
- Check active query count
- Review query timeouts
- Increase pool size
- Optimize slow queries
High Latency¶
- Check database performance
- Verify indexes are in place
- Analyze query complexity
- Check network latency
Summary¶
FraiseQL production deployments include:
✅ Zero-trust security - View-based access, rate limiting, introspection control
✅ High availability - Multi-replica Kubernetes, health checks, graceful shutdown
✅ Performance - APQ, caching, connection pooling, optional fraiseql_rs acceleration
✅ Observability - Prometheus, OpenTelemetry, structured logging
✅ Scalability - HPA, load balancing, multi-region ready
✅ Compliance - Audit logging, security headers, standards-compliant
✅ Disaster recovery - Backup/restore procedures, replication
Start with the standard Kubernetes deployment template and scale to multi-region as needed.
See Also¶
- Performance Tuning Runbook - Day-2 operations and tuning
- Monitoring & Observability Guide - Setting up Prometheus, Grafana, and OpenTelemetry
- Security Checklist - Pre-production security verification
- Troubleshooting Guide - Common production issues and solutions
- Scaling Guide - Horizontal and vertical scaling strategies