Skip to content
DOCKER

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.

January 22, 2026 8 min read

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.

dockerfile
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.

text
node_modules
npm-debug.log
.git
.env
.env.*
Dockerfile
docker-compose.yml
coverage
dist

Use 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.

yaml
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.

bash
docker build -t registry.example.com/app:$GIT_SHA .
docker push registry.example.com/app:$GIT_SHA

Habits 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.

#Docker#DevOps#Docker Compose#CI/CD#Containers