Dhirendra.
Back to Blog
DockerDevOpsNode.js

Docker for JavaScript Developers: A No-Nonsense Guide

Everything you actually need to know about Docker as a JS dev — from writing your first Dockerfile to production multi-stage builds that keep images small.

September 22, 20243 min read434 words

Why Bother With Docker?

"It works on my machine" is a cliché because it's a real problem. Docker solves it by packaging your app and everything it needs — Node version, OS libs, environment — into a portable image that runs identically everywhere.

For JS developers specifically, Docker gives you: consistent dev environments across a team, easy local service dependencies (Postgres, Redis), and reproducible production builds on any cloud.

Your First Dockerfile

Start with a basic Node.js app:

FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .

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

A few choices explained: node:20-alpine uses the Alpine Linux base, which is ~5MB vs ~900MB for the full Debian image. npm ci instead of npm install is deterministic — it installs exactly what's in your lockfile.

The Multi-Stage Build (Production Pattern)

Single-stage builds include dev dependencies, build tools, and source files in the final image. Multi-stage builds fix this:

# ── Stage 1: Build ──────────────────────────
FROM node:20-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# ── Stage 2: Production ──────────────────────
FROM node:20-alpine AS runner

WORKDIR /app
ENV NODE_ENV=production

# Only copy what we need
COPY --from=builder /app/package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist

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

The final image contains zero build tools and zero dev dependencies. For a typical Next.js app this takes you from ~1.5GB down to ~200MB.

Docker Compose for Local Dev

Running your app with its dependencies (Postgres, Redis) locally:

# docker-compose.yml
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgres://postgres:password@db:5432/myapp
      REDIS_URL: redis://cache:6379
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: password
      POSTGRES_DB: myapp
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      retries: 5
    volumes:
      - postgres_data:/var/lib/postgresql/data

  cache:
    image: redis:7-alpine

volumes:
  postgres_data:

docker compose up starts everything. docker compose down -v tears it all down including data. Your team clones the repo, runs one command, and has a working environment.

.dockerignore Is Not Optional

Without a .dockerignore, COPY . . sends your entire project including node_modules (often 500MB+) to the Docker build context:

node_modules
.next
dist
.git
*.log
.env*

This alone can cut build times from minutes to seconds on first run.

The Patterns That Matter

For JS developers, three things deliver 80% of the value: multi-stage builds for small images, Compose for local dev parity, and npm ci over npm install for reproducibility. Everything else — Kubernetes, swarm, custom networks — you can learn when you need it. Start with these three.

// keep reading

Related Articles