Production uses a persistent checkout at /opt/docker/service-auth on the OptiPlex server. Both workflows run on the dedicated Auth self-hosted runner and update that checkout from origin/main before doing any work.
The shared PostgreSQL cluster, Auth database, restricted login role, and connection secret are owned by pior-labs/platform-deploy. This repository owns the Auth schema migration and seed data.
Both jobs target the GitHub PRODUCTION environment. Production secrets and variables may be configured there; repository-level values also remain available.
Run the platform-deploy workflow before the first Auth bootstrap. It must create:
pior_data
/opt/docker/pior-labs/secrets/service-auth/database-url
The generated connection string targets database auth with the restricted role auth_app. It remains on the server and is mounted into the API container as a Compose secret.
The production application environment. It must include, at minimum:
API_PORT=3000
NODE_ENV=production
BETTER_AUTH_SECRET=<long-random-secret>
BETTER_AUTH_URL=https://auth.szarans.ca
WEB_ORIGIN=https://auth.szarans.ca
VITE_AUTH_BASE_URL=
FINLENS_CLIENT_SECRET=<shared-finlens-client-secret>
SEED_USER_1_EMAIL=<email>
SEED_USER_1_NAME=<name>
SEED_USER_1_PASSWORD=<password>
SEED_USER_2_EMAIL=<email>
SEED_USER_2_NAME=<name>
SEED_USER_2_PASSWORD=<password>Do not store DATABASE_URL, a database password, or POSTGRES_ADMIN_URL in GitHub. The workflows remove legacy database entries from the rendered .env before deployment.
Leaving VITE_AUTH_BASE_URL blank makes the SPA use whichever Auth origin served it. The canonical OAuth issuer is https://auth.szarans.ca/api/auth.
The seeded FinLens client accepts this production callback URL:
https://finance.szarans.ca/api/auth/oauth2/callback/auth-pior
The exact FINLENS_CLIENT_SECRET value must also be configured in the Finance production environment.
| Variable | Default | Purpose |
|---|---|---|
AUTH_DATABASE_URL_FILE |
/opt/docker/pior-labs/secrets/service-auth/database-url |
Host path to the platform-generated Auth connection secret |
DATA_NETWORK |
pior_data |
External Docker network shared with PostgreSQL |
The workflow writes these values and the production Compose override into /opt/docker/service-auth/.env so subsequent server-side Compose commands use the same configuration.
docker-compose.yml remains usable with a direct DATABASE_URL for local development. Production adds docker-compose.production.yml, which:
- clears any direct
DATABASE_URL - mounts the platform-generated URL as
/run/secrets/auth_database_url - sets
DATABASE_URL_FILEinside the API container - attaches the API to
pior_data
The API accepts either DATABASE_URL or DATABASE_URL_FILE; direct values remain useful locally, while production uses the mounted file.
Auth has one production application identity: auth.szarans.ca. That hostname is used for the Better Auth base URL, OAuth/OIDC issuer, browser origin, client discovery, and documentation.
Routing is selected below the application layer:
- Tailscale-connected clients use platform split DNS and resolve
auth.szarans.cadirectly to the OptiPlex Tailscale address. - Other clients resolve the same hostname through public DNS.
- Containers on
pior_edgeresolveauth.szarans.cato the platform Caddy container through Docker DNS.
Applications must not use a separate Tailscale-specific hostname for OAuth or OIDC identity.
Run this workflow once before the first deployment, and again when seed users or trusted OAuth client configuration must be reconciled.
The workflow:
- requires the dispatch confirmation
bootstrap-auth - pulls
origin/maininto/opt/docker/service-auth - writes the non-database application environment from
AUTH_ENV - validates
pior_dataandpior_edge - validates that the platform-generated database secret can be mounted
- builds the API image
- applies the checked-in migration
- seeds the two users and trusted OAuth clients
Database and role provisioning no longer occurs here. The migration is idempotent for the current schema, and the seed expects exactly two credential users.
Run this workflow after bootstrap and after merging production-ready changes.
The workflow:
- pulls
origin/maininto/opt/docker/service-auth - writes the non-database application environment from
AUTH_ENV - validates the external networks and database secret mount
- builds the API and web images
- applies database migrations
- recreates the Auth services
- waits for the API health check inside the container
- verifies
/healthand/sign-inthrough production Caddy athttps://auth.szarans.ca
Deployment remains manual through workflow_dispatch.
The runner executing these workflows must:
- be available to the
service-authrepository - have the labels
self-hosted,linux, andservice-auth - authenticate the existing checkout's Git remote as the runner service account
- have write access to
/opt/docker/service-auth - be able to run Docker without
sudo - have
curlinstalled
The runner user does not need the database connection string in GitHub. Docker mounts the server-side secret into the API and one-off migration containers.