TechLead
Lesson 3 of 18
5 min read
Docker & DevOps

Dockerfile Fundamentals

Master writing Dockerfiles to build custom images with best practices for layers and caching

What is a Dockerfile?

A Dockerfile is a text file containing a set of instructions that Docker uses to build an image. Each instruction creates a new layer in the image, and Docker caches these layers to speed up subsequent builds.

Basic Dockerfile Instructions

FROM — Set Base Image

# Every Dockerfile starts with FROM
FROM node:20-alpine

# Multi-stage build (more on this later)
FROM node:20-alpine AS builder

WORKDIR — Set Working Directory

# Set the working directory inside the container
WORKDIR /app

# All subsequent commands run relative to /app

COPY and ADD — Copy Files

# Copy files from host to container
COPY package.json package-lock.json ./
COPY src/ ./src/

# Copy everything (use .dockerignore to exclude files)
COPY . .

# ADD can also extract tar archives and fetch URLs
ADD archive.tar.gz /app/
# Prefer COPY over ADD for simple file copying

RUN — Execute Commands

# Install dependencies
RUN npm ci --production

# Run multiple commands (creates one layer)
RUN apt-get update && \
    apt-get install -y curl && \
    rm -rf /var/lib/apt/lists/*

ENV — Set Environment Variables

# Set environment variables
ENV NODE_ENV=production
ENV PORT=3000

# Use variables in subsequent instructions
RUN echo "Running in $NODE_ENV mode"

EXPOSE — Declare Ports

# Document which ports the container listens on
EXPOSE 3000
EXPOSE 80 443

# This is documentation only — you still need -p when running

CMD and ENTRYPOINT — Define Startup Command

# CMD — default command (can be overridden)
CMD ["node", "server.js"]
CMD ["npm", "start"]

# ENTRYPOINT — always runs (CMD becomes arguments)
ENTRYPOINT ["node"]
CMD ["server.js"]
# Runs: node server.js

# Shell form vs Exec form
CMD node server.js          # Shell form (runs in /bin/sh -c)
CMD ["node", "server.js"]   # Exec form (preferred)

Complete Example: Node.js App

# Use official Node.js Alpine image
FROM node:20-alpine

# Set working directory
WORKDIR /app

# Copy dependency files first (better caching)
COPY package.json package-lock.json ./

# Install dependencies
RUN npm ci --production

# Copy application code
COPY . .

# Set environment variables
ENV NODE_ENV=production
ENV PORT=3000

# Expose the port
EXPOSE 3000

# Start the application
CMD ["node", "server.js"]

The .dockerignore File

Like .gitignore, the .dockerignore file prevents unnecessary files from being sent to the Docker daemon during builds.

# .dockerignore
node_modules
npm-debug.log
.git
.gitignore
.env
.env.local
Dockerfile
docker-compose.yml
README.md
.DS_Store
coverage
.next

Building Images

# Build an image from the current directory
docker build -t myapp:latest .

# Build with a specific Dockerfile
docker build -f Dockerfile.prod -t myapp:prod .

# Build with build arguments
docker build --build-arg NODE_ENV=production -t myapp .

# Build without cache
docker build --no-cache -t myapp .

Layer Caching Tips

  • ✅ Copy package.json before source code for better caching
  • ✅ Combine related RUN commands to reduce layers
  • ✅ Order instructions from least to most frequently changing
  • ✅ Use .dockerignore to exclude unnecessary files

Continue Learning