Deploying a nest-api
Docker image
Section titled “Docker image”Two stages on the house Node image (aurostack.dev/wesnetech/nodejs:24):
- Build:
corepack enable,yarn install --immutablefrom the committedyarn.lock,yarn db:generate,yarn build, then drop dev dependencies. - Runtime:
node_modules,dist,publicandprisma, started withnode dist/src/main, exposing the port.
The image has no .env (excluded on purpose): configure it with real
environment variables. Logs are JSON on stdout.
Migrations
Section titled “Migrations”The runtime image doesn’t include the Prisma CLI (a dev dependency), so run migrations from somewhere that does, before the new version starts:
- a CI step or a one-off job using the build stage
(
docker build --target build), runningyarn db:migrate:prod; or - a machine with the repository and the production
DATABASE_URL.
Never db:push against production: it applies the schema without a migration
history.
CI workflows
Section titled “CI workflows”| Workflow | When | Does |
|---|---|---|
test.yml |
Push to any branch except main and development |
Install, pull .env.test from Infisical, unit tests, build |
test-e2e.yml |
Pull request into development |
PostgreSQL and Redis service containers, .env.test from Infisical rewritten to point at them, schema push, seed, e2e tests |
build-and-push.yml |
Push to main |
Build and push the image, tagged latest and <yy_mm_dd>_<random> |
Secrets and variables to set on the repository:
| Name | For |
|---|---|
INFISICAL_TOKEN |
Machine identity token for the Infisical CLI |
INFISICAL_PROJECT_ID |
The project’s Infisical id. Not in the workflows by design: add it to the runner environment or the workflow env |
REGISTRY_URL, REGISTRY, DK_USER, DK_ACCESS_TOKEN |
Registry login and image prefix |
INFISICAL_API_URL in the workflows points at the Infisical instance; change it
if yours is elsewhere.
Production checklist
Section titled “Production checklist”APP_ENV=production, a strongBETTER_AUTH_SECRET, newBASIC_AUTH_*.SERVER_HOSTandBETTER_AUTH_URL: the API’s public URL. The/healthHTTP check callsSERVER_HOSTfrom inside the container, so it must resolve there.FRONTEND_HOST, plusMISC_CORS_ORIGINSfor any other front-end origins: together they’re what auth trusts and CORS allows.BETTER_AUTH_COOKIE_DOMAIN: empty for a single front-end origin, or your apex with a leading dot (.example.com) to share one login across subdomains.RATE_LIMIT_IP_HEADERS: the header your reverse proxy sets. Behind a proxy without it, every client shares the proxy’s rate-limit bucket; exposed directly with it, clients can spoof it.S3_ENDPOINTset even on AWS (e.g.https://s3.us-east-1.amazonaws.com): it’s also the prefix of stored file URLs, which are relative when it’s empty.HEALTH_*sized to the container’s real memory and disk.- Migrations applied before the new version starts.
- WebSocket upgrades forwarded on
/realtime, with realtime on. - Telemetry and Temporal settings from their service pages.