{"id":4728,"date":"2026-03-02T21:21:11","date_gmt":"2026-03-02T15:51:11","guid":{"rendered":"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/"},"modified":"2026-03-02T21:21:11","modified_gmt":"2026-03-02T15:51:11","slug":"10-docker-best-practices-to-optimize-your-containers-2","status":"publish","type":"post","link":"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/","title":{"rendered":"10 Docker Best Practices to Optimize Your Containers"},"content":{"rendered":"<p>It is 05:42 AM. I have consumed four double-espressos, two cold slices of pepperoni pizza, and enough adrenaline to stop a rhino\u2019s heart. The production cluster is finally stable, no thanks to the &#8220;optimization&#8221; PR merged by a junior developer who thought they knew better than the last ten years of containerization history.<\/p>\n<p>I\u2019m writing this because if I see one more <code>Dockerfile<\/code> that starts with <code>FROM ubuntu:latest<\/code>, I am going to decommission my own biological life support system. This isn&#8217;t a &#8220;thought leadership&#8221; piece. This is a survival guide written in the blood of my sleep cycle. We are going to talk about <strong>docker best<\/strong> practices, not because they make the YAML look pretty, but because they prevent the 3:00 AM phone call that makes me want to throw my pager into a woodchipper.<\/p>\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_80 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">Table of Contents<\/p>\n<label for=\"ez-toc-cssicon-toggle-item-69d8b4dc77212\" class=\"ez-toc-cssicon-toggle-label\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/label><input type=\"checkbox\"  id=\"ez-toc-cssicon-toggle-item-69d8b4dc77212\"  aria-label=\"Toggle\" \/><nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/#1_THE_INCIDENT_REPORT_THE_NIGHT_THE_REGISTRY_DIED\" >1. THE INCIDENT REPORT: THE NIGHT THE REGISTRY DIED<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/#2_THE_AUTOPSY_ANATOMY_OF_A_DISASTER\" >2. THE AUTOPSY: ANATOMY OF A DISASTER<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/#3_THE_COMMANDMENTS_OF_SANITY\" >3. THE COMMANDMENTS OF SANITY<\/a><ul class='ez-toc-list-level-4' ><li class='ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/#H2_STOP_USING_%E2%80%98LATEST_BEFORE_YOU_KILL_US_ALL\" >H2: STOP USING &#8216;LATEST&#8217; BEFORE YOU KILL US ALL<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/#H2_YOUR_LAYERS_ARE_FAT_AND_YOU_SHOULD_FEEL_BAD\" >H2: YOUR LAYERS ARE FAT AND YOU SHOULD FEEL BAD<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/#H2_ROOT_IS_FOR_GOD_AND_IDIOTS_AND_YOU_ARENT_GOD\" >H2: ROOT IS FOR GOD AND IDIOTS, AND YOU AREN&#8217;T GOD<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/#H2_SIGNAL_PROPAGATION_OR_WHY_YOUR_APP_TAKES_30_SECONDS_TO_DIE\" >H2: SIGNAL PROPAGATION OR: WHY YOUR APP TAKES 30 SECONDS TO DIE<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/#H2_THE_DOCKERIGNORE_FILE_IS_NOT_A_SUGGESTION\" >H2: THE .DOCKERIGNORE FILE IS NOT A SUGGESTION<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/#H2_BUILDKIT_IS_NOT_A_SUGGESTION_IT_IS_THE_LAW\" >H2: BUILDKIT IS NOT A SUGGESTION, IT IS THE LAW<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/#4_THE_MECHANICS_OF_FAILURE_CGROUPS_AND_KERNEL_PANIC\" >4. THE MECHANICS OF FAILURE: CGROUPS AND KERNEL PANIC<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-11\" href=\"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/#5_THE_%E2%80%9CFIX_IT%E2%80%9D_DIFF\" >5. THE &#8220;FIX IT&#8221; DIFF<\/a><ul class='ez-toc-list-level-4' ><li class='ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-12\" href=\"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/#THE_CRIME_Before\" >THE CRIME (Before)<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-13\" href=\"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/#THE_SANITY_After\" >THE SANITY (After)<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-14\" href=\"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/#6_WHY_WE_USE_MULTI-STAGE_BUILDS_THE_DEEP_DIVE\" >6. WHY WE USE MULTI-STAGE BUILDS (THE DEEP DIVE)<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-15\" href=\"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/#7_THE_FINAL_WORD_ON_CACHING\" >7. THE FINAL WORD ON CACHING<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-16\" href=\"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/#Related_Articles\" >Related Articles<\/a><\/li><\/ul><\/nav><\/div>\n<h3><span class=\"ez-toc-section\" id=\"1_THE_INCIDENT_REPORT_THE_NIGHT_THE_REGISTRY_DIED\"><\/span>1. THE INCIDENT REPORT: THE NIGHT THE REGISTRY DIED<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p><strong>[2024-05-14 03:02:11]<\/strong> PagerDuty triggers. <code>CRITICAL: KubeNodeDiskPressure<\/code> on the <code>us-east-1<\/code> production-a cluster.<br \/>\n<strong>[2024-05-14 03:05:45]<\/strong> <code>CRITICAL: KubePodNotReady<\/code>. The <code>api-gateway<\/code> service is failing readiness probes.<br \/>\n<strong>[2024-05-14 03:10:22]<\/strong> I\u2019m online. <code>kubectl get nodes<\/code> shows three nodes in <code>NotReady<\/code> state. <code>df -h<\/code> on the nodes shows <code>\/var\/lib\/docker<\/code> is at 100% capacity.<br \/>\n<strong>[2024-05-14 03:15:00]<\/strong> Attempting to purge unused images with <code>docker image prune -a<\/code>. It\u2019s taking forever because the I\/O wait is through the roof.<br \/>\n<strong>[2024-05-14 03:22:18]<\/strong> I find the culprit. A new deployment of the <code>user-profile-service<\/code>. The image size is 4.2GB. For a Python microservice.<br \/>\n<strong>[2024-05-14 03:30:45]<\/strong> The Horizontal Pod Autoscaler (HPA) is trying to spin up more pods to handle the failing requests. Each pod pull is 4.2GB. The internal container registry is now screaming under a 40Gbps egress load.<br \/>\n<strong>[2024-05-14 03:45:12]<\/strong> Registry enters a crash loop. All deployments across the entire company are now paralyzed.<br \/>\n<strong>[2024-05-14 04:00:00]<\/strong> I manually kill the deployment and roll back to the previous version (which was 220MB).<br \/>\n<strong>[2024-05-14 04:30:00]<\/strong> Nodes are still stuck. The <code>overlay2<\/code> storage driver is struggling to clean up the massive layers. I have to manually <code>rm -rf \/var\/lib\/docker\/overlay2\/*<\/code> on three nodes and rejoin them to the cluster.<br \/>\n<strong>[2024-05-14 05:30:00]<\/strong> Traffic normalizes. The &#8220;optimization&#8221; was a junior dev adding <code>apt-get install -y cuda-toolkit<\/code> &#8220;just in case we need AI features later&#8221; and forgetting to clean the cache.<\/p>\n<hr \/>\n<h3><span class=\"ez-toc-section\" id=\"2_THE_AUTOPSY_ANATOMY_OF_A_DISASTER\"><\/span>2. THE AUTOPSY: ANATOMY OF A DISASTER<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>I ran a <code>docker history --no-trunc<\/code> on that 4.2GB monstrosity. Look at this. Look at it and weep.<\/p>\n<pre class=\"codehilite\"><code class=\"language-text\">IMAGE          CREATED          CREATED BY                                                                                                                                                                                                                                                                                                                                                                                          SIZE      COMMENT\n&lt;missing&gt;      3 hours ago      COPY . . # buildkit                                                                                                                                                                                                                                                                                                                                                                                 1.8GB     \n&lt;missing&gt;      3 hours ago      RUN \/bin\/sh -c apt-get update &amp;&amp; apt-get install -y python3-pip git vim curl wget build-essential cmake cuda-toolkit-12-1 # buildkit                                                                                                                                                                                                                                                                2.1GB     \n&lt;missing&gt;      3 hours ago      RUN \/bin\/sh -c pip install -r requirements.txt # buildkit                                                                                                                                                                                                                                                                                                                                           300MB     \n&lt;missing&gt;      4 weeks ago      \/bin\/sh -c #(nop)  CMD [&quot;\/bin\/bash&quot;]                                                                                                                                                                                                                                                                                                                                                                0B        \n&lt;missing&gt;      4 weeks ago      \/bin\/sh -c #(nop) ADD file:702967679848520898520984520984520984520984520984520984520984520 in \/                                                                                                                                                                                                                                                                                                    77.8MB    \n<\/code><\/pre>\n<p>The <code>COPY . .<\/code> command pulled in the entire <code>.git<\/code> directory (800MB), the local <code>venv<\/code> (600MB), and a bunch of raw <code>.csv<\/code> test data (400MB) because the developer didn&#8217;t know what a <code>.dockerignore<\/code> file was. The <code>apt-get<\/code> layer is a crime against humanity. They didn&#8217;t use <code>--no-install-recommends<\/code>, and they didn&#8217;t clean up <code>\/var\/lib\/apt\/lists\/<\/code>. <\/p>\n<p>This is why we can&#8217;t have nice things.<\/p>\n<hr \/>\n<h3><span class=\"ez-toc-section\" id=\"3_THE_COMMANDMENTS_OF_SANITY\"><\/span>3. THE COMMANDMENTS OF SANITY<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<h4><span class=\"ez-toc-section\" id=\"H2_STOP_USING_%E2%80%98LATEST_BEFORE_YOU_KILL_US_ALL\"><\/span>H2: STOP USING &#8216;LATEST&#8217; BEFORE YOU KILL US ALL<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>If you use <code>FROM python:latest<\/code> or <code>FROM node:latest<\/code>, you are playing Russian Roulette with the production environment. &#8220;Latest&#8221; is a moving target. When the Python maintainers decide to switch the base image from Debian Bullseye to Bookworm, or change the default OpenSSL version, your build is going to break. Or worse, it\u2019s going to build successfully but fail at runtime with a <code>GLIBC_2.34 not found<\/code> error.<\/p>\n<p>You must use specific, immutable tags. Not just <code>python:3.11<\/code>. Use <code>python:3.11.9-slim-bookworm<\/code>. This tells me exactly what the runtime environment is, what the underlying OS is, and it ensures that when I rebuild this image in six months, I get the same result. This is the first rule of <strong>docker best<\/strong> practices: reproducibility is not optional.<\/p>\n<h4><span class=\"ez-toc-section\" id=\"H2_YOUR_LAYERS_ARE_FAT_AND_YOU_SHOULD_FEEL_BAD\"><\/span>H2: YOUR LAYERS ARE FAT AND YOU SHOULD FEEL BAD<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>Docker images are a stack of read-only layers. Every <code>RUN<\/code>, <code>COPY<\/code>, and <code>ADD<\/code> instruction creates a new layer. If you do this:<\/p>\n<pre class=\"codehilite\"><code class=\"language-dockerfile\">RUN apt-get update\nRUN apt-get install -y heavy-package\nRUN rm -rf \/var\/lib\/apt\/lists\/*\n<\/code><\/pre>\n<p>You have failed. The <code>heavy-package<\/code> is still there in the second layer. Deleting it in the third layer doesn&#8217;t remove it from the image; it just hides it in the top layer&#8217;s view. The bits are still being pushed to the registry.<\/p>\n<p>You must chain your commands:<\/p>\n<pre class=\"codehilite\"><code class=\"language-dockerfile\">RUN apt-get update &amp;&amp; apt-get install -y \\\n    --no-install-recommends \\\n    heavy-package \\\n    &amp;&amp; rm -rf \/var\/lib\/apt\/lists\/*\n<\/code><\/pre>\n<p>And for the love of everything holy, use <strong>multi-stage builds<\/strong>. Your runtime image doesn&#8217;t need <code>gcc<\/code>, <code>make<\/code>, <code>git<\/code>, or the headers for every library you compiled. Build your wheels in a <code>builder<\/code> stage, then copy only the artifacts to a <code>slim<\/code> or <code>distroless<\/code> final stage.<\/p>\n<h4><span class=\"ez-toc-section\" id=\"H2_ROOT_IS_FOR_GOD_AND_IDIOTS_AND_YOU_ARENT_GOD\"><\/span>H2: ROOT IS FOR GOD AND IDIOTS, AND YOU AREN&#8217;T GOD<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>Running your application as <code>root<\/code> inside a container is a firing offense in my book. If there is a container escape vulnerability (and there will be), you\u2019ve just handed the attacker a golden ticket to the host kernel. <\/p>\n<p>Modern namespaces and cgroups v2 provide isolation, but they aren&#8217;t magic. If your process is UID 0, and it manages to break out via a <code>shocker<\/code> style exploit or a misconfigured volume mount to <code>\/var\/run\/docker.sock<\/code>, the game is over. <\/p>\n<p>Always create a system user and switch to it:<\/p>\n<pre class=\"codehilite\"><code class=\"language-dockerfile\">RUN groupadd -g 10001 appuser &amp;&amp; \\\n    useradd -u 10000 -g appuser appuser\nUSER 10000\n<\/code><\/pre>\n<p>Notice I used explicit UIDs. This prevents collisions with the host system and makes it easier to manage file permissions on persistent volumes.<\/p>\n<h4><span class=\"ez-toc-section\" id=\"H2_SIGNAL_PROPAGATION_OR_WHY_YOUR_APP_TAKES_30_SECONDS_TO_DIE\"><\/span>H2: SIGNAL PROPAGATION OR: WHY YOUR APP TAKES 30 SECONDS TO DIE<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>When Kubernetes sends a <code>SIGTERM<\/code> to your pod, it\u2019s politely asking your app to finish its current request, close database connections, and exit. If your app is running as a sub-process of a shell script, it will never see that signal.<\/p>\n<p>If you use <code>CMD my-app.sh<\/code>, Docker runs it as <code>\/bin\/sh -c my-app.sh<\/code>. The shell (PID 1) does not forward signals to its children. When the 30-second <code>terminationGracePeriodSeconds<\/code> expires, the kernel sends a <code>SIGKILL<\/code>, which is like pulling the power plug. This leads to corrupted state and broken transactions.<\/p>\n<p>Use the &#8220;exec form&#8221;: <code>ENTRYPOINT [\"\/usr\/bin\/my-app\"]<\/code>. This runs your app as PID 1. If your app can&#8217;t handle being PID 1 (e.g., it doesn&#8217;t reap zombie processes), use <code>tini<\/code> as your entrypoint. It\u2019s a tiny init binary designed exactly for this.<\/p>\n<h4><span class=\"ez-toc-section\" id=\"H2_THE_DOCKERIGNORE_FILE_IS_NOT_A_SUGGESTION\"><\/span>H2: THE .DOCKERIGNORE FILE IS NOT A SUGGESTION<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>When you run <code>docker build .<\/code>, the first thing the CLI does is send the &#8220;build context&#8221; to the Docker daemon. If you have a 2GB <code>node_modules<\/code> folder or a <code>.git<\/code> directory with five years of history, you are sending gigabytes of useless data over the socket before the build even starts.<\/p>\n<p>A proper <code>.dockerignore<\/code> is a <strong>docker best<\/strong> practice that saves hours of CI\/CD time. It should include:<br \/>\n&#8211; <code>.git<\/code><br \/>\n&#8211; <code>**\/node_modules<\/code><br \/>\n&#8211; <code>**\/__pycache__<\/code><br \/>\n&#8211; <code>*.log<\/code><br \/>\n&#8211; <code>.env<\/code> (don&#8217;t you dare bake secrets into your image)<br \/>\n&#8211; <code>tmp\/<\/code><\/p>\n<h4><span class=\"ez-toc-section\" id=\"H2_BUILDKIT_IS_NOT_A_SUGGESTION_IT_IS_THE_LAW\"><\/span>H2: BUILDKIT IS NOT A SUGGESTION, IT IS THE LAW<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>If you aren&#8217;t using <code>DOCKER_BUILDKIT=1<\/code>, you are living in the stone age. Modern BuildKit allows for <code>COPY --link<\/code>, which is a game-changer. <\/p>\n<p>Normally, if you change a file in an early layer, every subsequent layer must be rebuilt because the hash of the underlying filesystem changed. <code>COPY --link<\/code> puts files into a separate snapshot and merges them into the final image without depending on the previous layers&#8217; state. It means if you change your app code, but your dependencies haven&#8217;t changed, Docker can just swap the app layer without re-calculating anything.<\/p>\n<p>Also, use <code>RUN --mount=type=cache<\/code>. This allows you to persist your <code>pip<\/code> cache or <code>npm<\/code> cache between builds without bloating the final image. It\u2019s the difference between a 10-minute build and a 30-second build.<\/p>\n<hr \/>\n<h3><span class=\"ez-toc-section\" id=\"4_THE_MECHANICS_OF_FAILURE_CGROUPS_AND_KERNEL_PANIC\"><\/span>4. THE MECHANICS OF FAILURE: CGROUPS AND KERNEL PANIC<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Let&#8217;s talk about why that 4.2GB image actually killed the nodes. It wasn&#8217;t just the disk space. It was the memory overhead and the way the Linux kernel handles <code>overlay2<\/code>.<\/p>\n<p>When you pull a massive image, the <code>dockerd<\/code> process has to decompress those layers. This consumes CPU and memory. On a node already under pressure, this triggers the OOM (Out Of Memory) killer. But the OOM killer is a blunt instrument. It might kill the <code>kubelet<\/code> instead of the offending <code>docker pull<\/code> process. <\/p>\n<p>Furthermore, we are running on <strong>cgroups v2<\/strong>. When you don&#8217;t set memory limits on your containers, or when your runtime (like Java 8 or older Node versions) isn&#8217;t cgroup-aware, the process sees the total host memory. It tries to allocate a heap based on 128GB of RAM while the container is actually restricted to 4GB by the orchestrator. The result? A constant cycle of <code>CrashLoopBackOff<\/code>.<\/p>\n<p>The junior dev\u2019s image also didn&#8217;t have a <code>HEALTHCHECK<\/code> instruction. Kubernetes was relying on TCP socket checks, which passed because the container was &#8220;running,&#8221; even though the application inside was stuck in a <code>python-magic<\/code> library dependency hell caused by the bloated <code>apt-get install<\/code>.<\/p>\n<hr \/>\n<h3><span class=\"ez-toc-section\" id=\"5_THE_%E2%80%9CFIX_IT%E2%80%9D_DIFF\"><\/span>5. THE &#8220;FIX IT&#8221; DIFF<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Here is the &#8220;Before&#8221; (The Crime) and the &#8220;After&#8221; (The Sanity).<\/p>\n<h4><span class=\"ez-toc-section\" id=\"THE_CRIME_Before\"><\/span>THE CRIME (Before)<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<pre class=\"codehilite\"><code class=\"language-dockerfile\"># This is garbage. Do not do this.\nFROM python:latest\n\n# No .dockerignore, so this pulls in 2GB of junk\nCOPY . \/app\nWORKDIR \/app\n\n# Massive layer, no cleanup, installs unnecessary compilers\nRUN apt-get update &amp;&amp; apt-get install -y git build-essential vim cmake cuda-toolkit-12-1\nRUN pip install -r requirements.txt\n\n# Runs as root. Dangerous.\n# Uses shell form, so SIGTERM is ignored.\nCMD python main.py\n<\/code><\/pre>\n<h4><span class=\"ez-toc-section\" id=\"THE_SANITY_After\"><\/span>THE SANITY (After)<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<pre class=\"codehilite\"><code class=\"language-dockerfile\"># Use a specific, small base image. \n# python:3.11-slim-bookworm is ~120MB vs 1GB+ for the full image.\nFROM python:3.11.9-slim-bookworm AS builder\n\n# Set environment variables for Python\nENV PYTHONDONTWRITEBYTECODE=1 \\\n    PYTHONUNBUFFERED=1 \\\n    PIP_NO_CACHE_DIR=1\n\nWORKDIR \/build\n\n# Install build dependencies in the builder stage only\nRUN apt-get update &amp;&amp; apt-get install -y --no-install-recommends \\\n    gcc \\\n    python3-dev \\\n    &amp;&amp; rm -rf \/var\/lib\/apt\/lists\/*\n\n# Use a cache mount for pip to speed up builds\nCOPY requirements.txt .\nRUN --mount=type=cache,target=\/root\/.cache\/pip \\\n    pip wheel --no-deps --wheel-dir \/build\/wheels -r requirements.txt\n\n# --- Final Stage ---\nFROM python:3.11.9-slim-bookworm\n\n# Create a non-privileged user\nRUN groupadd -g 10001 appgroup &amp;&amp; \\\n    useradd -u 10000 -g appgroup -m -s \/bin\/bash appuser\n\nWORKDIR \/app\n\n# Copy only the wheels from the builder\nCOPY --from=builder \/build\/wheels \/wheels\nRUN pip install --no-cache \/wheels\/*\n\n# Use --link to optimize cache invalidation\nCOPY --link --chown=10000:10001 . .\n\n# Switch to non-root user\nUSER 10000\n\n# Use tini to handle signals correctly\n# This is a docker best practice for signal propagation\nRUN apt-get update &amp;&amp; apt-get install -y tini &amp;&amp; rm -rf \/var\/lib\/apt\/lists\/*\nENTRYPOINT [&quot;\/usr\/bin\/tini&quot;, &quot;--&quot;]\n\n# Exec form of CMD\nCMD [&quot;python&quot;, &quot;main.py&quot;]\n<\/code><\/pre>\n<hr \/>\n<h3><span class=\"ez-toc-section\" id=\"6_WHY_WE_USE_MULTI-STAGE_BUILDS_THE_DEEP_DIVE\"><\/span>6. WHY WE USE MULTI-STAGE BUILDS (THE DEEP DIVE)<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>I need you to understand the &#8220;why&#8221; here. In the &#8220;After&#8221; Dockerfile, the <code>builder<\/code> stage is where all the mess happens. We install <code>gcc<\/code>, we compile C-extensions for Python libraries, and we create a bunch of temporary files. <\/p>\n<p>When we transition to the second <code>FROM<\/code> instruction, we start with a fresh, clean slate. The <code>gcc<\/code> compiler, the headers, and the <code>apt<\/code> cache are all left behind in the first stage. We only <code>COPY<\/code> the pre-compiled wheels. This is how you get an image from 4GB down to 150MB.<\/p>\n<p>Smaller images mean:<br \/>\n1.  <strong>Faster Pulls<\/strong>: During a scale-up event, 150MB pulls in seconds. 4GB pulls in minutes (or fails).<br \/>\n2.  <strong>Reduced Attack Surface<\/strong>: If a hacker gets into your container, they won&#8217;t find <code>git<\/code>, <code>gcc<\/code>, or <code>curl<\/code> to help them move laterally through your network.<br \/>\n3.  <strong>Lower Costs<\/strong>: We pay for S3 storage for the registry and for data transfer. Bloated images are literally burning company money.<\/p>\n<hr \/>\n<h3><span class=\"ez-toc-section\" id=\"7_THE_FINAL_WORD_ON_CACHING\"><\/span>7. THE FINAL WORD ON CACHING<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Docker&#8217;s layer caching is based on the order of operations. If you <code>COPY . .<\/code> at the top of your Dockerfile, any change to <em>any<\/em> file in your repository invalidates the cache for every single line below it. <\/p>\n<p>That is why we copy <code>requirements.txt<\/code> (or <code>package.json<\/code>) first, install the dependencies, and <em>then<\/em> copy the rest of the source code. Dependencies change much less frequently than code. By separating them, 90% of your builds will use the cached dependency layer, making your CI\/CD pipeline actually usable instead of a bottleneck.<\/p>\n<p>I am going to sleep now. If I wake up and see a <code>FROM ubuntu:latest<\/code> in the PR queue, I am revoking everyone&#8217;s <code>sudo<\/code> access and we are going back to deploying via FTP on shared hosting. <\/p>\n<p>Do better. For the sake of my sanity and the uptime of this company.<\/p>\n<p><strong>Signed,<\/strong><br \/>\n<em>The SRE who seen too many layers.<\/em><\/p>\n<h2><span class=\"ez-toc-section\" id=\"Related_Articles\"><\/span>Related Articles<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Explore more insights and best practices:<\/p>\n<ul>\n<li><a href=\"https:\/\/itsupportwale.com\/blog\/8-people-to-whatsapp-voice-and-video-group-call\/\">8 People To Whatsapp Voice And Video Group Call<\/a><\/li>\n<li><a href=\"https:\/\/itsupportwale.com\/blog\/serverless-computing-the-future-of-cloud-development\/\">Serverless Computing The Future Of Cloud Development<\/a><\/li>\n<li><a href=\"https:\/\/itsupportwale.com\/blog\/the-ultimate-kubectl-cheat-sheet-you-ever-need\/\">The Ultimate Kubectl Cheat Sheet You Ever Need<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>It is 05:42 AM. I have consumed four double-espressos, two cold slices of pepperoni pizza, and enough adrenaline to stop a rhino\u2019s heart. The production cluster is finally stable, no thanks to the &#8220;optimization&#8221; PR merged by a junior developer who thought they knew better than the last ten years of containerization history. I\u2019m writing &#8230; <a title=\"10 Docker Best Practices to Optimize Your Containers\" class=\"read-more\" href=\"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/\" aria-label=\"Read more  on 10 Docker Best Practices to Optimize Your Containers\">Read more<\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-4728","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.0 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>10 Docker Best Practices to Optimize Your Containers - ITSupportWale<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"10 Docker Best Practices to Optimize Your Containers - ITSupportWale\" \/>\n<meta property=\"og:description\" content=\"It is 05:42 AM. I have consumed four double-espressos, two cold slices of pepperoni pizza, and enough adrenaline to stop a rhino\u2019s heart. The production cluster is finally stable, no thanks to the &#8220;optimization&#8221; PR merged by a junior developer who thought they knew better than the last ten years of containerization history. I\u2019m writing ... Read more\" \/>\n<meta property=\"og:url\" content=\"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/\" \/>\n<meta property=\"og:site_name\" content=\"ITSupportWale\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/Itsupportwale-298547177495978\" \/>\n<meta property=\"article:published_time\" content=\"2026-03-02T15:51:11+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/itsupportwale.com\/blog\/wp-content\/uploads\/2021\/05\/android-chrome-512x512-1.png\" \/>\n\t<meta property=\"og:image:width\" content=\"512\" \/>\n\t<meta property=\"og:image:height\" content=\"512\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Techie\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Techie\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"11 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/\"},\"author\":{\"name\":\"Techie\",\"@id\":\"https:\/\/itsupportwale.com\/blog\/#\/schema\/person\/8c5a2b3d36396e0a8fd91ec8242fd46d\"},\"headline\":\"10 Docker Best Practices to Optimize Your Containers\",\"datePublished\":\"2026-03-02T15:51:11+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/\"},\"wordCount\":1677,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/itsupportwale.com\/blog\/#organization\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/\",\"url\":\"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/\",\"name\":\"10 Docker Best Practices to Optimize Your Containers - ITSupportWale\",\"isPartOf\":{\"@id\":\"https:\/\/itsupportwale.com\/blog\/#website\"},\"datePublished\":\"2026-03-02T15:51:11+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/itsupportwale.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"10 Docker Best Practices to Optimize Your Containers\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/itsupportwale.com\/blog\/#website\",\"url\":\"https:\/\/itsupportwale.com\/blog\/\",\"name\":\"ITSupportWale\",\"description\":\"Tips, Tricks, Fixed-Errors, Tutorials &amp; Guides\",\"publisher\":{\"@id\":\"https:\/\/itsupportwale.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/itsupportwale.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/itsupportwale.com\/blog\/#organization\",\"name\":\"itsupportwale\",\"url\":\"https:\/\/itsupportwale.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/itsupportwale.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/itsupportwale.com\/blog\/wp-content\/uploads\/2023\/09\/cropped-Logo-trans-without-slogan.png\",\"contentUrl\":\"https:\/\/itsupportwale.com\/blog\/wp-content\/uploads\/2023\/09\/cropped-Logo-trans-without-slogan.png\",\"width\":1119,\"height\":144,\"caption\":\"itsupportwale\"},\"image\":{\"@id\":\"https:\/\/itsupportwale.com\/blog\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/Itsupportwale-298547177495978\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/itsupportwale.com\/blog\/#\/schema\/person\/8c5a2b3d36396e0a8fd91ec8242fd46d\",\"name\":\"Techie\",\"sameAs\":[\"https:\/\/itsupportwale.com\",\"iswblogadmin\"],\"url\":\"https:\/\/itsupportwale.com\/blog\/author\/iswblogadmin\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"10 Docker Best Practices to Optimize Your Containers - ITSupportWale","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/","og_locale":"en_US","og_type":"article","og_title":"10 Docker Best Practices to Optimize Your Containers - ITSupportWale","og_description":"It is 05:42 AM. I have consumed four double-espressos, two cold slices of pepperoni pizza, and enough adrenaline to stop a rhino\u2019s heart. The production cluster is finally stable, no thanks to the &#8220;optimization&#8221; PR merged by a junior developer who thought they knew better than the last ten years of containerization history. I\u2019m writing ... Read more","og_url":"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/","og_site_name":"ITSupportWale","article_publisher":"https:\/\/www.facebook.com\/Itsupportwale-298547177495978","article_published_time":"2026-03-02T15:51:11+00:00","og_image":[{"width":512,"height":512,"url":"https:\/\/itsupportwale.com\/blog\/wp-content\/uploads\/2021\/05\/android-chrome-512x512-1.png","type":"image\/png"}],"author":"Techie","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Techie","Est. reading time":"11 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/#article","isPartOf":{"@id":"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/"},"author":{"name":"Techie","@id":"https:\/\/itsupportwale.com\/blog\/#\/schema\/person\/8c5a2b3d36396e0a8fd91ec8242fd46d"},"headline":"10 Docker Best Practices to Optimize Your Containers","datePublished":"2026-03-02T15:51:11+00:00","mainEntityOfPage":{"@id":"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/"},"wordCount":1677,"commentCount":0,"publisher":{"@id":"https:\/\/itsupportwale.com\/blog\/#organization"},"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/","url":"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/","name":"10 Docker Best Practices to Optimize Your Containers - ITSupportWale","isPartOf":{"@id":"https:\/\/itsupportwale.com\/blog\/#website"},"datePublished":"2026-03-02T15:51:11+00:00","breadcrumb":{"@id":"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/itsupportwale.com\/blog\/10-docker-best-practices-to-optimize-your-containers-2\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/itsupportwale.com\/blog\/"},{"@type":"ListItem","position":2,"name":"10 Docker Best Practices to Optimize Your Containers"}]},{"@type":"WebSite","@id":"https:\/\/itsupportwale.com\/blog\/#website","url":"https:\/\/itsupportwale.com\/blog\/","name":"ITSupportWale","description":"Tips, Tricks, Fixed-Errors, Tutorials &amp; Guides","publisher":{"@id":"https:\/\/itsupportwale.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/itsupportwale.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/itsupportwale.com\/blog\/#organization","name":"itsupportwale","url":"https:\/\/itsupportwale.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/itsupportwale.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/itsupportwale.com\/blog\/wp-content\/uploads\/2023\/09\/cropped-Logo-trans-without-slogan.png","contentUrl":"https:\/\/itsupportwale.com\/blog\/wp-content\/uploads\/2023\/09\/cropped-Logo-trans-without-slogan.png","width":1119,"height":144,"caption":"itsupportwale"},"image":{"@id":"https:\/\/itsupportwale.com\/blog\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/Itsupportwale-298547177495978"]},{"@type":"Person","@id":"https:\/\/itsupportwale.com\/blog\/#\/schema\/person\/8c5a2b3d36396e0a8fd91ec8242fd46d","name":"Techie","sameAs":["https:\/\/itsupportwale.com","iswblogadmin"],"url":"https:\/\/itsupportwale.com\/blog\/author\/iswblogadmin\/"}]}},"_links":{"self":[{"href":"https:\/\/itsupportwale.com\/blog\/wp-json\/wp\/v2\/posts\/4728","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/itsupportwale.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/itsupportwale.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/itsupportwale.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/itsupportwale.com\/blog\/wp-json\/wp\/v2\/comments?post=4728"}],"version-history":[{"count":0,"href":"https:\/\/itsupportwale.com\/blog\/wp-json\/wp\/v2\/posts\/4728\/revisions"}],"wp:attachment":[{"href":"https:\/\/itsupportwale.com\/blog\/wp-json\/wp\/v2\/media?parent=4728"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/itsupportwale.com\/blog\/wp-json\/wp\/v2\/categories?post=4728"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/itsupportwale.com\/blog\/wp-json\/wp\/v2\/tags?post=4728"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}