Operational

Runbook: One-Click Self-Hosting with Auto-TLS

TL;DR: Provision and deploy a full self-hosted application stack (Go API, Next.js, PostgreSQL, Redis, MinIO, FFmpeg worker) onto a fresh Hetzner, AWS, or GCP server — or any Ubuntu 24.04 host — in a single command, with Caddy handling Let's Encrypt TLS automatically and nightly backups to object storage.

This runbook deploys the Porch platform — a private, invite-only social app — from zero to a TLS-secured production host. The same flow targets Hetzner, AWS, GCP, or an existing Ubuntu box; the launch scripts provision the server, install Docker, render config, and bring the stack up behind Caddy.

Stack that gets deployed

Go 1.25 API (:8080) · Next.js 15 frontend (:3000) · PostgreSQL 16 · Redis 7 · MinIO (S3-compatible) · FFmpeg transcoding worker · Caddy 2 reverse proxy (:80/:443, automatic Let’s Encrypt). Everything runs under Docker Compose in /opt/porch.

1. Configure secrets

cp .env.example .env.prod
openssl rand -hex 64                 # JWT_SECRET
openssl rand -hex 20                 # DB_PASSWORD / REDIS_PASSWORD
npx web-push generate-vapid-keys     # VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY
# then set DOMAIN, MAILGUN_*, and (optionally) TWILIO_* in .env.prod

2. Provision & deploy (pick one)

# Hetzner / AWS / GCP — provisions the server and deploys in one shot
./scripts/launch-hetzner.sh --domain porch.example.com --email [email protected] --generate-secrets
./scripts/launch-aws.sh     --domain porch.example.com --email [email protected] --generate-secrets
./scripts/launch-gcp.sh     --domain porch.example.com --email [email protected] --generate-secrets

# Existing Ubuntu 22.04/24.04 server
./scripts/deploy.sh root@<server-ip>

3. Point DNS & verify TLS

Add an A record for your domain to the IP the script prints. Wait ~30–60s for propagation; Caddy then issues the certificate on first request. Visit https://porch.example.com to confirm.

4. Bootstrap the first admin

ssh root@<server-ip> && cd /opt/porch && source .env
# create an invite token, register at /register?invite=<token>, then:
docker exec -i porch-postgres psql -U "$DB_USER" -d "$DB_NAME" \
  -c "UPDATE users SET role = 'admin' WHERE email = '[email protected]';"

Ongoing operations

./scripts/deploy.sh root@<server-ip> update      # git pull + rebuild + up -d
docker compose -f docker-compose.prod.yml logs -f api      # tail backend
docker compose -f docker-compose.prod.yml ps               # service status
/opt/porch/scripts/backup.sh                               # manual backup

Backups run nightly at 03:00 to the MinIO porch-backups bucket (gzipped Postgres dump + media tarball, 30-day retention). Defaults worth knowing: deploy dir /opt/porch, public ports 80/443, persistent porch_postgres and porch_media_data volumes, and Caddy routing /api/* to the backend and everything else to the frontend.