Zum Inhalt springen

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 -d after 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/):

Terminal-Fenster
yarn build

This 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

docker-compose.yml
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)

Terminal-Fenster
docker compose run --rm app bun /app/kumiko.js migrate apply

Runs the Drizzle migrations idempotently. Safe to run on every release — fast no-op if nothing pending.

4. Boot

Terminal-Fenster
docker compose up -d

App 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:

Terminal-Fenster
0 3 * * * docker compose exec -T db pg_dump -U app app | gzip | aws s3 cp - s3://my-backups/$(date +\%Y-\%m-\%d).sql.gz

Restore-drill quarterly. Untested backups are not backups.