A Practical Docker Setup for Small Teams
A lightweight, opinionated Docker workflow that makes local development and production match — multi-stage builds, Compose for local parity, and image tips — without over-engineering anything.
On this page
Docker earns its place the moment local development and production stop drifting apart. For a small team, the goal is not a sprawling platform with a dozen moving parts — it is a predictable setup that a new teammate can understand in an afternoon and trust in production.
This is the pragmatic version: one well-structured image, Compose for local parity, and a handful of habits that keep things fast and reproducible.
Start with a clean, multi-stage Dockerfile
A multi-stage build separates the tooling needed to build your app from the runtime that actually ships. The result is a smaller final image with a reduced attack surface. Copying package files before the rest of the source lets Docker cache the dependency layer, so day-to-day rebuilds stay quick.
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/package*.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/server.js"]Keep images small with .dockerignore
A good .dockerignore keeps local noise out of the build context, which speeds up builds and avoids accidentally baking secrets or huge folders into an image.
node_modules
npm-debug.log
.git
.env
.env.*
Dockerfile
docker-compose.yml
coverage
distUse Compose for local parity
A single Compose file lets any teammate bring up the whole stack — app plus dependencies like a database — with one command. This removes an entire category of “works on my machine” problems, because everyone runs the same versions the same way.
services:
api:
build: .
ports:
- "3000:3000"
env_file: .env
depends_on:
- db
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: local
volumes:
- dbdata:/var/lib/postgresql/data
volumes:
dbdata:Build once, promote everywhere
Images should be built in CI and tagged, then the same image should move through environments. Never build a production image by hand on a laptop — it is the fastest way to introduce differences you cannot reproduce.
docker build -t registry.example.com/app:$GIT_SHA .
docker push registry.example.com/app:$GIT_SHAHabits that keep it maintainable
- Pin base image versions (node:20-alpine, not node:latest) for reproducible builds.
- Run the container as a non-root user in production images.
- Add a lightweight healthcheck so orchestrators know when the app is ready.
- Keep one responsibility per container — resist bundling unrelated services together.
Why boring wins
A setup the whole team can reason about beats a clever one that only one person understands. Multi-stage builds, a clear Compose file and disciplined tagging cover the vast majority of real needs, and they scale gracefully when the team grows or moves to an orchestrator like Kubernetes.