Deploy an app
For @arki/[email protected] · source docs/recipes/deploy.md
To ship an app made by dot new, build its container image, give it a
database URL, and check /api/health. This recipe assumes the default
stack template, which ships a Dockerfile and the health route.
docker build ──▶ image (server bundle + drizzle/ migrations)
│
docker run ────────▶ boot: env check → migrations → plugins → start
│
curl /api/health ──▶ {"status":"ok"} (app started, Postgres answers)Steps
-
Check the wiring without side effects.
dot explainonly runsconfigure, so it touches no database:npx dot explain --json | jq -e '.ok' -
Run the tests. The template's tests boot the app on in-memory PGlite, so they need no Docker:
bun run test -
Build the image. The
Dockerfilehas three stages: install from the lockfile, build the server bundle, then copy only.output/anddrizzle/into a small runner image:docker build -t my-app . -
Run it with a database URL. The other variables are listed in
.env.example:docker run -p 3000:3000 -e DB_URL=postgres://app:[email protected]:5432/app my-appOn boot, the
dbMigrationsplugin applies the migrations indrizzle/to that database.SIGTERM(whatdocker stopsends) drains the app:stophooks run, thendisposehooks, in reverse order.
Why it works this way
- Migrations ship inside the image. Code and schema travel together, so a container never runs against a schema it does not know. The cost: the first boot of a new version changes the database. Keep migrations backward compatible if you want to roll back by redeploying the old image.
- Every stage uses the musl Bun image. A Bun binary copied from a glibc image into Alpine crashes on start. That bug is boring to find twice.
- The health route checks Postgres too. It starts the app if needed
and runs
select 1. It answers503when either fails, so the platform does not route traffic to a container that cannot serve it.
Check it
With the container running, this command prints the health answer:
curl -fsS http://localhost:3000/api/healthExpected output:
{"status":"ok"}