Environment Management
Docker for Developers
Containerize your applications so they run identically everywhere — from your laptop to production.
What Docker Is
Docker is a tool that packages your application together with everything it needs to run — the runtime, dependencies, configuration, and system libraries — into a single portable unit called a container.
The promise: if it runs in a container on your laptop, it runs identically on any other machine with Docker installed. No more "it works on my machine."
Images vs Containers
Two concepts are essential to understand:
Image — A read-only blueprint. Think of it as a recipe. It defines what the container will contain: the base OS, the Node.js version, your application code, installed dependencies, configuration.
Container — A running instance of an image. Think of it as a meal made from the recipe. You can run multiple containers from the same image simultaneously.
Image → Container (running)
→ Container (running)
→ Container (stopped)Writing a Dockerfile
A Dockerfile is a text file that contains instructions to build an image:
# Start from the official Node.js 20 image
FROM node:20-alpine
# Set the working directory inside the container
WORKDIR /app
# Copy package files first (for better caching)
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy the rest of the application code
COPY . .
# Build the application
RUN npm run build
# Expose the port the app runs on
EXPOSE 3000
# The command to run when the container starts
CMD ["node", "server.js"]Multi-Stage Builds
For Next.js and other compiled applications, use multi-stage builds to keep the final image small:
# 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's needed to run (not dev dependencies)
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]The builder stage compiles the app; the runner stage only contains what's needed to run it. Result: a much smaller production image.
Essential Docker Commands
# Build an image from the Dockerfile in the current directory
docker build -t myapp:latest .
# Run a container, mapping port 3000 on host to port 3000 in container
docker run -p 3000:3000 myapp:latest
# Run with environment variables
docker run -p 3000:3000 -e DATABASE_URL=... myapp:latest
# Run in the background (detached mode)
docker run -d -p 3000:3000 myapp:latest
# List running containers
docker ps
# View container logs
docker logs container-id
# Execute a command inside a running container
docker exec -it container-id sh
# Stop a container
docker stop container-idWhen to Use Docker
Docker shines when:
- Your team has inconsistent local environments (different OS, Node versions)
- Your app has complex dependencies (databases, caches, background workers)
- You need production-like behavior during development
- Your deployment target is a container registry (AWS ECS, Google Cloud Run, Kubernetes)
When Docker may be overkill:
- Simple static sites deployed to Vercel or Netlify
- Early prototyping where speed of iteration matters more than environment parity
- Solo projects where you control the entire stack
Key Takeaways
- Docker packages your app and its dependencies into a portable container that runs identically everywhere
- Images are blueprints; containers are running instances
- Multi-stage Dockerfiles keep production images small by separating the build environment from the runtime environment
- Use volume mounts (-v) for development to get hot-reloading without rebuilding the image
- Docker is not always necessary — Vercel/Netlify handle deployment for most frontend projects
Example
# Multi-stage Dockerfile for Next.js
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
EXPOSE 3000
CMD ["node", "server.js"]