Docker Module
Node.js 24 containers with Docker Compose v2
The Docker option adds a Dockerfile, docker-compose.yml, and .dockerignore. You need Docker Desktop or Docker Engine with Compose v2 running.
Build and run
npm run docker:build
npm run docker:up
npm run docker:logs
npm run docker:downThese scripts call docker build and docker compose.
Every image uses Node.js 24 Alpine, a separate build stage, and a non-root user at runtime. What runs in the container depends on the framework:
- Express runs
dist/index.js. - Next.js runs the standalone
server.jsoutput. - TanStack Start runs the Nitro server in
.output/server/index.mjs. Nitro bundles the dependencies it needs into.output/, so the image has nonode_modules.
If the build fails, the image build stops with an error.
Package manager
The image installs dependencies with the package manager you picked:
| Package manager | Installed in the image with | With a committed lockfile |
|---|---|---|
| npm | Node.js itself | npm ci (package-lock.json) |
| pnpm | npm install -g pnpm@11 | pnpm install --frozen-lockfile |
| bun | the bun binary from oven/bun:1-alpine | bun install --frozen-lockfile |
Without a lockfile the image runs a plain install, so commit your lockfile to get the same versions in every build. The app itself always runs on Node.js.
Health check
The image has a HEALTHCHECK that requests /health (Express and NestJS) or / (Next.js and TanStack Start) every 30 seconds. docker ps shows the container as healthy once the app answers.
Prisma
The Prisma client is generated during the image build and copied into the final image. The build uses placeholder database URLs, so it never connects to a database or runs migrations. Set the real DATABASE_URL at runtime and run migrations separately.
Environment and databases
Compose loads your .env file through env_file and sets NODE_ENV=production.
With a database, Compose also runs it as a db service, and the app waits until the database reports healthy:
- PostgreSQL (Prisma and Drizzle):
postgres:17-alpinewith user, password, and databasepostgres - MongoDB (Mongoose):
mongo:8, run as a one-node replica set because Better Auth needs transactions
Data is kept in the db-data volume. The database port (5432 or 27017) is published, so migrations and npm run dev on your machine use the same database through the localhost URL in .env. Stop a local PostgreSQL or MongoDB first if it already uses that port.
Inside a container, localhost means the container itself, so Compose sets DATABASE_URL or MONGODB_URI for the app to the db service. To use an external database instead, remove the db service and the override, and set the URL in .env.
Typical first run:
docker compose up -d db # start the database
npm run prisma:migrate # or db:migrate for Drizzle, from your machine
npm run docker:up # build and start the appEach project gets its own random auth secret in .env. For production, set your own secrets and set BETTER_AUTH_URL to the deployed address.
.env is excluded from the build context. If your Next.js code needs public variables at build time, pass them to the build explicitly. Never put production secrets in the Dockerfile.