Deploy: Solo (single VM)
Smallest production deploy: one VM, docker-compose with your app + Postgres + Redis. Good for early-stage / single-tenant / internal-tool apps.
What you get
- One server runs everything (app + DB + Redis)
- Backup = nightly DB dump to S3 / off-site
- Updates =
docker compose pull && up -dafter pushing a new image - Cost: one VM (Hetzner CAX21 ≈ €7/month for arm64, plenty for sub-1k users)
Trade-off: no horizontal scaling, downtime on updates (sub-second), single point of failure. Fine until you have paying customers.
Setup
1. Build a production image
In your app workspace (e.g. samples/showcases/publicstatus/):
yarn buildThis produces dist/ (client bundle) + dist-server/ (server bundle + kumiko.js migrate CLI).
Wrap it in a Dockerfile (see the Docker deploy guide for the full pattern).
2. docker-compose on the VM
services: app: image: ghcr.io/your-org/your-app:latest pull_policy: always environment: DATABASE_URL: postgresql://app:password@db:5432/app REDIS_URL: redis://redis:6379 depends_on: [db, redis] ports: ["80:3000"] restart: unless-stopped
db: image: postgres:17-alpine environment: POSTGRES_DB: app POSTGRES_USER: app POSTGRES_PASSWORD: password volumes: [pg-data:/var/lib/postgresql/data] restart: unless-stopped
redis: image: redis:7-alpine restart: unless-stopped
volumes: pg-data:3. Pre-deploy migrate (every release)
docker compose run --rm app bun /app/kumiko.js migrate applyRuns the Drizzle migrations idempotently. Safe to run on every release — fast no-op if nothing pending.
4. Boot
docker compose up -dApp starts only after a successful boot-validation against the DB schema. If migrations weren’t applied, container exits with SchemaDriftError — no half-started state.
SSL
Either:
- Reverse proxy (Caddy / Traefik / nginx) on the same VM with Let’s Encrypt
- Cloudflare Tunnel — no inbound ports needed, free TLS
Backup
Add a daily cron on the VM:
0 3 * * * docker compose exec -T db pg_dump -U app app | gzip | aws s3 cp - s3://my-backups/$(date +\%Y-\%m-\%d).sql.gzRestore-drill quarterly. Untested backups are not backups.