Docker Compose
The recommended production deployment: the app container plus PostgreSQL 17.
Pre-built image (no build tools needed)
curl -O https://raw.githubusercontent.com/mitchelljfranklin/mitch-dojo/master/docker-compose.pull.ymlEdit docker-compose.pull.yml and change the three change-me secrets (each openssl rand -hex 32), then:
docker compose -f docker-compose.pull.yml up -dThis pulls the published image from GitHub Container Registry instead of building from source. Everything below applies identically.
1. Prepare the environment
git clone https://github.com/mitchelljfranklin/mitch-dojo.git
cd mitch-dojoAll environment values live inline in docker-compose.yml — no .env file needed. Change the three change-me secrets (each 32+ characters):
openssl rand -hex 32 # AUTH_SECRET
openssl rand -hex 32 # APP_ENCRYPTION_KEY
openssl rand -hex 32 # CRON_SECRET2. Start the stack
docker compose up -d --buildThis starts:
- app - the Next.js standalone server (node:22.23.4-slim)
- db — PostgreSQL 17 with a persistent volume
The entrypoint applies prisma migrate deploy and seeds system roles + default settings once (idempotent, marker-file guarded), then serves.
3. First run
Open http://localhost:3000 — the app redirects to first-run setup where you create the initial administrator account. Afterwards, configure the dojo under Admin (see the configuration guide).
Updating
git pull
docker compose up -d --buildMigrations apply automatically on boot. Data lives in the named Postgres volume; rebuilds don't touch it.
Development database only
For local development against a real Postgres without running the app in Docker:
docker compose -f docker-compose.db-only.yml up -dData & volumes
| Volume | Contents |
|---|---|
| Postgres data volume | All records — students, attendance, belts, invoices |
DATA_STORAGE_PATH mount | Uploaded files (photos, attachments, logo) |
Back up both — see Backups.
Environment reference
All values live inline in the compose file (edit docker-compose.yml or docker-compose.pull.yml directly — no .env file needed):
| Variable | Current value | Purpose |
|---|---|---|
DATABASE_URL | postgresql://mitch:mitch@db:5432/mitch_dojo | Postgres connection string |
AUTH_SECRET | change-me-… | Session signing secret (min 32 chars) |
APP_ENCRYPTION_KEY | change-me-… | AES-256-GCM key for secrets at rest (min 32 chars) |
CRON_SECRET | change-me-… | Protects /api/cron/run (min 32 chars, enforced) |
APP_URL | http://localhost:3000 | Public base URL (used in emails and public pages) |
DATA_STORAGE_PATH | /app/.storage/files | File storage root |
TRUSTED_PROXY_COUNT | 0 | Proxy hops for client-IP resolution |
TZ | Australia/Brisbane | The dojo's IANA time zone — see the note below |
Change the three
change-mesecrets before exposing the app publicly. The container refuses to start ifCRON_SECRETis shorter than 32 characters.
Time zone
The container's TZ must match the dojo's time zone — there is no in-app timezone setting. Check-in ("today's classes"), session generation, the attendance browser's default window, analytics day buckets, trial follow-up windows, and reminder timing all key off the server's local day. With the default UTC container timezone, a dojo east of UTC would see "No classes today" on the check-in page for any class starting before the UTC day rolls over (10am for UTC+10). Set TZ to your IANA zone (e.g. Australia/Sydney, Europe/London, America/New_York) and recreate the container.
Health check: GET /api/health returns 200 {"status":"ok"}.