Skip to content

Docker Deployment

For local development and testing, run Authgent with SQLite in a single container:

Terminal window
# Generate signing keys
openssl ecparam -genkey -name prime256v1 -noout -out ec-private.pem
chmod 600 ec-private.pem
# Run Authgent
docker 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:latest

Verify it’s running:

Terminal window
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.

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:
auth.yourcompany.com {
reverse_proxy authgent:8080
}
Terminal window
POSTGRES_PASSWORD=your-secure-password-here
Terminal window
# Generate signing keys
mkdir -p keys
openssl ecparam -genkey -name prime256v1 -noout -out keys/ec-private.pem
chmod 600 keys/ec-private.pem
# Create .env file
echo "POSTGRES_PASSWORD=$(openssl rand -base64 32)" > .env
# Start all services
docker compose up -d
# Check health
docker compose ps
docker compose logs authgent --tail 20
# Verify Authgent is running
curl https://auth.yourcompany.com/.well-known/oauth-authorization-server | jq .

Mount your private key as a read-only volume:

volumes:
- ./keys/ec-private.pem:/keys/ec-private.pem:ro

Set restrictive permissions on the host:

Terminal window
chmod 600 keys/ec-private.pem
chown root:root keys/ec-private.pem

For environments where volume mounting is impractical (some PaaS platforms), you can pass the key as an environment variable:

Terminal window
docker run -d \
-e AUTHGENT_SIGNING_KEY_DATA="$(cat ec-private.pem)" \
authgent/authgent:latest

When 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.

Authgent supports automatic key rotation. When AUTHGENT_KEY_ROTATION_DAYS is set, Authgent:

  1. Generates a new signing key on the configured schedule
  2. Adds the new key to the JWKS endpoint
  3. Signs new tokens with the new key
  4. Keeps the old key in JWKS for verification (tokens signed with it remain valid until they expire)
  5. Removes the old key from JWKS after 2x the rotation period
environment:
AUTHGENT_KEY_ROTATION_DAYS: "30" # Rotate every 30 days

Authgent exposes a /health endpoint that returns the service status:

Terminal window
curl http://localhost:8080/health
{
"status": "healthy",
"version": "0.1.0",
"database": "connected",
"uptime_seconds": 3600
}

The docker-compose.yml above includes a health check. For standalone containers:

Terminal window
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:latest

Check health status:

Terminal window
docker inspect --format='{{.State.Health.Status}}' authgent

In production, always run Authgent behind a reverse proxy for TLS termination.

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;
}
VariableRequiredDefaultDescription
AUTHGENT_ISSUERYesPublic URL of this Authgent instance
AUTHGENT_SIGNING_KEYYesPath to EC or RSA private key PEM file
AUTHGENT_SIGNING_ALGNoES256JWT signing algorithm (ES256, RS256)
AUTHGENT_DB_DSNYesDatabase connection string
AUTHGENT_PORTNo8080HTTP port to listen on
AUTHGENT_LOG_LEVELNoinfoLog level (debug, info, warn, error)
AUTHGENT_LOG_FORMATNojsonLog format (json, text)
AUTHGENT_TOKEN_TTLNo300Access token TTL in seconds
AUTHGENT_REFRESH_TTLNo86400Refresh token TTL in seconds
AUTHGENT_DCR_MODENoopenDynamic Client Registration mode
AUTHGENT_KEY_ROTATION_DAYSNo30Days between automatic key rotation

See the Configuration Reference for the complete list including IdP and observability settings.

  1. Pull the new image:
Terminal window
docker compose pull authgent
  1. Check for migration notes in the release changelog. Authgent runs database migrations automatically on startup.

  2. Restart with zero downtime:

Terminal window
docker compose up -d --no-deps authgent

The --no-deps flag restarts only the Authgent container without touching PostgreSQL or Caddy.

  1. Verify the upgrade:
Terminal window
# Check version
curl http://localhost:8080/health | jq .version
# Check logs for migration output
docker compose logs authgent --tail 50
# Verify OAuth metadata
curl https://auth.yourcompany.com/.well-known/oauth-authorization-server | jq .

If something goes wrong:

Terminal window
# Pin to the previous version
docker compose down authgent
docker compose up -d --no-deps authgent # with image tag pinned in docker-compose.yml

Always pin image tags in production (authgent/authgent:0.1.0) rather than using latest.

Terminal window
# Full database dump
docker compose exec postgres pg_dump -U authgent -d authgent > backup-$(date +%Y%m%d).sql
# Compressed backup
docker compose exec postgres pg_dump -U authgent -d authgent | gzip > backup-$(date +%Y%m%d).sql.gz
# Restore from backup
cat backup-20250101.sql | docker compose exec -T postgres psql -U authgent -d authgent
Terminal window
# Add to crontab: daily backup at 2 AM, keep 30 days
0 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 -delete

Your signing key is the most critical asset — if lost, all issued tokens become unverifiable.

Terminal window
# 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
# Restore
gpg --decrypt keys/ec-private.pem.gpg > keys/ec-private.pem
chmod 600 keys/ec-private.pem

Store 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.