Docker for Local Development

A practical guide to setting up reproducible development environments with Docker Compose.

"It works on my machine" is a phrase that should never be uttered in 2025. Docker Compose gives you reproducible development environments that match production, and setting it up is easier than you might think.

The Basic Setup

A typical web application needs an app server, a database, and maybe Redis for caching. Here's a minimal docker-compose.yml:

version: '3.8'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - .:/app
      - /app/node_modules
    depends_on:
      - db
      - redis
  
  db:
    image: postgres:15
    environment:
      POSTGRES_DB: myapp
      POSTGRES_PASSWORD: secret
    volumes:
      - pgdata:/var/lib/postgresql/data
  
  redis:
    image: redis:7-alpine

volumes:
  pgdata:

Hot Reloading

The key to good developer experience is the volume mount: - .:/app. This syncs your local files into the container, so changes reflect immediately without rebuilding.

The trick with - /app/node_modules prevents your local node_modules from overwriting the container's. Dependencies are installed inside the container where they belong.

Development vs Production

I use a multi-stage Dockerfile with a development target that includes dev dependencies and debugging tools:

FROM node:20 AS base
WORKDIR /app

FROM base AS development
COPY package*.json ./
RUN npm install
CMD ["npm", "run", "dev"]

FROM base AS production
COPY package*.json ./
RUN npm ci --only=production
COPY . .
CMD ["npm", "start"]

Common Pitfalls

Slow file syncing on macOS: Use the :cached or :delegated flags on volume mounts, or consider using Docker's new VirtioFS file sharing.

Permission issues: Run your container as a non-root user that matches your host UID. This prevents file ownership headaches.

Forgetting to persist data: Always use named volumes for databases. Anonymous volumes get deleted, and you'll lose your data.

The Payoff

New team member onboarding becomes: clone repo, run docker compose up. That's it. No installing specific versions of Node, Python, or databases. No "follow the wiki" that's always out of date.