Repo Layout Basics
This section shows the concrete file layout for containerizing services: one Dockerfile per deployable, co-located with its code, plus the supporting files that make builds reproducible.
Search across all documentation pages
This section shows the concrete file layout for containerizing services: one Dockerfile per deployable, co-located with its code, plus the supporting files that make builds reproducible.
The examples move from a single service up to a small multi-service repo you can build and run.
docker compose, not the legacy docker-compose.git. On Debian/Ubuntu the official packages install both the engine and the Compose plugin.# Debian/Ubuntu - install Engine + Compose plugin from Docker's repo
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin
docker version && docker compose versionEach service that ships as its own image gets its own Dockerfile, next to the code it builds.
services/
api/
Dockerfile
src/
worker/
Dockerfile
src/Dockerfile, one image.api/ owns its build.Dockerfile; shared behavior belongs in a base image instead.Keep the Dockerfile beside src/, not in a top-level docker/ dump.
docker build -t api:dev services/apiCOPY src ./src resolves relative to that context root.A .dockerignore at the context root prunes files before they are sent to the build.
.git
node_modules
**/*.env
dist
coverage.env and history like .git out of image layers.Dockerfile by default.Multi-stage builds keep toolchains out of the runtime image.
FROM node:22-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:22-slim
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
USER node
CMD ["node", "dist/server.js"]build stage carries dev dependencies; the final stage does not.COPY --from=build moves only the artifacts you need.USER node drops root before the process starts.Pin by tag at minimum, by digest for reproducibility.
FROM node:22-slim@sha256:2b9f...c31alatest drift and break reproducibility.Read configuration from the environment, not from files baked into the image.
ENV NODE_ENV=production
EXPOSE 8080EXPOSE documents the port; it does not publish it.Compose v2 wires a service and its dependencies for the inner loop.
services:
api:
build: ./services/api
ports:
- "8080:8080"
depends_on:
- db
db:
image: postgres:17
environment:
POSTGRES_PASSWORD: devbuild: points at the service context, reusing the same Dockerfile.depends_on orders startup for local development.Group deployables and shared code so build contexts stay predictable.
repo/
services/
api/Dockerfile
worker/Dockerfile
packages/
shared/
.dockerignore
compose.yaml
Makefileservices/, shared libraries under packages/..dockerignore governs root-context builds.Makefile (or just) standardizes build, scan, and push.Wrap the build so every developer and CI job runs the same command.
IMAGE ?= registry.internal/api
TAG ?= $(shell git rev-parse --short HEAD)
build:
docker build -f services/api/Dockerfile -t $(IMAGE):$(TAG) ..) so shared packages are reachable.make build, so behavior does not drift.Mount source into a running container to iterate without rebuilding.
services:
api:
build:
context: ./services/api
target: build
command: npm run dev
volumes:
- ./services/api/src:/app/srctarget: build stops at the dev stage that has the toolchain.Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).
Reviewed by Chris St. John·Last updated Jul 19, 2026