Two issues would each stop a clean production `docker compose up` for the event: 1. Healthchecks probed http://localhost:{3000,3001}, but the app and frontend bind IPv4 (0.0.0.0) while `localhost` resolves to ::1 (IPv6) first inside the container — so the probe got "connection refused" and neither container ever turned healthy. Caddy is gated on `condition: service_healthy` for both, so on a fresh boot it would block forever and nothing gets served. Switch both probes to 127.0.0.1. (Verified: both containers now report healthy.) 2. The prod caddy service never received DOMAIN, so the Caddyfile's `{$DOMAIN}` site address expanded to empty — malformed site block, no TLS, no serving. Add `environment: { DOMAIN: ${DOMAIN} }` to the caddy service. Also make .env.example honest and event-ready: - Add DATABASE_MAX_CONNECTIONS (real env lever; recommend 30 for ~100 guests). - The DEFAULT_* upload/rate/capacity vars are NOT read from env — they are seeded into the DB config table and managed at runtime via the admin dashboard. Replace the misleading entries (e.g. upload rate showed 10; live value is 100 via migration 015) with a note pointing to the admin UI and the real seeded defaults. - Document that raising COMPRESSION_WORKER_CONCURRENCY also needs the app memory limit raised (ffmpeg), so a video burst can't OOM the box. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
68 lines
4.5 KiB
Plaintext
68 lines
4.5 KiB
Plaintext
# ── Domain ────────────────────────────────────────────────────────────────────
|
|
# Public domain Caddy will serve and obtain a TLS certificate for.
|
|
DOMAIN=my-event.example.com
|
|
|
|
# ── App server ────────────────────────────────────────────────────────────────
|
|
APP_PORT=3000
|
|
# Set to `production` in real deployments. This activates the secret guard that
|
|
# refuses to boot with placeholder JWT_SECRET / ADMIN_PASSWORD_HASH values.
|
|
# (docker-compose.yml already sets APP_ENV=production for the app service.)
|
|
APP_ENV=production
|
|
|
|
# ── Database ──────────────────────────────────────────────────────────────────
|
|
# Set a strong password and keep it in sync between DATABASE_URL and
|
|
# POSTGRES_PASSWORD. Generate one with: openssl rand -hex 24
|
|
DATABASE_URL=postgres://eventsnap:CHANGE_ME_use_a_strong_password@db:5432/eventsnap
|
|
POSTGRES_USER=eventsnap
|
|
POSTGRES_PASSWORD=CHANGE_ME_use_a_strong_password
|
|
POSTGRES_DB=eventsnap
|
|
# Connection pool size. Default 10. For a busy event (~100 guests polling the feed
|
|
# + SSE + uploads at once) raise to ~30 so requests don't queue on a pool permit.
|
|
DATABASE_MAX_CONNECTIONS=30
|
|
|
|
# ── Authentication ────────────────────────────────────────────────────────────
|
|
# Generate with: openssl rand -hex 64
|
|
JWT_SECRET=change_me_to_a_random_64_byte_hex_string
|
|
SESSION_EXPIRY_DAYS=30
|
|
|
|
# Admin dashboard password (bcrypt hash).
|
|
# Generate with: htpasswd -bnBC 12 "" yourpassword | tr -d ':\n'
|
|
# IMPORTANT: keep the SINGLE QUOTES. A bcrypt hash is full of `$` (e.g. $2b$12$…$…),
|
|
# and both Docker Compose's env_file interpolation and dotenvy's variable substitution
|
|
# would otherwise eat the `$…` segments (reading them as unset vars) and corrupt the
|
|
# hash — every admin login then 401s. Single quotes make both read it literally.
|
|
ADMIN_PASSWORD_HASH='$2y$12$placeholder_replace_me'
|
|
|
|
# ── Event ─────────────────────────────────────────────────────────────────────
|
|
EVENT_NAME=Max & Maria's Wedding
|
|
EVENT_SLUG=max-maria-2026
|
|
|
|
# ── Storage ───────────────────────────────────────────────────────────────────
|
|
MEDIA_PATH=/media
|
|
# Export archives (Gallery.zip / Memories.zip). MUST be outside MEDIA_PATH —
|
|
# /media is publicly served, so exports here would be downloadable without auth.
|
|
EXPORT_PATH=/exports
|
|
|
|
# ── Runtime settings (upload limits, rate limits, capacity) ───────────────────
|
|
# NOTE: These are NOT environment variables. Upload size caps, rate limits, guest
|
|
# count and quota tolerance are stored in the database `config` table (seeded once
|
|
# at first boot) and changed at runtime from the ADMIN DASHBOARD — the backend does
|
|
# not read them from .env. Setting them here has no effect. Current seeded defaults:
|
|
# upload rate 100 / hour / guest (raised from 10 by migration 015)
|
|
# feed rate 60 / minute
|
|
# export rate 3 / day
|
|
# max image size 20 MB
|
|
# max video size 500 MB
|
|
# estimated guests 100
|
|
# quota tolerance 0.75 (fraction of disk that triggers the low-storage warning)
|
|
# Adjust these in the admin UI before the event if needed.
|
|
|
|
# ── Workers ───────────────────────────────────────────────────────────────────
|
|
# Number of parallel image/video compression workers. Default 2. This is the main
|
|
# throughput bottleneck: with 2 workers a burst of uploads can take ~5s to appear.
|
|
# For a large event (100+ guests) 4 is a good target — but each worker can run an
|
|
# ffmpeg transcode, so if you raise this ALSO raise the app container's memory limit
|
|
# in docker-compose.yml (`app.deploy.resources.limits.memory`) from 1G to ~2G, or a
|
|
# burst of large videos can OOM the box and take Postgres down with it.
|
|
COMPRESSION_WORKER_CONCURRENCY=2
|