Docker for JavaScript Developers: A Practical Guide

Docker can seem intimidating at first. Let me break it down in terms that make sense for web developers.

Why Docker?

"It works on my machine" is the curse of our profession. Docker solves this by packaging your app with its entire environment.

The Basics

A Docker container is like a lightweight VM. It has:

  • Its own filesystem
  • Its own network
  • Isolated processes

But it shares the host kernel, so it's way lighter than a real VM.

Your First Dockerfile

For a Node.js app:

FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .

EXPOSE 3000
CMD ["node", "index.js"]

Build and run:

docker build -t my-app .
docker run -p 3000:3000 my-app

Docker Compose for Development

Most apps need multiple services. Docker Compose handles this:

# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - .:/app
      - /app/node_modules
    environment:
      - DATABASE_URL=postgres://postgres:password@db:5432/mydb
    depends_on:
      - db
  
  db:
    image: postgres:15
    environment:
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=mydb
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

Now `docker compose up` starts everything.

Hot Reloading

The volume mount (`- .:/app`) maps your local files into the container. Combined with nodemon:

CMD ["npx", "nodemon", "index.js"]

Changes reflect immediately without rebuilding.

Production vs Development

Use multi-stage builds for smaller production images:

# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Production stage
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
RUN npm ci --production
CMD ["node", "dist/index.js"]

Common Gotchas

1. **Don't run as root** - Add `USER node` after install steps 2. **Use .dockerignore** - Don't copy node_modules into the image 3. **Layer caching** - Copy package.json first, then code

Docker takes practice, but once it clicks, you'll wonder how you lived without it.

Back to Journal