Docker fundamentals: images, containers, and layers

Ryan Nakamura Feb 2026
2 tabs
# Multi-stage build for a Node.js application

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

WORKDIR /app

# Copy package files first (leverage layer caching)
COPY package.json package-lock.json ./
RUN npm ci --only=production

# Copy source code (changes more frequently)
COPY src/ ./src/
COPY tsconfig.json ./

RUN npm run build

# Stage 2: Production
FROM node:20-alpine AS production

# Create non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

WORKDIR /app

# Copy only production artifacts
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./

# Set ownership
RUN chown -R appuser:appgroup /app

USER appuser

EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3   CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

CMD ["node", "dist/server.js"]
2 files · dockerfile, bash Explain with highlit

Docker packages applications into lightweight, portable containers. A Dockerfile defines build instructions—each instruction creates an immutable layer. The FROM directive sets the base image. COPY and ADD bring files into the image. RUN executes commands during build. CMD and ENTRYPOINT define the container's startup command. The docker build command creates images from Dockerfiles. docker run starts containers from images. Layer caching dramatically speeds up builds—order instructions from least to most frequently changing. Multi-stage builds reduce final image size by separating build and runtime dependencies. Use .dockerignore to exclude unnecessary files. Tag images with semantic versions, never rely solely on latest. Understanding the image layer system is key to writing efficient Dockerfiles.