Docker Deployment
Single-container quickstart
Section titled “Single-container quickstart”For local development and testing, run Authgent with SQLite in a single container:
# Generate signing keysopenssl ecparam -genkey -name prime256v1 -noout -out ec-private.pemchmod 600 ec-private.pem
# Run Authgentdocker run -d \ --name authgent \ -p 8080:8080 \ -v $(pwd)/ec-private.pem:/keys/ec-private.pem:ro \ -e AUTHGENT_ISSUER=http://localhost:8080 \ -e AUTHGENT_SIGNING_KEY=/keys/ec-private.pem \ -e AUTHGENT_SIGNING_ALG=ES256 \ -e AUTHGENT_DB_DSN=sqlite:///data/authgent.db \ -v authgent-data:/data \ authgent/authgent:latestVerify it’s running:
curl http://localhost:8080/.well-known/oauth-authorization-server | jq .SQLite is fine for development but not recommended for production — it doesn’t support concurrent writes and has no replication. Use PostgreSQL for production deployments.
Production setup with PostgreSQL
Section titled “Production setup with PostgreSQL”docker-compose.yml
Section titled “docker-compose.yml”version: "3.9"
services: caddy: image: caddy:2-alpine restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro - caddy-data:/data - caddy-config:/config depends_on: authgent: condition: service_healthy networks: - frontend
authgent: image: authgent/authgent:latest restart: unless-stopped expose: - "8080" environment: AUTHGENT_ISSUER: https://auth.yourcompany.com AUTHGENT_SIGNING_KEY: /keys/ec-private.pem AUTHGENT_SIGNING_ALG: ES256 AUTHGENT_DB_DSN: postgres://authgent:${POSTGRES_PASSWORD}@postgres:5432/authgent?sslmode=disable AUTHGENT_PORT: "8080" AUTHGENT_LOG_LEVEL: info AUTHGENT_LOG_FORMAT: json AUTHGENT_TOKEN_TTL: "300" AUTHGENT_REFRESH_TTL: "86400" AUTHGENT_DCR_MODE: open AUTHGENT_KEY_ROTATION_DAYS: "30" volumes: - ./keys/ec-private.pem:/keys/ec-private.pem:ro healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"] interval: 10s timeout: 5s retries: 5 start_period: 10s depends_on: postgres: condition: service_healthy networks: - frontend - backend
postgres: image: postgres:16-alpine restart: unless-stopped environment: POSTGRES_DB: authgent POSTGRES_USER: authgent POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} volumes: - postgres-data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U authgent -d authgent"] interval: 5s timeout: 5s retries: 5 start_period: 10s networks: - backend
volumes: postgres-data: caddy-data: caddy-config:
networks: frontend: backend:Caddyfile
Section titled “Caddyfile”auth.yourcompany.com { reverse_proxy authgent:8080}POSTGRES_PASSWORD=your-secure-password-hereStart everything
Section titled “Start everything”# Generate signing keysmkdir -p keysopenssl ecparam -genkey -name prime256v1 -noout -out keys/ec-private.pemchmod 600 keys/ec-private.pem
# Create .env fileecho "POSTGRES_PASSWORD=$(openssl rand -base64 32)" > .env
# Start all servicesdocker compose up -d
# Check healthdocker compose psdocker compose logs authgent --tail 20
# Verify Authgent is runningcurl https://auth.yourcompany.com/.well-known/oauth-authorization-server | jq .Key management
Section titled “Key management”Volume mounting (recommended)
Section titled “Volume mounting (recommended)”Mount your private key as a read-only volume:
volumes: - ./keys/ec-private.pem:/keys/ec-private.pem:roSet restrictive permissions on the host:
chmod 600 keys/ec-private.pemchown root:root keys/ec-private.pemEnvironment variable alternative
Section titled “Environment variable alternative”For environments where volume mounting is impractical (some PaaS platforms), you can pass the key as an environment variable:
docker run -d \ -e AUTHGENT_SIGNING_KEY_DATA="$(cat ec-private.pem)" \ authgent/authgent:latestWhen AUTHGENT_SIGNING_KEY_DATA is set, Authgent reads the key directly from the variable instead of a file path. This is less secure than volume mounting — the key may appear in docker inspect output and process listings.
Key rotation
Section titled “Key rotation”Authgent supports automatic key rotation. When AUTHGENT_KEY_ROTATION_DAYS is set, Authgent:
- Generates a new signing key on the configured schedule
- Adds the new key to the JWKS endpoint
- Signs new tokens with the new key
- Keeps the old key in JWKS for verification (tokens signed with it remain valid until they expire)
- Removes the old key from JWKS after 2x the rotation period
environment: AUTHGENT_KEY_ROTATION_DAYS: "30" # Rotate every 30 daysHealth checks
Section titled “Health checks”Authgent exposes a /health endpoint that returns the service status:
curl http://localhost:8080/health{ "status": "healthy", "version": "0.1.0", "database": "connected", "uptime_seconds": 3600}Docker HEALTHCHECK
Section titled “Docker HEALTHCHECK”The docker-compose.yml above includes a health check. For standalone containers:
docker run -d \ --name authgent \ --health-cmd="wget --no-verbose --tries=1 --spider http://localhost:8080/health" \ --health-interval=10s \ --health-timeout=5s \ --health-retries=5 \ --health-start-period=10s \ authgent/authgent:latestCheck health status:
docker inspect --format='{{.State.Health.Status}}' authgentReverse proxy setup
Section titled “Reverse proxy setup”In production, always run Authgent behind a reverse proxy for TLS termination.
Caddy (recommended)
Section titled “Caddy (recommended)”Caddy automatically provisions TLS certificates via Let’s Encrypt:
auth.yourcompany.com { reverse_proxy authgent:8080
header { Strict-Transport-Security "max-age=31536000; includeSubDomains" X-Content-Type-Options "nosniff" X-Frame-Options "DENY" }}server { listen 443 ssl http2; server_name auth.yourcompany.com;
ssl_certificate /etc/letsencrypt/live/auth.yourcompany.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/auth.yourcompany.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5;
location / { proxy_pass http://authgent:8080; 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; }
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "DENY" always;}
server { listen 80; server_name auth.yourcompany.com; return 301 https://$host$request_uri;}Environment variables reference
Section titled “Environment variables reference”| Variable | Required | Default | Description |
|---|---|---|---|
AUTHGENT_ISSUER | Yes | — | Public URL of this Authgent instance |
AUTHGENT_SIGNING_KEY | Yes | — | Path to EC or RSA private key PEM file |
AUTHGENT_SIGNING_ALG | No | ES256 | JWT signing algorithm (ES256, RS256) |
AUTHGENT_DB_DSN | Yes | — | Database connection string |
AUTHGENT_PORT | No | 8080 | HTTP port to listen on |
AUTHGENT_LOG_LEVEL | No | info | Log level (debug, info, warn, error) |
AUTHGENT_LOG_FORMAT | No | json | Log format (json, text) |
AUTHGENT_TOKEN_TTL | No | 300 | Access token TTL in seconds |
AUTHGENT_REFRESH_TTL | No | 86400 | Refresh token TTL in seconds |
AUTHGENT_DCR_MODE | No | open | Dynamic Client Registration mode |
AUTHGENT_KEY_ROTATION_DAYS | No | 30 | Days between automatic key rotation |
See the Configuration Reference for the complete list including IdP and observability settings.
Upgrading
Section titled “Upgrading”Rolling update procedure
Section titled “Rolling update procedure”- Pull the new image:
docker compose pull authgent-
Check for migration notes in the release changelog. Authgent runs database migrations automatically on startup.
-
Restart with zero downtime:
docker compose up -d --no-deps authgentThe --no-deps flag restarts only the Authgent container without touching PostgreSQL or Caddy.
- Verify the upgrade:
# Check versioncurl http://localhost:8080/health | jq .version
# Check logs for migration outputdocker compose logs authgent --tail 50
# Verify OAuth metadatacurl https://auth.yourcompany.com/.well-known/oauth-authorization-server | jq .Rollback
Section titled “Rollback”If something goes wrong:
# Pin to the previous versiondocker compose down authgentdocker compose up -d --no-deps authgent # with image tag pinned in docker-compose.ymlAlways pin image tags in production (authgent/authgent:0.1.0) rather than using latest.
Backup
Section titled “Backup”PostgreSQL backup
Section titled “PostgreSQL backup”# Full database dumpdocker compose exec postgres pg_dump -U authgent -d authgent > backup-$(date +%Y%m%d).sql
# Compressed backupdocker compose exec postgres pg_dump -U authgent -d authgent | gzip > backup-$(date +%Y%m%d).sql.gz
# Restore from backupcat backup-20250101.sql | docker compose exec -T postgres psql -U authgent -d authgentAutomated backup with cron
Section titled “Automated backup with cron”# Add to crontab: daily backup at 2 AM, keep 30 days0 2 * * * docker compose -f /opt/authgent/docker-compose.yml exec -T postgres pg_dump -U authgent -d authgent | gzip > /opt/authgent/backups/backup-$(date +\%Y\%m\%d).sql.gz && find /opt/authgent/backups -name "*.sql.gz" -mtime +30 -deleteKey backup
Section titled “Key backup”Your signing key is the most critical asset — if lost, all issued tokens become unverifiable.
# Backup the private key (encrypt it!)gpg --symmetric --cipher-algo AES256 keys/ec-private.pem# Creates keys/ec-private.pem.gpg — store this in your secrets manager
# Restoregpg --decrypt keys/ec-private.pem.gpg > keys/ec-private.pemchmod 600 keys/ec-private.pemStore encrypted key backups in a separate location from your database backups. Use your organization’s secrets management system (Vault, AWS Secrets Manager, GCP Secret Manager) for production key storage.