Production Deployment
Pre-Deployment Checklist
Before going live, complete every item on this list:
Security
- Change
GANTRY_ADMIN_PASSWORD— Never usechangemein production - Set
GANTRY_JWT_SECRET— Use a stable 64-character random hex string. If unset, all sessions invalidate on restart. - Set
GANTRY_ENCRYPTION_KEY— Required for stable plugin credential encryption - Enable HTTPS — Use a reverse proxy (Nginx, Caddy, Traefik) or set
--tls-cert/--tls-key - Disable dev mode — Ensure
GANTRY_DEV=false(the default) - Restrict admin access — Create dedicated user accounts; don't share the
adminaccount - Use API keys for automation — Never use passwords in CI/CD; create a
developer-role API key
Data
- Back up your data directory — Set up regular backups of
$GANTRY_DATA_DIR - Back up
encryption.keyseparately — Loss of this key = loss of plugin credentials - Mount a persistent volume if using Docker/Kubernetes
Networking
- Put a reverse proxy in front — Gantry doesn't do TLS termination by default
- Set CORS appropriately — Gantry with
GANTRY_DEV=falseonly serves same-origin requests - Configure health checks — Use
/healthzand/readyzfor load balancer health probes
Reverse Proxy Configuration
Nginx
server {
listen 443 ssl http2;
server_name gantry.your-org.com;
ssl_certificate /etc/nginx/certs/gantry.crt;
ssl_certificate_key /etc/nginx/certs/gantry.key;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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;
# WebSocket support
proxy_read_timeout 86400;
}
}
server {
listen 80;
server_name gantry.your-org.com;
return 301 https://$host$request_uri;
}
Caddy
# Caddyfile
gantry.your-org.com {
reverse_proxy localhost:8080
}
Caddy handles TLS automatically via Let's Encrypt.
Traefik (Docker labels)
services:
gantry:
image: ghcr.io/go2engle/gantry:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.gantry.rule=Host(`gantry.your-org.com`)"
- "traefik.http.routers.gantry.entrypoints=websecure"
- "traefik.http.routers.gantry.tls.certresolver=letsencrypt"
- "traefik.http.services.gantry.loadbalancer.server.port=8080"
Systemd Service (Linux)
# /etc/systemd/system/gantry.service
[Unit]
Description=Gantry IDP
After=network.target
Wants=network.target
[Service]
Type=simple
User=gantry
Group=gantry
WorkingDirectory=/opt/gantry
ExecStart=/usr/local/bin/gantry serve
Restart=on-failure
RestartSec=5
# Environment
Environment=GANTRY_DATA_DIR=/var/lib/gantry
Environment=GANTRY_PORT=8080
EnvironmentFile=/etc/gantry/gantry.env
# Security
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/lib/gantry
[Install]
WantedBy=multi-user.target
# /etc/gantry/gantry.env (chmod 600, owned by gantry:gantry)
GANTRY_ADMIN_PASSWORD=my-secure-password
GANTRY_JWT_SECRET=64charrandombytes
GANTRY_ENCRYPTION_KEY=64charrandombytes
sudo systemctl enable gantry
sudo systemctl start gantry
sudo systemctl status gantry
Monitoring
Health Endpoints
| Endpoint | Use |
|---|---|
GET /healthz | Process is alive. Returns {"status":"ok"} |
GET /readyz | DB is reachable. Returns 503 if not. Use for readiness probe. |
Metrics
GET /metrics returns Prometheus-format metrics:
# HELP gantry_entities_total Total number of entities
# TYPE gantry_entities_total gauge
gantry_entities_total 142
# HELP gantry_http_requests_total Total HTTP requests
# TYPE gantry_http_requests_total counter
gantry_http_requests_total{method="GET",status="200"} 8432
gantry_http_requests_total{method="POST",status="201"} 87
# HELP gantry_request_duration_seconds HTTP request latency
# TYPE gantry_request_duration_seconds histogram
Prometheus Scrape Config
# prometheus.yml
scrape_configs:
- job_name: gantry
static_configs:
- targets: ['gantry.your-org.com:8080']
metrics_path: /metrics
Backup Strategy
SQLite Backup
# Live consistent backup using SQLite backup API
sqlite3 /var/lib/gantry/gantry.db ".backup /backups/gantry-$(date +%Y%m%d-%H%M).db"
Or use VACUUM INTO:
sqlite3 /var/lib/gantry/gantry.db "VACUUM INTO '/backups/gantry-$(date +%Y%m%d-%H%M).db'"
Encryption Key Backup
cp /var/lib/gantry/encryption.key /secure-backup/gantry-encryption.key
Store this in a secure secrets manager (Vault, AWS Secrets Manager, etc.).
Automated Daily Backup (Cron)
# /etc/cron.d/gantry-backup
0 2 * * * gantry sqlite3 /var/lib/gantry/gantry.db "VACUUM INTO '/backups/gantry-$(date +\%Y\%m\%d).db'" && find /backups -name "gantry-*.db" -mtime +30 -delete
Upgrading
Gantry runs database migrations automatically on startup. Upgrades are generally safe:
# Download new binary
curl -sSL https://github.com/go2engle/gantry/releases/download/v0.2.0/gantry_linux_amd64.tar.gz | tar -xz
sudo mv gantry /usr/local/bin/gantry
# Restart
sudo systemctl restart gantry
For Docker:
docker compose pull
docker compose up -d
Backup Before Upgrading
Always back up gantry.db before upgrading across major versions.
High Availability
Gantry with SQLite is inherently single-instance. For HA:
- Switch to PostgreSQL (
GANTRY_DB=postgres://...) - Run multiple Gantry instances behind a load balancer
- Ensure all instances share the same
GANTRY_JWT_SECRETandGANTRY_ENCRYPTION_KEY - Use a shared
GANTRY_DATA_DIRonly for the encryption key (not for SQLite)
HA Status
PostgreSQL-backed HA is currently in progress. Single-instance SQLite is recommended for most deployments.