Skip to main content

Docker Deployment

Deploy Replane using Docker and Docker Compose for production.

Prerequisites

  • Docker 20.10+
  • Docker Compose v2.0+
  • A domain name (optional, but recommended for HTTPS)
  • OAuth provider (GitHub or Okta)

Production docker-compose.yml

services:
db:
image: postgres:17
restart: unless-stopped
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: replane
volumes:
- replane-db:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
networks:
- replane-network

app:
image: ghcr.io/replane-dev/replane:latest
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: postgresql://postgres:${DB_PASSWORD}@db:5432/replane
BASE_URL: ${BASE_URL}
SECRET_KEY_BASE: ${SECRET_KEY_BASE}

# Authentication (choose one)
GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID}
GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET}

# Optional
ORGANIZATION_NAME: ${ORGANIZATION_NAME}
ALLOW_SELF_APPROVALS: ${ALLOW_SELF_APPROVALS:-false}
ports:
- "3000:3000"
networks:
- replane-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s

volumes:
replane-db:

networks:
replane-network:

Environment Variables

Create a .env file:

# Database
DB_PASSWORD=your-secure-password-here

# Application
BASE_URL=https://replane.yourdomain.com
SECRET_KEY_BASE=generate-a-long-random-string-here

# GitHub OAuth
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret

# Optional
ORGANIZATION_NAME=Your Company Name
ALLOW_SELF_APPROVALS=false

Generating SECRET_KEY_BASE

openssl rand -hex 64

Deploy

# Start services
docker-compose up -d

# Check logs
docker-compose logs -f app

# Check health
curl http://localhost:3000/api/health

Expected response:

{
"status": "ok"
}

HTTPS with Caddy

Use Caddy as a reverse proxy for automatic HTTPS:

docker-compose.yml (add Caddy service)
  caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy-data:/data
- caddy-config:/config
networks:
- replane-network

volumes:
caddy-data:
caddy-config:

Create Caddyfile:

replane.yourdomain.com {
reverse_proxy app:3000
}

Update .env:

BASE_URL=https://replane.yourdomain.com

Remove port mapping from app service (Caddy handles it):

app:
# Remove: ports: - "3000:3000"
# Caddy will proxy to app:3000

Restart:

docker-compose down
docker-compose up -d

Caddy automatically obtains and renews Let's Encrypt certificates.

HTTPS with Nginx

Alternatively, use Nginx:

nginx.conf
server {
listen 80;
server_name replane.yourdomain.com;
return 301 https://$server_name$request_uri;
}

server {
listen 443 ssl http2;
server_name replane.yourdomain.com;

ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;

location / {
proxy_pass http://app:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
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;
}
}

Backups

Database Backups

Create a backup script backup.sh:

#!/bin/bash
DATE=$(date +%Y%m%d_%H%M%S)
docker-compose exec -T db pg_dump -U postgres replane > backup_$DATE.sql
gzip backup_$DATE.sql

Run daily via cron:

0 2 * * * /path/to/backup.sh

Restore from Backup

gunzip backup_20250116_020000.sql.gz
docker-compose exec -T db psql -U postgres replane < backup_20250116_020000.sql

Updates

Update to the latest version:

# Pull latest image
docker-compose pull app

# Restart (migrations run automatically)
docker-compose up -d app

# Check logs
docker-compose logs -f app

Monitoring

Health Check

curl https://replane.yourdomain.com/api/health

Logs

# Follow logs
docker-compose logs -f app

# Last 100 lines
docker-compose logs --tail=100 app

Metrics

Monitor:

  • CPU and memory usage
  • Database connections
  • Response times
  • SSE connection count

Use tools like Prometheus, Grafana, or your cloud provider's monitoring.

Scaling

Multiple App Instances

Use Docker Swarm or Kubernetes to run multiple app instances behind a load balancer.

Database Read Replicas

For read-heavy workloads, configure PostgreSQL read replicas:

app:
environment:
DATABASE_URL: postgresql://postgres:pass@db-primary:5432/replane
DATABASE_READ_URL: postgresql://postgres:pass@db-replica:5432/replane

Troubleshooting

Container won't start

Check logs:

docker-compose logs app

Common issues:

  • Missing environment variables
  • Database connection errors
  • Invalid OAuth configuration

Database connection failed

Ensure PostgreSQL is healthy:

docker-compose ps db

Check connectivity:

docker-compose exec app sh -c 'psql $DATABASE_URL -c "SELECT 1"'

OAuth callback errors

Verify:

  • Callback URL matches OAuth provider settings exactly
  • BASE_URL environment variable is correct
  • OAuth credentials are valid

Next Steps