A small Gleam HTTP server in signup/ exposes POST /signup that validates
a shared INVITE_CODE env var, then PUTs a user document to CouchDB's
_users database using the existing admin credentials. The frontend gets
back 201 + the username and is expected to call /_session itself to
obtain the AuthSession cookie (chosen over server-side cookie forwarding
because vaquant.at and couch.apoena.dev are different sites and the
cross-site cookie path is fragile under modern browser privacy modes).
Wired into docker-compose as a second service, reachable on
signup-couch.apoena.dev with its own Traefik labels and the same
hardcoded UUID network attachment as couchdb. Reuses COUCHDB_USER /
COUCHDB_PASSWORD for the admin call. INVITE_CODE must be set in Coolify
env vars.
PouchDB-style apps call fetch with credentials: 'include' to maintain
the CouchDB _session cookie. Without Access-Control-Allow-Credentials:
true in the preflight response, the browser silently blocks the
mutating request (Firefox HAR shows status 0, _securityState insecure).
Switching cors/credentials to true forces enumerating allowed origins
(wildcard + credentials is forbidden by the CORS spec). Listed are the
common Vite/Next/etc. local dev ports plus the vaquant.at frontend.
Coolify is preventing variable interpolation in compose labels — the
container's labels were stored literally as Host(\`\${SERVICE_FQDN_COUCHDB_5984}\`)
etc., so Traefik matched no router and fell back to default_redirect_503.
Variable substitution works in environment: (CouchDB env was correct),
just not in labels: — likely Coolify sets labels via the Docker API
after compose runs, bypassing compose interpolation. Hardcoding the host
and the per-resource network name is the only thing left that works.
The previous diagnostic showed Coolify does not auto-inject Traefik
labels just because SERVICE_FQDN_<NAME>_<PORT> is set — the container
had zero traefik.* labels after deploy, which is why the host returned
503. Adding explicit router+service labels and binding the docker
provider to the per-resource UUID network gives Traefik a definite
backend. Router/service names use the coolcouch prefix so they cannot
clash with any Coolify-managed couchdb router on the same install.
Traefik in this Coolify install resolves backends on a per-resource
network named after the resource UUID, not the shared "coolify" network.
COOLIFY_RESOURCE_UUID is injected into the compose env by Coolify, so
naming the external network from it lets Traefik find couchdb.
The container was only on the project's default network, so Coolify's
Traefik (on the shared coolify network) had no route to it — Traefik
matched the router but reported "no available server". Putting couchdb
on both networks lets Traefik reach 5984 while leaving the init sidecar
talking to it over the default network as before.
When SERVICE_FQDN_<NAME>_<PORT> is set, Coolify auto-injects its own
Traefik router and service labels on the couchdb container. Our manual
labels reused the same router/service name (couchdb), so the two
definitions collided and Traefik ended up with a router whose backing
service had no servers — Traefik returned "no available server" for
couch.apoena.dev. Dropping the manual labels lets Coolify own routing
end-to-end.
The couchdb:3 image purges curl after the build, so the curl-based
healthcheck could never succeed. The cors.ini bind-mount also looks
like the cause of the ~600ms container exit observed in the first two
Coolify deploys. Both pieces are removed; CORS is now applied by the
init sidecar via PUTs against /_node/_local/_config/..., which is
idempotent on redeploy.