Skip to main content

Production Deployment

Pre-Deployment Checklist

Before going live, complete every item on this list:

Security

  • Change GANTRY_ADMIN_PASSWORD — Never use changeme in 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 admin account
  • 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.key separately — 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=false only serves same-origin requests
  • Configure health checks — Use /healthz and /readyz for 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

EndpointUse
GET /healthzProcess is alive. Returns {"status":"ok"}
GET /readyzDB 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:

  1. Switch to PostgreSQL (GANTRY_DB=postgres://...)
  2. Run multiple Gantry instances behind a load balancer
  3. Ensure all instances share the same GANTRY_JWT_SECRET and GANTRY_ENCRYPTION_KEY
  4. Use a shared GANTRY_DATA_DIR only for the encryption key (not for SQLite)
HA Status

PostgreSQL-backed HA is currently in progress. Single-instance SQLite is recommended for most deployments.