hackathons.monashcoding.com runs as a Dokploy Compose service (Compose + Traefik)
on the Oracle Cloud ARM VM. This is the runbook for standing it up and for the next
committee to inherit it with zero tribal knowledge.
What's in the repo already:
docker-compose.dokploy.yml— the production compose (Traefik labels, no host ports, secrets via${VAR}interpolation).Dockerfile+docker-entrypoint.sh— build the app; the entrypoint runs Drizzle migrations then starts the server on every deploy..github/workflows/deploy.yml— optional CI-gated deploy (only ships after tests pass).
The plain
docker-compose.ymlis the local dev file and is not used in production.
Point an A record for hackathons.monashcoding.com at the Oracle VM's public IP. Let's
Encrypt (via Traefik) needs this resolving before it can issue a certificate.
- In Dokploy: Create Project (or use the MAC project) → Create Service → Compose.
- Provider = GitHub, repo
monashcoding/hackathons, branchmac-hackathon-mvp. - Compose Path:
docker-compose.dokploy.yml. - Save. Don't deploy yet — set the environment first.
Paste these, filling in the real secrets. All secrets come from role inboxes
(projects@monashcoding.com) — no personal accounts.
# Organiser access is by mac-auth ROLE (committee/exec/admin), not the `team`
# claim. The default already covers the committee — usually leave it unset.
ORGANISER_ROLES=committee,exec,admin
HUMANITIX_API_KEY=<from Humanitix Console → Account → Advanced → Public API key>
TICKET_SYNC_REVOKE_THRESHOLD=0.20
DISCORD_ALERT_WEBHOOK_URL=<optional: a Discord webhook for sync-failure alerts>
# Notion content CMS (leave blank until the Notion databases exist; the site
# still runs — content sync just no-ops):
NOTION_API_KEY=
NOTION_EVENTS_DB_ID=
NOTION_PRIZES_DB_ID=
NOTION_JUDGES_DB_ID=
NOTION_SCHEDULE_DB_ID=
NOTION_SPONSORS_DB_ID=
NOTION_FAQ_DB_ID=
DATABASE_URL, MAC_AUTH_*, JWT_AUDIENCE, PORT, PUBLIC_URL, and the Postgres
credentials are hard-set in the compose file — you don't add them here.
Organiser access is role-based. A signed-in user reaches
/adminonly if their mac-auth token carries acommittee,exec, oradminrole — these come from the central committee roster (curated in Notion, synced into mac-auth). If the director can't get in, confirm they're on that roster; you don't manage organisers in this app.Single sign-on requires the
*.monashcoding.comorigin. The dashboard/find-team/admin sign-in works because the app is served fromhackathons.monashcoding.comand reads the shared mac-auth session cookie. It will NOT work fromlocalhostor an IP — mac-auth only trusts*.monashcoding.comorigins.
Dokploy → the service → Domains → add hackathons.monashcoding.com, HTTPS on,
certificate Let's Encrypt, container port 3000. (The compose already carries the
Traefik labels, so this mainly registers the domain + cert with Dokploy.)
The compose uses tls.certresolver=letsencrypt and entrypoint websecure — Dokploy's
defaults. If your Dokploy uses a differently-named cert resolver, update that label.
Hit Deploy. Dokploy clones the branch, builds the image, brings up Postgres, waits for its healthcheck, runs migrations, and starts the app. Watch the logs for:
[migrate] done.
[server] mac-hackathon listening on :3000 (production)
[ticket-cron] scheduled ticket sync ...
Then check:
https://hackathons.monashcoding.com/— the public site.https://hackathons.monashcoding.com/api/health→{"status":"ok","db":"ok"}.
- CI-gated (recommended): Dokploy service → Deployments → Webhook URL, copy it, and
add it as a GitHub repo secret
DOKPLOY_DEPLOY_WEBHOOK. Thendeploy.ymlredeploys after thetestworkflow passes on the default branch. (Also add aHUMANITIX_API_KEYrepo secret if you want the live Humanitix test to run in CI; it skips without it.) - Native: enable Auto Deploy on the Dokploy service (registers a GitHub webhook that
redeploys on every push, tests or not) and delete
.github/workflows/deploy.yml.
- The database volume
db_datais the only stateful thing. Back it up before major changes. Redeploys reuse it; migrations are additive. - Dead Humanitix key? The organiser dashboard's health banner shows
⚠️ FAILED, andGET /api/health/syncreports per-source last-success. Regenerate the key in Humanitix, updateHUMANITIX_API_KEYin Dokploy, redeploy. If a sweep would mass-revoke tickets it aborts and alerts instead — setFORCE_TICKET_SYNC=1only once you've confirmed the revocation is real, then unset it. - Next year's committee: they do not fork the repo or edit code. They create the new
event in the admin UI at
/admin, map its Humanitix event id and ticket types there, and (optionally) point the Notion databases at the new event. Everything else is data.