Deployment
This deployment documentation is currently being enhanced and refined. Some sections may be incomplete or subject to change. We're actively working on comprehensive examples and troubleshooting guides for all platforms.
flAPI is designed for deployment flexibility. Available as a single binary or Docker container, it runs on any platform that supports Linux containers or x86-64 binaries.
Quick Start
🚀 Interactive Deployment Configurator
Configure your deployment with config mounting strategy
# 1. Create directory structure on host
mkdir -p ~/flapi/{config/sqls,data}
# 2. Create your flapi.yaml
cat > ~/flapi/config/flapi.yaml <<EOF
project_name: my-api
template:
path: '/config/sqls'
connections:
# Your connections here
duckdb:
db_path: /data/flapi.db
EOF
# 3. Add your SQL templates
# cp your_templates.yaml ~/flapi/config/sqls/
# 4. Create docker-compose.yml
cat > ~/flapi/docker-compose.yml <<EOF
version: '3.8'
services:
flapi:
image: ghcr.io/datazoode/flapi:latest
ports:
- "8080:8080"
volumes:
- ./config:/config:ro
- ./data:/data
restart: unless-stopped
deploy:
resources:
limits:
cpus: '1'
memory: 2G
EOF
# 5. Deploy
cd ~/flapi && docker-compose up -dRequirements
| Resource | Minimum | Recommended | Notes |
|---|---|---|---|
| CPU | 1 vCPU | 2 vCPU | DuckDB is CPU-intensive for aggregations |
| Memory | 1 GB | 2-4 GB | Depends on cache size |
| Storage | 1 GB | 10 GB+ | For DuckDB cache persistence |
| Port | 8080 | - | Default HTTP port |
| Startup | < 100ms | - | Fast cold starts for serverless |
Configuration Management
flAPI requires configuration files to function. The base Docker image (ghcr.io/datazoode/flapi:latest) contains only the binary - you must mount your configuration to run it.
Standard Directory Structure
flAPI expects the following structure:
/config/
├── flapi.yaml # Main configuration file (required)
└── sqls/ # SQL template directory (required)
├── users.yaml # Endpoint definitions
├── products.yaml
└── orders.yaml
Minimal Example Configuration
Create flapi.yaml:
project-name: my-api
project-description: My flAPI deployment
template:
path: '/config/sqls' # Path to SQL templates and endpoint configs
connections:
postgres:
init: |
INSTALL postgres;
LOAD postgres;
ATTACH 'host=db.example.com port=5432 dbname=mydb user=app password=${DB_PASSWORD}'
AS pgdb (TYPE postgres);
properties:
schema: public
duckdb:
db_path: /data/flapi.db # Cache database location
access_mode: READ_WRITE
Create your first endpoint sqls/users.yaml and its SQL template sqls/users.sql. Endpoint configs always reference an external SQL file via template-source: — there is no inline sql: field.
url-path: /users
method: GET
template-source: users.sql
connection:
- postgres
SELECT *
FROM pgdb.users
WHERE active = true
LIMIT 100
Configuration Mounting Strategies
Choose the approach that fits your deployment platform:
1️⃣ Local Volume Mount (Docker, VPS, K8s)
Best for: Development, VPS deployment, on-premises
Mount your local config directory directly into the container:
docker run -d \
-p 8080:8080 \
-v $(pwd)/config:/config:ro \
-v $(pwd)/data:/data \
ghcr.io/datazoode/flapi:latest
Advantages:
- ✅ Simple and direct
- ✅ Easy to update (edit files, restart container)
- ✅ Works with any text editor
Disadvantages:
- ❌ Requires host filesystem access
- ❌ Not suitable for serverless platforms
2️⃣ Kubernetes ConfigMap (K8s, IONOS, STACKIT)
Best for: Kubernetes deployments, multi-replica setups
Create ConfigMaps from your config files:
# Create ConfigMap for main config
kubectl create configmap flapi-config \
--from-file=flapi.yaml=./config/flapi.yaml \
--namespace=flapi
# Create ConfigMap for SQL templates
kubectl create configmap flapi-sqls \
--from-file=./config/sqls/ \
--namespace=flapi
Mount in your Deployment:
volumeMounts:
- name: config
mountPath: /config/flapi.yaml
subPath: flapi.yaml
- name: sqls
mountPath: /config/sqls
volumes:
- name: config
configMap:
name: flapi-config
- name: sqls
configMap:
name: flapi-sqls
Advantages:
- ✅ Native Kubernetes integration
- ✅ Version controlled via kubectl
- ✅ Can be updated without rebuilding images
Disadvantages:
- ❌ ConfigMap size limit (1MB)
- ❌ Requires kubectl access to update
For large SQL template collections, use PersistentVolumes instead of ConfigMaps.
3️⃣ Cloud Storage Mount (Cloud Run, Lambda, Azure)
Best for: Serverless platforms, multi-region deployments
Google Cloud Run (GCS Bucket):
# 1. Upload config
gsutil -m cp -r ./config gs://my-bucket/flapi-config/
# 2. Deploy with GCS mount
gcloud run deploy flapi \
--image ghcr.io/datazoode/flapi:latest \
--add-volume=name=config,type=cloud-storage,bucket=my-bucket \
--add-volume-mount=volume=config,mount-path=/config
AWS Lambda (EFS Mount):
# 1. Create EFS file system
aws efs create-file-system --tags Key=Name,Value=flapi-config
# 2. Mount EFS to EC2, upload config to /config
# 3. Create Lambda with EFS mount
aws lambda create-function \
--function-name flapi \
--file-system-configs "Arn=arn:aws:elasticfilesystem:...,LocalMountPath=/config"
Advantages:
- ✅ Works with serverless platforms
- ✅ Centralized configuration storage
- ✅ Can be shared across multiple instances
Disadvantages:
- ❌ Increased cold start time
- ❌ Additional cost for storage
- ❌ More complex setup
4️⃣ Baked-In Configuration (Custom Image)
Best for: Immutable deployments, CI/CD pipelines
Create a custom Docker image with config baked in:
FROM ghcr.io/datazoode/flapi:latest
# Copy configuration into image
COPY config/flapi.yaml /config/flapi.yaml
COPY config/sqls /config/sqls
# The config-file path is passed via CLI (-c), not via env var
CMD ["/app/flapi", "-c", "/config/flapi.yaml"]
Build and deploy:
docker build -t my-org/flapi:v1.0 .
docker push my-org/flapi:v1.0
Advantages:
- ✅ Self-contained, portable image
- ✅ Fastest cold start (no external mounts)
- ✅ Works on any platform
Disadvantages:
- ❌ Config changes require image rebuild
- ❌ Larger image size
- ❌ Less flexible
Never bake secrets (database passwords, API keys) into images. Use environment variables or secret management instead.
Configuration Update Strategies
| Platform | Update Method | Downtime | Rollback |
|---|---|---|---|
| Docker Compose | Edit files → docker-compose restart | < 1s | Manual (git revert) |
| Kubernetes | kubectl apply configmap → Rolling update | 0s | kubectl rollout undo |
| Cloud Run | Update GCS bucket → Redeploy | ~30s | Deploy previous revision |
| Custom Image | Rebuild → Push → Deploy | ~2-5min | Deploy previous tag |
Configuration Validation
Before deploying, validate your configuration:
# Validate configuration and exit (does NOT start the server)
docker run --rm \
-v $(pwd)/config:/config \
ghcr.io/datazoode/flapi:latest \
-c /config/flapi.yaml --validate-config
# Check YAML syntax
yamllint config/flapi.yaml config/sqls/*.yaml
# Test SQL templates
# (flAPI will validate SQL on startup)
Environment Variables
The flAPI server itself does not read its config-file path, HTTP port, or log level from environment variables — those are set via CLI flags (-c, -p, --log-level). What environment variables do control:
| Variable | Read by | Purpose |
|---|---|---|
FLAPI_CONFIG_SERVICE_TOKEN | flapi server | Bearer token for the runtime config service API (fallback for --config-service-token) |
FLAPI_NO_TELEMETRY | flapi server | Set to 1, true, or yes to disable startup/shutdown telemetry |
FLAPI_BASE_URL | flapii CLI client | Default base URL for the config service |
FLAPI_TOKEN | flapii CLI client | Bearer token used by flapii |
FLAPI_TIMEOUT | flapii CLI client | Request timeout (seconds) |
FLAPI_RETRIES | flapii CLI client | Number of retry attempts |
FLAPI_GEMINI_KEY | flapii CLI client | Gemini API key for AI-assisted features |
Beyond these, any environment variable whose name matches a regex in template.environment-whitelist may be substituted inside flapi.yaml and SQL templates via ${VAR_NAME} — that is how secrets like DB_PASSWORD reach the running server.
# Docker — pass secrets as substitution sources, not as flAPI-specific overrides
docker run \
-e DB_PASSWORD="${DB_PASSWORD}" \
-e FLAPI_NO_TELEMETRY=1 \
-v $(pwd)/config:/config:ro \
-p 8080:8080 \
ghcr.io/datazoode/flapi:latest \
-c /config/flapi.yaml -p 8080 --log-level info
# Kubernetes
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secrets
key: password
- name: FLAPI_NO_TELEMETRY
value: "1"
Valid
--log-levelvalues:debug,info,warning,error(note:warning, notwarn).
Enterprise Cloud Platforms
Google Cloud Run
Best for: Serverless container deployment with auto-scaling to zero
# Deploy to Cloud Run.
# The config-file path is passed as a CLI arg to the binary (--args=...),
# not via an env var — flAPI doesn't read FLAPI_CONFIG.
gcloud run deploy flapi \
--image ghcr.io/datazoode/flapi:latest \
--platform managed \
--region europe-west1 \
--port 8080 \
--memory 2Gi \
--cpu 1 \
--allow-unauthenticated \
--args="-c,/config/flapi.yaml"
Pricing: Pay per request, scales to zero
- Free tier: 2 million requests/month
- Cost: ~$0.00002400 per request (beyond free tier)
AWS App Runner
Best for: Fully managed container service with simple deployment
# Create apprunner.yaml
version: 1.0
runtime: python3
build:
commands:
image: ghcr.io/datazoode/flapi:latest
run:
runtime-version: 3.8
command: /app/flapi -c /config/flapi.yaml
network:
port: 8080
env:
- name: FLAPI_ENV
value: production
# Deploy
aws apprunner create-service \
--service-name flapi \
--source-configuration file://apprunner.yaml
Pricing: Pay for active time
- Cost: ~$0.064/vCPU-hour + $0.007/GB-hour
AWS Lambda
Best for: Sporadic traffic patterns, event-driven workloads
# Package as container image
docker build -t flapi-lambda .
# Push to ECR
aws ecr get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin ACCOUNT.dkr.ecr.us-east-1.amazonaws.com
docker push ACCOUNT.dkr.ecr.us-east-1.amazonaws.com/flapi:latest
# Create function. Use the image's CMD (set in your Dockerfile) to point
# at the config file with `-c /config/flapi.yaml`; flAPI does not read a
# FLAPI_CONFIG env var.
aws lambda create-function \
--function-name flapi \
--package-type Image \
--code ImageUri=ACCOUNT.dkr.ecr.us-east-1.amazonaws.com/flapi:latest \
--role arn:aws:iam::ACCOUNT:role/lambda-flapi \
--memory-size 2048 \
--timeout 30
Lambda containers are ephemeral. Use EFS for persistent DuckDB cache:
--file-system-config Arn=arn:aws:elasticfilesystem:REGION:ACCOUNT:access-point/ACCESS_POINT
Pricing: Pay per invocation
- Free tier: 1M requests + 400,000 GB-seconds/month
- Cost: $0.20 per 1M requests + $0.0000166667 per GB-second
Azure Container Apps
Best for: Kubernetes-based microservices without cluster management
az containerapp create \
--name flapi \
--resource-group myResourceGroup \
--image ghcr.io/datazoode/flapi:latest \
--target-port 8080 \
--ingress external \
--min-replicas 1 \
--max-replicas 10 \
--cpu 1.0 \
--memory 2.0Gi \
--command "/app/flapi" --args "-c,/config/flapi.yaml"
Pricing: Pay for vCPU and memory
- Cost: ~$0.000012/vCPU-second + $0.000002/GB-second
European Cloud Providers
IONOS Cloud
Best for: German/European data sovereignty, GDPR compliance
IONOS provides managed Kubernetes and container hosting with data centers in Germany and Spain.
# Install IONOS CLI
curl -sL https://github.com/ionos-cloud/ionosctl/releases/latest/download/ionosctl-linux-amd64.tar.gz | tar -xzv
# Create Kubernetes cluster (if not exists)
ionosctl k8s cluster create \
--name flapi-cluster \
--k8s-version 1.28 \
--datacenter-id DATACENTER_ID
# Deploy flAPI
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: flapi
labels:
app: flapi
spec:
replicas: 2
selector:
matchLabels:
app: flapi
template:
metadata:
labels:
app: flapi
spec:
containers:
- name: flapi
image: ghcr.io/datazoode/flapi:latest
ports:
- containerPort: 8080
resources:
requests:
memory: "2Gi"
cpu: "1"
limits:
memory: "4Gi"
cpu: "2"
volumeMounts:
- name: config
mountPath: /config
- name: data
mountPath: /data
volumes:
- name: config
persistentVolumeClaim:
claimName: flapi-config
- name: data
persistentVolumeClaim:
claimName: flapi-data
---
apiVersion: v1
kind: Service
metadata:
name: flapi
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 8080
selector:
app: flapi
EOF
Key Features:
- Data centers: Frankfurt (de/fra), Berlin (de/txl), Logroño (es/vit)
- GDPR-compliant infrastructure
- ISO 27001 certified
- German support
Pricing: Managed Kubernetes
- Cost: ~€0.10/hour per worker node + €0.01/GB storage
STACKIT
Best for: Schwarz Group ecosystem, German cloud sovereignty
# Create STACKIT Kubernetes cluster via portal or CLI
stackit kubernetes cluster create \
--project-id PROJECT_ID \
--name flapi-cluster
# Get kubeconfig
stackit kubernetes credentials get \
--cluster-id CLUSTER_ID \
--project-id PROJECT_ID
# Deploy flAPI
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: flapi
spec:
replicas: 2
selector:
matchLabels:
app: flapi
template:
metadata:
labels:
app: flapi
spec:
containers:
- name: flapi
image: ghcr.io/datazoode/flapi:latest
ports:
- containerPort: 8080
resources:
requests:
memory: "2Gi"
cpu: "1000m"
---
apiVersion: v1
kind: Service
metadata:
name: flapi
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 8080
selector:
app: flapi
EOF
Key Features:
- German data centers (operated by Schwarz Digits)
- Enterprise SLA
- Direct integration with SAP systems
- Carbon-neutral operations
Kubernetes
Generic Kubernetes Deployment
Best for: Multi-cloud portability, full control, enterprise scale
Create a complete flAPI deployment with persistent storage and horizontal scaling:
# Namespace
apiVersion: v1
kind: Namespace
metadata:
name: flapi
---
# ConfigMap for flapi.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: flapi-config
namespace: flapi
data:
flapi.yaml: |
project-name: production-api
project-description: Production flAPI deployment
template:
path: '/config/sqls'
connections:
# Your connections here
duckdb:
db_path: /data/flapi.db
access_mode: READ_WRITE
---
# PersistentVolumeClaim for DuckDB cache
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: flapi-data
namespace: flapi
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
storageClassName: standard
---
# Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: flapi
namespace: flapi
labels:
app: flapi
spec:
replicas: 3
selector:
matchLabels:
app: flapi
template:
metadata:
labels:
app: flapi
spec:
containers:
- name: flapi
image: ghcr.io/datazoode/flapi:latest
ports:
- containerPort: 8080
name: http
resources:
requests:
memory: "2Gi"
cpu: "1"
limits:
memory: "4Gi"
cpu: "2"
volumeMounts:
- name: config
mountPath: /config
- name: data
mountPath: /data
env:
- name: FLAPI_ENV
value: "production"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
volumes:
- name: config
configMap:
name: flapi-config
- name: data
persistentVolumeClaim:
claimName: flapi-data
---
# Service
apiVersion: v1
kind: Service
metadata:
name: flapi
namespace: flapi
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
selector:
app: flapi
---
# HorizontalPodAutoscaler
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: flapi
namespace: flapi
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: flapi
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
Deploy:
kubectl apply -f flapi-k8s.yaml
Ingress Configuration
For production deployments, add an Ingress with TLS:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: flapi
namespace: flapi
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- api.yourdomain.com
secretName: flapi-tls
rules:
- host: api.yourdomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: flapi
port:
number: 80
Indie Hacker Platforms
Fly.io
Best for: Edge deployment, global distribution, developer experience
# Install flyctl
curl -L https://fly.io/install.sh | sh
# Launch flAPI
fly launch \
--image ghcr.io/datazoode/flapi:latest \
--name flapi-app \
--region fra \
--vm-size shared-cpu-2x \
--vm-memory 2048
# Add persistent volume for cache
fly volumes create flapi_data --region fra --size 10
# Update fly.toml for volume
cat > fly.toml <<EOF
app = "flapi-app"
[build]
image = "ghcr.io/datazoode/flapi:latest"
[[mounts]]
source = "flapi_data"
destination = "/data"
[[services]]
internal_port = 8080
protocol = "tcp"
[[services.ports]]
handlers = ["http"]
port = 80
[[services.ports]]
handlers = ["tls", "http"]
port = 443
EOF
# Deploy
fly deploy
Pricing: Free tier available
- Free: 3 shared-cpu-1x VMs (256MB RAM)
- Paid: ~$0.0000022/second for shared-cpu-2x (2GB RAM)
Railway
Best for: Git-based deployment, simple setup, PostgreSQL integration
# Install Railway CLI
npm i -g @railway/cli
# Login
railway login
# Initialize project
railway init
# Deploy from Docker image
railway up --image ghcr.io/datazoode/flapi:latest
# Or use railway.json for configuration
cat > railway.json <<EOF
{
"build": {
"dockerImage": "ghcr.io/datazoode/flapi:latest"
},
"deploy": {
"startCommand": "/app/flapi -c /config/flapi.yaml",
"restartPolicyType": "ON_FAILURE",
"healthcheckPath": "/health",
"healthcheckTimeout": 100
}
}
EOF
Pricing: $5 credit/month on free plan
- Free: $5/month credit (covers ~500 hours)
- Paid: $0.000231/GB-hour RAM + $0.00463/vCPU-hour
Render
Best for: Free tier for testing, managed services
# Create render.yaml
cat > render.yaml <<EOF
services:
- type: web
name: flapi
env: docker
image:
url: ghcr.io/datazoode/flapi:latest
plan: starter
healthCheckPath: /health
envVars:
- key: FLAPI_ENV
value: production
disk:
name: flapi-data
mountPath: /data
sizeGB: 10
EOF
# Deploy via Render Dashboard or CLI
render deploy
Pricing: Free tier available
- Free: 750 hours/month, sleeps after inactivity
- Starter: $7/month (persistent, no sleep)
- Standard: $25/month (2 GB RAM, 1 vCPU)
DigitalOcean App Platform
Best for: Predictable pricing, managed databases, simple scaling
# Create .do/app.yaml
mkdir -p .do
cat > .do/app.yaml <<EOF
name: flapi
services:
- name: api
image:
registry_type: GHCR
repository: datazoode/flapi
tag: latest
http_port: 8080
instance_count: 2
instance_size_slug: basic-xs
routes:
- path: /
health_check:
http_path: /health
envs:
- key: FLAPI_ENV
value: production
EOF
# Deploy via doctl
doctl apps create --spec .do/app.yaml
# Or use DigitalOcean Dashboard
Pricing: Predictable monthly cost
- Basic (512MB): $5/month
- Basic (1GB): $12/month
- Professional (2GB): $24/month
Cost-Effective VPS
Hetzner Cloud
Best for: European hosting, best price/performance ratio
# Create server via CLI
hcloud server create \
--name flapi \
--type cx21 \
--image docker-ce \
--ssh-key my-key
# SSH into server
ssh root@YOUR_SERVER_IP
# Create docker-compose.yml
cat > docker-compose.yml <<EOF
version: '3.8'
services:
flapi:
image: ghcr.io/datazoode/flapi:latest
container_name: flapi
restart: unless-stopped
ports:
- "8080:8080"
volumes:
- ./config:/config:ro
- ./data:/data
- ./sqls:/sqls:ro
environment:
- FLAPI_ENV=production
deploy:
resources:
limits:
cpus: '2'
memory: 4G
reservations:
cpus: '1'
memory: 2G
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# Optional: Caddy for HTTPS
caddy:
image: caddy:latest
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
volumes:
caddy_data:
caddy_config:
EOF
# Create Caddyfile for HTTPS
cat > Caddyfile <<EOF
api.yourdomain.com {
reverse_proxy flapi:8080
# Enable compression
encode gzip
# Security headers
header {
Strict-Transport-Security "max-age=31536000;"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
}
}
EOF
# Start services
docker-compose up -d
# View logs
docker-compose logs -f flapi
Pricing: Excellent value
- CX11 (2GB): €4.15/month (~$4.50)
- CX21 (4GB): €5.83/month (~$6.35)
- CX31 (8GB): €10.59/month (~$11.50)