In the age of microservices and CI/CD, the size of your Docker images is critically important. It's not just a matter of disk space, but above all deployment speed, security, and data transfer costs.
layers Why Size Matters
Heavy images (often exceeding 1GB for a simple application) are every DevOps engineer's nightmare. Every extra megabyte means longer docker pull times, a larger attack surface (additional system packages you don't need), and slower scaling during emergencies.
1. Multi-stage Builds: The Holy Grail of Optimisation
This is the most important technique you can apply. It allows you to use one image to build your application (with all the tools like compilers, git, npm), then copy only the resulting binary to the final, minimalist image.
Example: Before optimisation
FROM node:18
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
CMD ["npm", "start"]
Example: After optimisation (Multi-stage)
# Stage 1: Build
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Stage 2: Production
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
RUN npm install --production
CMD ["node", "dist/main.js"]
2. Choosing a Base Image: Alpine vs Slim
Instead of the standard ubuntu or node image, use the alpine version. Alpine Linux is a distribution weighing just ~5MB. It's more secure because it contains only what's necessary. If your application requires specific C libraries, consider the -slim version.
3. The Power of .dockerignore
Similar to .gitignore, this file prevents unnecessary data from being sent to the Docker daemon during a build (the so-called build context). Don't copy node_modules, .git folders, or log files. This dramatically speeds up the COPY . . process.
4. Combining Commands into Layers
Every RUN, COPY, and ADD instruction creates a new layer. Instead of multiple RUN commands, combine them into one using && and clean the package manager cache in the same layer.
# Best practice
RUN apt-get update && apt-get install -y \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
lightbulb Expert Tip
Always place instructions that change least frequently (e.g., installing system dependencies) at the top of your Dockerfile. This way Docker will use the cache on subsequent builds, saving you time.