Docker Images and Dockerfiles
Pull images from registries, write Dockerfiles, build your own images, and understand layer caching.
Image Basics
An image is a read-only stack of layers. Each layer represents a filesystem change from a Dockerfile instruction. Layers are cached and shared across images, so if two images use the same base (node:18-alpine), they share those layers on disk.
# Pull an image from Docker Hub
docker pull node:18-alpine
# List all local images
docker images
# Show image history (each layer)
docker history node:18-alpine
# Remove an image
docker rmi node:18-alpine
# Remove all unused images
docker image prune -a
Writing a Dockerfile
A Dockerfile is a plain text file named exactly Dockerfile. Each instruction creates a layer.
# Base image — always start here
FROM node:18-alpine
# Set working directory inside the container filesystem
WORKDIR /app
# Copy dependency manifests first (cache optimization)
COPY package*.json ./
# Install dependencies (this layer is cached until package.json changes)
RUN npm ci --only=production
# Copy application source code
COPY . .
# Document which port the app listens on
EXPOSE 3000
# Drop root privileges
USER node
# Default command when container starts
CMD ["node", "src/index.js"]
Key Instructions
| Instruction | Purpose |
|---|---|
FROM | Base image — every Dockerfile starts here |
WORKDIR | Sets the working directory (creates it if absent) |
COPY | Copy files from host into image |
RUN | Execute a command during build |
ENV | Set environment variables |
EXPOSE | Document a port (does not publish it) |
USER | Switch to a non-root user |
CMD | Default command when container starts |
ENTRYPOINT | Executable that always runs |
ARG | Build-time variable (not available at runtime) |
Building Images
# Build from Dockerfile in current directory, tag as myapp:1.0
docker build -t myapp:1.0 .
# Build with a different Dockerfile path
docker build -f docker/Dockerfile.prod -t myapp:prod .
# Pass a build argument
docker build --build-arg NODE_ENV=production -t myapp:prod .
# Build without using cache
docker build --no-cache -t myapp:fresh .
Layer Caching
Docker caches each layer. When a layer changes, all subsequent layers are invalidated and rebuilt. This is why dependency installation should come before copying source code:
# SLOW — changes to any source file invalidates npm install
COPY . .
RUN npm ci
# FAST — npm install only re-runs when package.json changes
COPY package*.json ./
RUN npm ci
COPY . .
.dockerignore
Like .gitignore — prevents files from being sent to the Docker build context:
node_modules
.git
*.log
.env
dist
coverage
Always include node_modules — you want Docker to install fresh dependencies, not copy your local ones.
Tagging and Pushing
# Tag an existing image
docker tag myapp:1.0 myusername/myapp:1.0
# Push to Docker Hub (requires docker login)
docker login
docker push myusername/myapp:1.0
# Pull it on another machine
docker pull myusername/myapp:1.0
Inspecting Images
# Detailed image metadata as JSON
docker inspect myapp:1.0
# Check image size and layers
docker history myapp:1.0 --no-trunc
# Run a shell inside to explore the image filesystem
docker run --rm -it myapp:1.0 sh