Discovering Docker and DevOps: My Learning Experience Begins
Day 8: Optimizing Docker Builds: Efficient Layer Caching and Reducing Image Sizes
🧠 What I Learned Today
Introduction
Continuing my Docker and DevOps learning journey, today I focused on refining Dockerfiles to make them faster and more efficient. Let’s dive into optimizing Docker layers and reducing image sizes. This guide breaks down how small changes in a Dockerfile can save both time and resources and why these best practices are crucial, especially as your project grows.
Why Optimizing Docker Layers Matters
Every command in a Dockerfile represents a “layer.” By leveraging Docker’s caching and efficient layer setup, we can avoid unnecessary reinstallation of packages and minimize rebuild times, especially for resource-heavy operations like npm install
.
Question: Why Does RUN npm install
Keep Executing?
In a basic Dockerfile setup, commands are executed line-by-line, top to bottom. Here’s a sample:
FROM node:20
WORKDIR /usr/src/app
COPY . .
RUN npm install
This setup can lead to unnecessary reinstallation of dependencies every time we make minor updates to our server files (like index.js
), as RUN npm install
is placed after the COPY . .
command. Since Docker caches layers progressively, any change to a command’s result affects all subsequent layers. So, when we edit index.js
, Docker invalidates layers from the COPY . .
step onward, forcing npm install
to rerun—wasting time and resources.
Solution: Optimizing the Dockerfile for Efficient Caching
By copying only package.json
and package-lock.json
files first, we avoid re-running RUN npm install
unless our dependency files change. Here’s an optimized version:
FROM node:20
WORKDIR /usr/src/app
COPY package*.json . # Only copies package files
RUN npm install # Installs dependencies only once if package.json remains unchanged
COPY . . # Copies the rest of the application
In this setup:
Layer 1-3: Node image setup, setting up the working directory, and copying
package.json
.Layer 4: Installs all dependencies listed in
package.json
.Layer 5: Copies remaining application files.
Now, when we make changes to the server code in index.js
, Docker rebuilds only from Layer 5, saving time and avoiding the unnecessary reinstallation of packages. This approach is particularly helpful in development environments where code changes are frequent.
Qusetion: Can We Reduce Docker Image Size?
Yes! Docker images can be large (often around 1GB), but by using “slim” or “alpine” versions of base images, we can significantly reduce the size.
Example: Using an alpine version of Node:
FROM mhart/alpine-node
The alpine version of the Node image is around 112MB compared to the regular image’s ~1GB size. Alpine Linux is a minimal, security-oriented distribution that’s great for lightweight builds. Note that it may require additional libraries for compatibility, so test to ensure your app runs smoothly.
Quick Recap
Avoid unnecessary rebuilds: Copy
package.json
first to preventRUN npm install
from rerunning on every code change.Reduce image size: Use alpine images where possible.
🔥 Takeaway
Understanding Docker’s layer-caching and image-size optimization can make your builds faster and lighter. With efficient Dockerfiles, you save on resources and make the development cycle smoother.
What’s Next?
In the next few days, I’ll be covering more Docker concepts, including how to manage containers more efficiently. Stay tuned for more updates as I continue on this journey of mastering Docker & DevOps!
🔗 Follow My Docker & DevOps Journey!
I’ll be learning and posting daily updates, sharing challenges, solutions, and practical examples along the way. You can follow my progress using the hashtag: #SalmanDockerDevOpsJourney.
Subscribe to my newsletter
Read articles from Mohd Salman directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by