Building Docker Images
Dockerfile Best Practices — Layer Ordering, .dockerignore, Non-Root Users
Structuring layers, reducing size, and running securely.
Interview: Essential for testing your production engineering skills. Be prepared to analyze a poorly written Dockerfile and identify layer issues, cache misses, and security holes.
Introduction
Writing a Dockerfile that "just works" is easy. Writing a Dockerfile that builds rapidly, runs securely, and minimizes resource usage is much harder.
Production container engineering relies on structural conventions that optimize the compilation process and protect the underlying host operating systems.
The Four Pillars of Best Practices
1. Layer Chaining & Cleanup
Every RUN directive creates a new history layer. To avoid bloating images:
• Chain multiple shell commands using && and backslashes (\).
• Clean up package lists and temporary build files in the **same instruction** they are installed:
2. Cache Alignment Ordering
Copy static lock files and download dependencies *before* copying your active source directories. Since application code changes on every commit, copying dependencies first keeps the package downloads cached.
3. Context Reduction (.dockerignore)
Create a .dockerignore file in your build root. This prevents heavy, secret, or unnecessary directories (like .git, local node_modules, and local configurations) from being sent to the Docker daemon.
4. Run as Non-Root
Create a custom system group and user, and assign ownership to your active runtime directory:
Quick Quiz
Q1: Why should "rm -rf /var/lib/apt/lists/*" be in the same RUN command as "apt-get install"?
A) Because it speeds up command execution.
B) Because Docker merges them anyway.
C) If run in a separate RUN instruction, the files are deleted in the new layer, but they remain inside the previous layer history, saving no image space.
D) To avoid running as root.
Answer: C — File cleanup must be done in the same layer they were created, to prevent the build data from being committed to history.
Scenario-Based Challenge
Production Scenario:
You run docker build .. The output terminal hangs at "Sending build context to Docker daemon: 4.8 GB" for several minutes, even though your Node application files only consume 5MB. What is causing this delay, and how do you resolve it?
Cause: When you start a build, the Docker client packs everything in your root directory into a tar archive (the build context) and sends it to the daemon. Without a .dockerignore file, Docker packs your local node_modules folder, local log directories, database exports, and the hidden .git repository history.
Solution: Create a .dockerignore file in the project root:
Interview Questions
1. Why is it recommended to use double quotes and JSON arrays in CMD/ENTRYPOINT?
Using JSON arrays (Exec Form) executes commands directly without starting a shell wrapper. This ensures your app runs as PID 1, meaning it receives termination signals (SIGTERM) immediately, allowing database connections to close gracefully.
2. What does the "--no-install-recommends" flag do in apt-get?
It tells the package manager to install only the explicit package dependencies, skipping optional recommended packages. This prevents the installation of dozens of auxiliary libraries, keeping image sizes small.
Production Considerations
Use security linters (like **Hadolint**) in your CI/CD pipelines to audit Dockerfiles. Hadolint scans instructions and highlights violations such as missing package tags, unchained commands, or running as root, blocking dangerous code before it reaches production.