Magnemite
Deployment

On a Coolify setup

Two domains, generated secrets, and a Caddy that stays in front of the hub.

docker-compose.coolify.yml lets Coolify's proxy terminate TLS and keeps a small Caddy of our own (edge) in front of the hub. It uses two domains rather than path routing, because the agents and the dashboard are separate audiences.

Coolify variablePoints at
SERVICE_FQDN_WEBthe dashboard you log into
SERVICE_FQDN_EDGEwhat the boxes talk to — use a subdomain like agents.<your-domain>

Deploying

Create the resource

New Resource → Docker Compose, pointed at docker-compose.coolify.yml.

Set both domains

In the Domains tab, give web your dashboard domain and edge the agents' subdomain. Give the edge domain the :8080 suffix so the proxy hits Caddy's port:

https://agents.magnemite.example.com:8080

Deploy, then seed once

docker ps --format '{{.Names}}' | grep hub
docker exec -it <hub-container> pnpm --filter @magnemite/db run seed

Read the generated admin password

Coolify generates the Postgres password, the internal secret, AUTH_SECRET and the admin password itself, and shows them in the Environment tab. Read SERVICE_PASSWORD_ADMIN from there to sign in the first time; change it from Settings → Accounts afterwards if you prefer one you can remember.

The admin login

The login defaults to [email protected]. Nothing is ever sent to it — the address is an identifier, not a mailbox — so it only needs changing if you want your own.

Set ADMIN_EMAIL before the first seed if you want a different one. Changing it later seeds a second admin rather than renaming the first, because the seed upserts on the address.

There is no .env file inside the container, so the seed reads ADMIN_EMAIL and ADMIN_PASSWORD from the hub service's environment. That is why they are set on hub and not only on web.

Things this file does differently

Bare SERVICE_FQDN_* declarations

edge and web declare - SERVICE_FQDN_EDGE and - SERVICE_FQDN_WEB with no value. That is deliberate: Coolify fills them from the Domains tab, and writing ${SERVICE_FQDN_EDGE} would make Compose interpolate a variable that does not exist yet and blank it out.

The auth URL fallback

BETTER_AUTH_URL is set from ${SERVICE_URL_WEB}, which Coolify does not always have populated by the time Compose interpolates — in which case the reference collapses to an empty string. Better Auth without a base URL derives the origin from the incoming Host header, which breaks callbacks behind a proxy. The app therefore falls back to the bare SERVICE_FQDN_WEB that Coolify injects, so keep that line even if you set the two explicitly. A bare host with no https:// is accepted.

Why there is still a Caddy

Coolify's proxy (Traefik) routes; it does not serve files. It has no file_server equivalent, and the artifacts volume belongs to this stack, not to the Coolify-managed proxy container — so pointing the boxes' domain straight at the hub means every one of those ~170 MB bundles is read and pushed by Node.

The edge service closes that gap. It mounts artifacts read-only, answers /files/* with file_server (native Range, so an interrupted download resumes) behind a forward_auth call to the hub's /internal/authz, and proxies only /ws/device, /api/enroll and /healthz through. Everything else on that domain is a 404, which keeps the hub's /internal/* API unreachable from outside — the same guarantee the Caddy setup gives.

Upgrading from an earlier version of this file

The boxes' domain moved from SERVICE_FQDN_HUB_3001 to SERVICE_FQDN_EDGE. Keep the same domain value and nothing needs re-flashing: a box only ever knows the URL.

Next

On this page