--- title: Docker description: Building and running kreogen applications as containers. type: guide --- # Docker Each app builds into a standalone image from a single Dockerfile, parameterised by which app you want. ```sh docker build --build-arg APP=app -t my-project/app . docker build --build-arg APP=web -t my-project/web . docker build --build-arg APP=api -t my-project/api . ``` Or all three in parallel with a shared cache: ```sh bun run docker:build # docker buildx bake ``` ## How the build is staged **pruner** — `turbo prune --docker` splits the workspace into manifests (`out/json`) and sources (`out/full`). Installing from the manifests alone means the dependency layer is only invalidated by a `package.json` or lockfile change, not by every source edit. That is the difference between a thirty-second rebuild and a five-minute one. **deps** — `bun install --frozen-lockfile`, with a BuildKit cache mount so the package cache survives between builds. **builder** — `turbo run build --filter=$APP`, then an assertion that the standalone output actually exists. Failing here is much easier to diagnose than a container that starts and immediately exits. **runner** — `node:22-slim`, non-root, with `tini` as PID 1 so `SIGTERM` reaches Next and shutdown is graceful. ## Standalone output `@kreogen/next-config` sets both `output: "standalone"` and `outputFileTracingRoot`. The second is not optional in a monorepo. Without it Next infers the tracing root from the nearest lockfile, and if that resolves to the app rather than the workspace root, the standalone bundle lands at a different path than the Dockerfile expects. It also sets `outputFileTracingIncludes` for the generated Prisma client, which lives at a custom output path that Next's tracing does not follow on its own. ## Public environment variables Next inlines `NEXT_PUBLIC_*` into the client bundle at build time. Left alone, that makes every image environment-specific and breaks promoting the exact artifact that passed CI. kreogen bakes in placeholders and substitutes them when the container starts, so one image serves staging and production: ```sh docker run \ -e NEXT_PUBLIC_APP_URL=https://app.example.com \ -e NEXT_PUBLIC_WEB_URL=https://example.com \ -e DATABASE_URL=... \ -e BETTER_AUTH_SECRET=... \ -p 3000:3000 \ my-project/app ``` The URL-valued placeholders are themselves valid URLs on the reserved `.invalid` TLD, because static generation parses them — `sitemap.ts` and `robots.ts` construct a `URL` at module scope, and a bare token fails the build there. Adding a new public variable means adding it to `docker/public-env.json` and running `bun run generate:public-env`. The Dockerfile's `ENV` block and `turbo.json`'s build `env` list are generated from that manifest, and the entrypoint reads it directly at container start. The three used to be maintained by hand, each with a comment asking whoever edited one to remember the others; the drift was silent and only observable in something already running — a variable baked in but not substituted serves a `.invalid` hostname to users, and one missing from `turbo.json` is stripped by `envMode: "strict"` and reads `undefined` in the browser only. ## Running the full stack locally ```sh docker compose -f docker-compose.yml -f docker-compose.apps.yml up --build ``` Every container listens on 3000 internally; the host port encodes the app, so the URLs match `bun run dev`. Migrations run as a one-shot: ```sh docker compose --profile tools run --rm migrate ``` ## Health checks Every app serves `/health`, and the image declares a `HEALTHCHECK` against it. It is a liveness probe — it answers "is the process serving?" and deliberately does not touch the database, so a slow query cannot cause a restart loop. `/health` is exempt from the auth middleware and from locale rewriting. Both exemptions are load-bearing: without them the check would be redirected to sign-in, or rewritten into a locale segment and 404. ## Ports Containers always listen on 3000. Map whatever you need at the host or ingress; `PORT` is honoured if you must change it. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt)