Skip to Content
Self-host

Self-host

shush is a four-service stack you can run on any container platform:

  • postgres — primary store (encrypted secret values, audit log).
  • redis — session store + device-code flow.
  • api — Bun + Hono backend.
  • web — Vite + nginx frontend.

This page covers two common setups: Docker Compose (for evaluation / small deployments) and Dokploy (for managed, domain-routed prod).

1. Generate secrets

Run these locally and store the output somewhere safe (1Password, AWS Secrets Manager, Vault — not in the same shush instance you’re about to boot):

openssl rand -base64 32 # → MASTER_KEK_B64 (wraps every org's DEK) openssl rand -base64 32 # → BETTER_AUTH_SECRET (signs sessions)

Losing MASTER_KEK_B64 permanently destroys every stored secret. Back it up to a separate, audited secret store — not in this app.

2. Docker Compose

The repo ships a production docker-compose.yml that builds and runs all four services:

docker compose up --build

This wires up:

  • postgres:16-alpine on :5432 (volume shush_postgres_data).
  • redis:7-alpine on :6379 with AOF persistence (volume shush_redis_data).
  • The API on :8787, built from apps/api/Dockerfile.
  • The web on :5173 → 80, built from apps/web/Dockerfile.

Required env vars for docker compose up:

BETTER_AUTH_SECRET=… MASTER_KEK_B64=… RESEND_API_KEY=… # for invitation / 2FA emails EMAIL_FROM=shush <no-reply@yourdomain.com>

Optional, with sensible defaults:

APP_URL=http://localhost:5173 BETTER_AUTH_URL=http://localhost:8787 CORS_ORIGINS=http://localhost:5173 VITE_API_URL=http://localhost:8787

The API depends on postgres + redis being healthy before it starts — the compose file handles that with healthchecks.

3. Dokploy

The repo also includes DOKPLOY.md with a step-by-step guide for hosting on Dokploy. The shape:

a. Postgres (managed DB)

  • Postgres 16, database shush, user shush, strong password.
  • Internal URL: postgres://shush:<password>@shush-postgres:5432/shush.

b. Redis (managed DB)

  • Redis 7 with AOF persistence on.
  • Internal URL: redis://shush-redis:6379.

c. API service

  • Type: Application → Dockerfile.

  • Build path: /, Dockerfile: apps/api/Dockerfile.

  • Internal port: 8787.

  • Domain: api.shush.yourdomain.com (HTTPS via Traefik / Lets Encrypt).

  • Env:

    NODE_ENV=production PORT=8787 DATABASE_URL=postgres://shush:<password>@shush-postgres:5432/shush REDIS_URL=redis://shush-redis:6379 APP_URL=https://shush.yourdomain.com BETTER_AUTH_URL=https://api.shush.yourdomain.com BETTER_AUTH_SECRET=… MASTER_KEK_B64=… CORS_ORIGINS=https://shush.yourdomain.com RESEND_API_KEY=… EMAIL_FROM=shush <no-reply@yourdomain.com>
  • Pre-deploy hook (one-time, then disable):

    cd apps/api && bun run db:push

    Or commit migrations with bun run db:generate and ship db:migrate.

d. Web service

  • Type: Application → Dockerfile, apps/web/Dockerfile.
  • Build arg: VITE_API_URL=https://api.shush.yourdomain.com.
  • Internal port: 80.
  • Domain: shush.yourdomain.com.

4. Deploy order

  1. Postgres + Redis first (must be healthy before the API boots).
  2. Run the API’s db:push pre-deploy.
  3. Start the API.
  4. Build the web with VITE_API_URL pointing at the API’s public domain.

5. Verify

curl https://api.shush.yourdomain.com/health # → {"ok":true,"checks":{"postgres":"ok","redis":"ok"}}

6. Rotating MASTER_KEK_B64

MASTER_KEK_B64 wraps each org’s DEK — not the secret values directly. Rotation is therefore cheap (one row per org):

  1. Generate a new MASTER_KEK_B64.
  2. Run apps/api/scripts/rotate-kek.ts with OLD_MASTER_KEK_B64 and MASTER_KEK_B64 set.
  3. Roll the env var, redeploy.

7. Backups

  • Use your platform’s managed-DB backup schedule (daily, retain 30d).
  • Encrypted columns (secret_value_version.ciphertext) are unreadable without MASTER_KEK_B64 — back up the KEK separately, in a system with its own access controls and audit log.
Last updated on