Part 3 — Deployment

For whoever runs the server. The whole app is one Docker Compose stack, so it runs the same on a home server or any cloud VM.

First install

git clone <your-repo> polltastic && cd polltastic
cp .env.example .env

Edit .env:

VariableWhat to set
ADMIN_PASSWORD Password for the first admin account. Change this.
ADMIN_EMAIL Email for that first admin (e.g. you@church.org)
APP_SECRET A long random string. Signs winner QR codes — keep it stable, or old codes stop verifying
POSTGRES_PASSWORD Any strong password
DOMAIN :80 for a LAN test; your real domain in production

Generate good secrets with openssl rand -base64 32.

Then:

docker compose up -d --build

Open the site and sign in at /teach with ADMIN_EMAIL / ADMIN_PASSWORD. That account is created automatically on first boot. Change the password, then add your teachers from /users.

HTTPS

Set DOMAIN=poll.yourchurch.org in .env, point that DNS name at the server, and open ports 80 and 443. Caddy fetches and renews a Let's Encrypt certificate automatically — nothing else to configure.

HTTPS isn't optional in practice: phone notifications, "Add to Home Screen", and the clipboard buttons all require it.

Push notifications (optional)

npm run vapid

Put the two keys into .env as VAPID_PUBLIC_KEY and VAPID_PRIVATE_KEY, set VAPID_SUBJECT=mailto:you@church.org, then docker compose up -d. Without these the app still works — phones just don't get notifications.

Backups

Everything lives in Postgres:

docker compose exec -T db pg_dump -U polltastic polltastic > backup-$(date +%F).sql

Keep .env somewhere safe too — losing APP_SECRET invalidates outstanding winner QR codes.

Moving to another host

  1. pg_dump on the old server (above).
  2. Copy the repo and .env to the new one, set DOMAIN.
  3. docker compose up -d --build
  4. Restore: ``sh docker compose exec -T db psql -U polltastic polltastic < backup.sql ``
  5. Point DNS at the new server.

No re-architecture and no data loss — that portability is a deliberate design constraint, so you're never locked to one host.

Upgrading

git pull && docker compose up -d --build

Database migrations run automatically on boot.

Branding the icons

App icons are generated, no image editor needed. Change the colour in tools/make-icons.mjs, then:

npm run icons && docker compose up -d --build

Scale

A single instance comfortably handles a congregation. Everything is in one Node process with in-memory realtime state, which is the right choice at this size. If you ever needed multiple instances, the realtime layer (server/src/hub.ts) is deliberately isolated so it can be swapped for Redis without touching anything else.