{"id":4847,"date":"2026-07-30T22:13:53","date_gmt":"2026-07-30T16:43:53","guid":{"rendered":"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/"},"modified":"2026-07-30T22:13:53","modified_gmt":"2026-07-30T16:43:53","slug":"docker-best-practices-optimize-your-container-workflow","status":"publish","type":"post","link":"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/","title":{"rendered":"Docker Best Practices: Optimize Your Container Workflow"},"content":{"rendered":"<p>I found the body of the production server at 3:14 AM, smothered by a 3.5GB image that had no business existing in a civilized society.<\/p>\n<p>The monitoring dashboard wasn&#8217;t just red; it was a screaming, digital hemorrhage. Disk pressure had triggered a death spiral. The kubelet, in its infinite, mechanical wisdom, tried to garbage collect images to save itself, but it couldn&#8217;t delete the one currently being pulled\u2014a bloated, necrotic mass of a container that was failing to start because the node had run out of IOPS just trying to unpack the damn thing.<\/p>\n<p>I\u2019ve spent fifteen years digging through the strata of failed &#8220;agile&#8221; deployments. I\u2019ve seen the rise and fall of a dozen &#8220;game-changing&#8221; orchestrators. And yet, here I was, staring at a Dockerfile written by someone who clearly thought a container was just a virtual machine that you didn&#8217;t have to pay for. It\u2019s the same story every time: a junior developer gets a &#8220;Hello World&#8221; running on their MacBook Pro with 32GB of RAM and assumes the cloud is just a bigger version of their laptop. It isn&#8217;t. The cloud is a cold, expensive place where every byte you ship costs money and every layer you mismanage is a security hole waiting to be kicked in.<\/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-6a6de668e8747\" 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-6a6de668e8747\"  aria-label=\"Toggle\" \/><nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/#The_Crime_Scene\" >The Crime Scene<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/#The_Layer_Cake_of_Lies\" >The Layer Cake of Lies<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/#The_Root_User_Sin\" >The Root User Sin<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/#The_Ghost_of_Dependencies_Past\" >The Ghost of Dependencies Past<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/#The_PID_1_Zombie_Apocalypse\" >The PID 1 Zombie Apocalypse<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/#The_Cache_Mount_Salvation\" >The Cache Mount Salvation<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/#The_dockerignore_Exclusion_Zone\" >The .dockerignore Exclusion Zone<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/#The_Surgical_Reconstruction_The_Final_Artifact\" >The Surgical Reconstruction: The Final Artifact<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/#The_Aftermath\" >The Aftermath<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/#Related_Articles\" >Related Articles<\/a><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"The_Crime_Scene\"><\/span>The Crime Scene<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>The repository was a graveyard of bad decisions. I pulled the Dockerfile and the <code>docker-compose.yml<\/code> from the wreckage. It was forty lines of pure, unadulterated architectural negligence.<\/p>\n<pre class=\"codehilite\"><code class=\"language-dockerfile\"># THE CRIME SCENE: Dockerfile\nFROM ubuntu:latest\n\n# Install everything including the kitchen sink\nRUN apt-get update\nRUN apt-get install -y python3 python3-pip build-essential git curl wget vim\n\n# Why is this here? Nobody knows.\nRUN curl -sL https:\/\/deb.nodesource.com\/setup_18.x | bash -\nRUN apt-get install -y nodejs\n\n# Copy everything, including .git, node_modules, and secrets.env\nADD . \/app\nWORKDIR \/app\n\n# Install dependencies as root\nRUN pip3 install -r requirements.txt\nRUN npm install\n\n# Open all the ports\nEXPOSE 1-65535\n\n# Run the app\nCMD [&quot;python3&quot;, &quot;app.py&quot;]\n<\/code><\/pre>\n<p>And the <code>docker-compose.yml<\/code> was no better:<\/p>\n<pre class=\"codehilite\"><code class=\"language-yaml\">version: '3.8'\nservices:\n  web:\n    build: .\n    privileged: true\n    volumes:\n      - .:\/app\n    environment:\n      - DEBUG=True\n      - DATABASE_URL=postgres:\/\/admin:password123@db:5432\/prod_db\n    ports:\n      - &quot;80:80&quot;\n<\/code><\/pre>\n<p>I ran a quick <code>docker images<\/code> and <code>docker history<\/code> on the resulting monstrosity. The results were enough to make a grown sysadmin weep.<\/p>\n<pre class=\"codehilite\"><code class=\"language-bash\">$ docker images\nREPOSITORY          TAG       IMAGE ID       CREATED          SIZE\nbloated-nightmare   latest    a1b2c3d4e5f6   10 minutes ago   3.52GB\n\n$ docker history bloated-nightmare\nIMAGE          CREATED          CREATED BY                                      SIZE      COMMENT\na1b2c3d4e5f6   10 minutes ago   CMD [&quot;python3&quot; &quot;app.py&quot;]                        0B        \n&lt;missing&gt;      10 minutes ago   RUN \/bin\/sh -c npm install                      840MB     \n&lt;missing&gt;      11 minutes ago   RUN \/bin\/sh -c pip3 install -r requirements.txt 420MB     \n&lt;missing&gt;      12 minutes ago   ADD . \/app                                      1.2GB     # Included a 1GB .git folder\n&lt;missing&gt;      15 minutes ago   RUN \/bin\/sh -c apt-get install -y nodejs        210MB     \n&lt;missing&gt;      16 minutes ago   RUN \/bin\/sh -c curl -sL https:\/\/deb.nodesource\u2026 5MB       \n&lt;missing&gt;      17 minutes ago   RUN \/bin\/sh -c apt-get install -y python3 pyth\u2026 650MB     \n&lt;missing&gt;      18 minutes ago   RUN \/bin\/sh -c apt-get update                   45MB      \n&lt;missing&gt;      20 minutes ago   FROM ubuntu:latest                              77.8MB    \n<\/code><\/pre>\n<p>3.52 gigabytes. For a Python script that queries a database and returns a JSON object. We are living in the end times.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"The_Layer_Cake_of_Lies\"><\/span>The Layer Cake of Lies<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>The first thing we have to address in this surgical reconstruction is the fundamental misunderstanding of how the Union File System (UnionFS) works. Every <code>RUN<\/code>, <code>COPY<\/code>, and <code>ADD<\/code> instruction in a Dockerfile creates a new layer. These layers are stacked. If you delete a file in layer 4 that was created in layer 3, the file is &#8220;hidden&#8221; in the final view, but it still exists in layer 3, taking up space and being pulled across the network.<\/p>\n<p>The junior dev who wrote this used <code>RUN apt-get update<\/code> as its own layer. Then <code>RUN apt-get install<\/code>. This is a classic rookie mistake. When you run <code>apt-get update<\/code>, you\u2019re downloading package indexes into <code>\/var\/lib\/apt\/lists\/<\/code>. If that happens in its own layer, those indexes stay in your image forever.<\/p>\n<p>In our reconstruction, we use the &#8220;Golden Rule of Layers&#8221;: combine commands and clean up in the same layer. But even before that, we stop using <code>ubuntu:latest<\/code>. &#8220;Latest&#8221; is a ticking time bomb. It\u2019s non-deterministic. Today it\u2019s Ubuntu 22.04; tomorrow it\u2019s 24.04, and suddenly your Python C-extensions won&#8217;t compile because the glibc version changed.<\/p>\n<p>We use <code>python:3.11-slim-bookworm<\/code>. It\u2019s Debian-based, stable, and doesn&#8217;t include the 400MB of &#8220;standard&#8221; utilities you\u2019ll never use in a container.<\/p>\n<pre class=\"codehilite\"><code class=\"language-bash\"># Comparison of base images\n$ docker images\nREPOSITORY   TAG              IMAGE ID       SIZE\nubuntu       latest           ba627c3a43f1   77.8MB\npython       3.11-slim-bookworm 1234567890ab   121MB\n# Note: The python image is larger because it includes the runtime, \n# but it's smaller than ubuntu + manual python install.\n<\/code><\/pre>\n<p>When we install packages, we do it like this:<\/p>\n<pre class=\"codehilite\"><code class=\"language-dockerfile\">RUN apt-get update &amp;&amp; apt-get install -y --no-install-recommends \\\n    build-essential \\\n    libpq-dev \\\n    &amp;&amp; rm -rf \/var\/lib\/apt\/lists\/*\n<\/code><\/pre>\n<p>By chaining the commands with <code>&amp;&amp;<\/code> and ending with <code>rm -rf \/var\/lib\/apt\/lists\/*<\/code>, we ensure the package indexes never make it into the final image layer. This is why following a docker best practice isn&#8217;t a suggestion; it&#8217;s a survival requirement for your disk space.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"The_Root_User_Sin\"><\/span>The Root User Sin<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Look at that <code>docker-compose.yml<\/code> again. <code>privileged: true<\/code>. Running as root inside the container. This is the equivalent of leaving your front door wide open, putting a &#8220;Free Money&#8221; sign on the lawn, and going on vacation.<\/p>\n<p>Containers use namespaces to isolate processes, but the UID 0 (root) inside the container is the same UID 0 as the host unless you\u2019ve gone through the pain of configuring user namespaces (which most people don&#8217;t). If a process escapes the container\u2014and they do\u2014it has root access to your host.<\/p>\n<p>Furthermore, the original Dockerfile didn&#8217;t define a <code>USER<\/code>. It defaulted to root. This means the application, the package manager, and every script had full reign over the container\u2019s internals.<\/p>\n<p>In the reconstruction, we create a non-privileged user. We don&#8217;t give them a shell. We don&#8217;t give them a home directory if we don&#8217;t have to.<\/p>\n<pre class=\"codehilite\"><code class=\"language-dockerfile\">RUN groupadd -g 10001 appuser &amp;&amp; \\\n    useradd -u 10000 -g appuser --create-home --shell \/sbin\/nologin appuser\n<\/code><\/pre>\n<p>We then ensure that only the necessary directories are owned by this user. We don&#8217;t <code>CHOWN<\/code> the whole <code>\/app<\/code> directory if we don&#8217;t have to, because\u2014guess what\u2014<code>CHOWN<\/code> creates a whole new layer, doubling the size of the files being changed. Instead, we use the <code>--chown<\/code> flag in the <code>COPY<\/code> command.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"The_Ghost_of_Dependencies_Past\"><\/span>The Ghost of Dependencies Past<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>The original image had <code>build-essential<\/code>, <code>git<\/code>, <code>curl<\/code>, and <code>nodejs<\/code> all sitting in the final production image. Why? Because the developer needed them to build the app. But the app doesn&#8217;t need <code>gcc<\/code> to <em>run<\/em>. It doesn&#8217;t need <code>git<\/code> to query a database.<\/p>\n<p>This is where multi-stage builds come in. It\u2019s the single most effective tool in the infrastructure archaeologist&#8217;s kit for removing the bloat of &#8220;move fast and break things&#8221; culture. We use a &#8220;builder&#8221; stage to compile our wheels and install our dependencies, and then we copy only the finished products into a &#8220;runner&#8221; stage.<\/p>\n<pre class=\"codehilite\"><code class=\"language-dockerfile\"># Stage 1: The Builder\nFROM python:3.11-slim-bookworm AS builder\n\nWORKDIR \/build\nRUN apt-get update &amp;&amp; apt-get install -y --no-install-recommends build-essential libpq-dev\nCOPY requirements.txt .\nRUN pip install --user --no-cache-dir -r requirements.txt\n\n# Stage 2: The Runner\nFROM python:3.11-slim-bookworm AS runner\nWORKDIR \/app\nRUN groupadd -g 10001 appuser &amp;&amp; useradd -u 10000 -g appuser appuser\nCOPY --from=builder --chown=appuser:appuser \/root\/.local \/home\/appuser\/.local\nCOPY --chown=appuser:appuser . .\nUSER appuser\nENV PATH=\/home\/appuser\/.local\/bin:$PATH\n<\/code><\/pre>\n<p>By doing this, we leave the <code>build-essential<\/code> (hundreds of megabytes) in the discarded builder stage. The final image only contains the Python runtime and the compiled libraries. This is how you turn a 3.5GB disaster into a 200MB professional artifact. Adhering to this docker best practice regarding layer squashing and stage separation is what separates the engineers from the script kiddies.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"The_PID_1_Zombie_Apocalypse\"><\/span>The PID 1 Zombie Apocalypse<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>When you run <code>CMD [\"python3\", \"app.py\"]<\/code>, Python becomes PID 1 inside the container. This is a problem. In Unix-like systems, PID 1 (init) has two special responsibilities: reaping &#8220;zombie&#8221; child processes and handling signals like <code>SIGTERM<\/code> or <code>SIGINT<\/code>.<\/p>\n<p>Python, like most high-level runtimes, isn&#8217;t designed to be an init system. If your app spawns subprocesses and they die, they stay as zombies because Python doesn&#8217;t know it&#8217;s supposed to wait on them. More importantly, when you run <code>docker stop<\/code>, Docker sends <code>SIGTERM<\/code> to PID 1. If Python hasn&#8217;t explicitly registered a signal handler for <code>SIGTERM<\/code>, the kernel will ignore the signal. Docker waits 10 seconds, gets annoyed, and sends <code>SIGKILL<\/code>, which is like pulling the power plug. Your app doesn&#8217;t close database connections, it doesn&#8217;t flush logs, it just dies.<\/p>\n<p>The solution is <code>tini<\/code>. It\u2019s a tiny init binary that handles these chores for you.<\/p>\n<pre class=\"codehilite\"><code class=\"language-dockerfile\">RUN apt-get update &amp;&amp; apt-get install -y tini\nENTRYPOINT [&quot;\/usr\/bin\/tini&quot;, &quot;--&quot;]\nCMD [&quot;python3&quot;, &quot;app.py&quot;]\n<\/code><\/pre>\n<p>Now, <code>tini<\/code> is PID 1. It catches <code>SIGTERM<\/code>, passes it to Python, and Python shuts down gracefully. It also reaps any zombies. It\u2019s a simple fix that prevents a world of corrupted data and orphaned processes.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"The_Cache_Mount_Salvation\"><\/span>The Cache Mount Salvation<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>One of the biggest complaints from the &#8220;move fast&#8221; crowd is that Docker builds are slow. So they skip the cleanup steps to &#8220;leverage&#8221; (God, I hate that word) the cache. But they\u2019re doing it wrong.<\/p>\n<p>Modern Docker (BuildKit) supports cache mounts. This allows you to persist a cache directory (like the pip cache or the npm cache) between builds without actually including that cache in the final image layers.<\/p>\n<pre class=\"codehilite\"><code class=\"language-dockerfile\"># Using BuildKit cache mounts\nRUN --mount=type=cache,target=\/root\/.cache\/pip \\\n    pip install -r requirements.txt\n<\/code><\/pre>\n<p>This is the holy grail. You get the speed of a local <code>pip install<\/code> because the <code>\/root\/.cache\/pip<\/code> directory is persisted on the build host, but the resulting image layer only contains the installed packages, not the cached <code>.whl<\/code> files. This is another instance where ignoring every docker best practice ever written by someone with more than two weeks of experience leads to the kind of 3:14 AM phone call I had to endure.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"The_dockerignore_Exclusion_Zone\"><\/span>The .dockerignore Exclusion Zone<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>The &#8220;Crime Scene&#8221; Dockerfile used <code>ADD . \/app<\/code>. This is a disaster. <code>ADD<\/code> is more powerful than <code>COPY<\/code>\u2014it can extract tarballs and fetch URLs\u2014which is exactly why you shouldn&#8217;t use it unless you need those specific features. <code>COPY<\/code> is predictable.<\/p>\n<p>But the real sin was copying <em>everything<\/em>. The <code>.git<\/code> directory in the project I found was 1.2GB. It contains the entire history of the project, every deleted binary, every large asset ever committed. It has no place in a production container.<\/p>\n<p>We need a <code>.dockerignore<\/code> file. It\u2019s not optional.<\/p>\n<pre class=\"codehilite\"><code class=\"language-text\"># .dockerignore\n.git\n.gitignore\nnode_modules\n*.pyc\n__pycache__\nsecrets.env\ntests\/\ndocs\/\n.vscode\/\n<\/code><\/pre>\n<p>By excluding these, the build context sent to the Docker daemon drops from gigabytes to kilobytes. The build starts faster, the layers are smaller, and you aren&#8217;t accidentally shipping your <code>secrets.env<\/code> to a public registry.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"The_Surgical_Reconstruction_The_Final_Artifact\"><\/span>The Surgical Reconstruction: The Final Artifact<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>After hours of cleaning up the mess, I produced a Dockerfile that wouldn&#8217;t make the server choke and die. It was lean, it was secure, and it was documented.<\/p>\n<pre class=\"codehilite\"><code class=\"language-dockerfile\"># --- STAGE 1: BUILDER ---\n# Use a specific, stable version. No 'latest' allowed in this house.\nFROM python:3.11-slim-bookworm AS builder\n\n# Set environment variables to make Python\/Apt behave in a container\nENV PYTHONDONTWRITEBYTECODE=1 \\\n    PYTHONUNBUFFERED=1 \\\n    DEBIAN_FRONTEND=noninteractive\n\nWORKDIR \/build\n\n# Install only what is needed for the build phase\n# Chaining and cleaning up in one layer to keep the image slim\nRUN apt-get update &amp;&amp; apt-get install -y --no-install-recommends \\\n    build-essential \\\n    libpq-dev \\\n    &amp;&amp; rm -rf \/var\/lib\/apt\/lists\/*\n\n# Use BuildKit cache mounts for faster, cleaner builds\nCOPY requirements.txt .\nRUN --mount=type=cache,target=\/root\/.cache\/pip \\\n    pip install --user --no-cache-dir -r requirements.txt\n\n\n# --- STAGE 2: RUNNER ---\nFROM python:3.11-slim-bookworm AS runner\n\n# Tini is the init process we deserve\nRUN apt-get update &amp;&amp; apt-get install -y --no-install-recommends \\\n    tini \\\n    libpq5 \\\n    &amp;&amp; rm -rf \/var\/lib\/apt\/lists\/*\n\nWORKDIR \/app\n\n# Create a non-root user with a specific UID\/GID for predictable permissions\nRUN groupadd -g 10001 appuser &amp;&amp; \\\n    useradd -u 10000 -g appuser --create-home --shell \/sbin\/nologin appuser\n\n# Copy only the installed site-packages from the builder\nCOPY --from=builder --chown=appuser:appuser \/root\/.local \/home\/appuser\/.local\n\n# Copy the application code, ensuring the non-root user owns it\n# The .dockerignore ensures we don't bring in the garbage\nCOPY --chown=appuser:appuser . .\n\n# Switch to the non-root user\nUSER appuser\n\n# Update PATH to include the local bin where pip installed things\nENV PATH=\/home\/appuser\/.local\/bin:$PATH \\\n    PYTHONUNBUFFERED=1\n\n# Expose only the necessary port\nEXPOSE 8080\n\n# Use Tini to manage signals and zombies\nENTRYPOINT [&quot;\/usr\/bin\/tini&quot;, &quot;--&quot;]\n\n# Run the application\nCMD [&quot;python&quot;, &quot;app.py&quot;]\n<\/code><\/pre>\n<p>I ran the build. I watched the output.<\/p>\n<pre class=\"codehilite\"><code class=\"language-bash\">$ docker build -t optimized-app:1.0.2 .\n...\n=&gt; exporting to image                                            0.2s\n=&gt; =&gt; exporting layers                                           0.2s\n=&gt; =&gt; writing image sha256:e9c1...                               0.0s\n=&gt; =&gt; naming to docker.io\/library\/optimized-app:1.0.2            0.0s\n\n$ docker images\nREPOSITORY      TAG       IMAGE ID       SIZE\noptimized-app   1.0.2     e9c1d2e3f4g5   184MB\nbloated-old     latest    a1b2c3d4e5f6   3.52GB\n<\/code><\/pre>\n<p>From 3.52GB to 184MB. A 95% reduction in size. The deployment time dropped from six minutes to twelve seconds. The memory footprint on the node stabilized because we weren&#8217;t running a dozen unnecessary background processes and the Python runtime was properly configured.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"The_Aftermath\"><\/span>The Aftermath<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>I updated the <code>docker-compose.yml<\/code> too. No more <code>privileged: true<\/code>. No more mounting the entire project directory in production. We use read-only filesystems where possible to prevent any compromised process from writing to the container&#8217;s root.<\/p>\n<pre class=\"codehilite\"><code class=\"language-yaml\">services:\n  web:\n    image: optimized-app:1.0.2\n    read_only: true\n    tmpfs:\n      - \/tmp\n      - \/home\/appuser\/.local\/\n    security_opt:\n      - no-new-privileges:true\n    cap_drop:\n      - ALL\n    cap_add:\n      - NET_BIND_SERVICE\n    deploy:\n      resources:\n        limits:\n          cpus: '0.5'\n          memory: 512M\n<\/code><\/pre>\n<p>This is what infrastructure work actually looks like. It\u2019s not about the &#8220;vibrant&#8221; new tools or &#8220;fostering&#8221; a culture of &#8220;seamless&#8221; delivery. It\u2019s about the gritty, boring work of making sure your systems don&#8217;t fall over because someone was too lazy to write a <code>.dockerignore<\/code> file.<\/p>\n<p>The junior developer came in the next morning, saw the PR, and asked why I &#8220;wasted&#8221; so much time on the Dockerfile when the old one &#8220;worked fine on his machine.&#8221; I didn&#8217;t answer. I just pointed at the monitoring logs from 3:14 AM and went to get more coffee.<\/p>\n<p>I\u2019ve been doing this for fifteen years. I\u2019ve seen the &#8220;move fast and break things&#8221; crowd break everything from startups to Fortune 500s. They move fast because they aren&#8217;t carrying the weight of their own mistakes\u2014people like me are. Containers aren&#8217;t magic. They are just fancy chroot jails. If you don&#8217;t treat them with the respect that Linux kernel primitives deserve, they will turn on you.<\/p>\n<p>This is why following a docker best practice isn&#8217;t a suggestion; it&#8217;s a survival requirement. If you want to play with the big boys in production, stop shipping your <code>.git<\/code> folders and start thinking about PID 1. Now, if you&#8217;ll excuse me, I have another server to exhume. Someone just tried to deploy a 5GB Machine Learning model inside a Node.js container using <code>FROM fedora:latest<\/code>.<\/p>\n<p>God help us all.<\/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\/10-essential-javascript-best-practices-for-clean-code\/\">10 Essential Javascript Best Practices For Clean Code<\/a><\/li>\n<li><a href=\"https:\/\/itsupportwale.com\/blog\/10-essential-aws-best-practices-for-cloud-optimization\/\">10 Essential Aws Best Practices For Cloud Optimization<\/a><\/li>\n<li><a href=\"https:\/\/itsupportwale.com\/blog\/docker-compose-guide\/\">Docker Compose Guide<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>I found the body of the production server at 3:14 AM, smothered by a 3.5GB image that had no business existing in a civilized society. The monitoring dashboard wasn&#8217;t just red; it was a screaming, digital hemorrhage. Disk pressure had triggered a death spiral. The kubelet, in its infinite, mechanical wisdom, tried to garbage collect &#8230; <a title=\"Docker Best Practices: Optimize Your Container Workflow\" class=\"read-more\" href=\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/\" aria-label=\"Read more  on Docker Best Practices: Optimize Your Container Workflow\">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-4847","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>Docker Best Practices: Optimize Your Container Workflow - 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\/docker-best-practices-optimize-your-container-workflow\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Docker Best Practices: Optimize Your Container Workflow - ITSupportWale\" \/>\n<meta property=\"og:description\" content=\"I found the body of the production server at 3:14 AM, smothered by a 3.5GB image that had no business existing in a civilized society. The monitoring dashboard wasn&#8217;t just red; it was a screaming, digital hemorrhage. Disk pressure had triggered a death spiral. The kubelet, in its infinite, mechanical wisdom, tried to garbage collect ... Read more\" \/>\n<meta property=\"og:url\" content=\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/\" \/>\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-07-30T16:43:53+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=\"13 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/\"},\"author\":{\"name\":\"Techie\",\"@id\":\"https:\/\/itsupportwale.com\/blog\/#\/schema\/person\/8c5a2b3d36396e0a8fd91ec8242fd46d\"},\"headline\":\"Docker Best Practices: Optimize Your Container Workflow\",\"datePublished\":\"2026-07-30T16:43:53+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/\"},\"wordCount\":1727,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/itsupportwale.com\/blog\/#organization\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/\",\"url\":\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/\",\"name\":\"Docker Best Practices: Optimize Your Container Workflow - ITSupportWale\",\"isPartOf\":{\"@id\":\"https:\/\/itsupportwale.com\/blog\/#website\"},\"datePublished\":\"2026-07-30T16:43:53+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/itsupportwale.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Docker Best Practices: Optimize Your Container Workflow\"}]},{\"@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":"Docker Best Practices: Optimize Your Container Workflow - 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\/docker-best-practices-optimize-your-container-workflow\/","og_locale":"en_US","og_type":"article","og_title":"Docker Best Practices: Optimize Your Container Workflow - ITSupportWale","og_description":"I found the body of the production server at 3:14 AM, smothered by a 3.5GB image that had no business existing in a civilized society. The monitoring dashboard wasn&#8217;t just red; it was a screaming, digital hemorrhage. Disk pressure had triggered a death spiral. The kubelet, in its infinite, mechanical wisdom, tried to garbage collect ... Read more","og_url":"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/","og_site_name":"ITSupportWale","article_publisher":"https:\/\/www.facebook.com\/Itsupportwale-298547177495978","article_published_time":"2026-07-30T16:43:53+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":"13 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/#article","isPartOf":{"@id":"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/"},"author":{"name":"Techie","@id":"https:\/\/itsupportwale.com\/blog\/#\/schema\/person\/8c5a2b3d36396e0a8fd91ec8242fd46d"},"headline":"Docker Best Practices: Optimize Your Container Workflow","datePublished":"2026-07-30T16:43:53+00:00","mainEntityOfPage":{"@id":"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/"},"wordCount":1727,"commentCount":0,"publisher":{"@id":"https:\/\/itsupportwale.com\/blog\/#organization"},"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/","url":"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/","name":"Docker Best Practices: Optimize Your Container Workflow - ITSupportWale","isPartOf":{"@id":"https:\/\/itsupportwale.com\/blog\/#website"},"datePublished":"2026-07-30T16:43:53+00:00","breadcrumb":{"@id":"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-your-container-workflow\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/itsupportwale.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Docker Best Practices: Optimize Your Container Workflow"}]},{"@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\/4847","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=4847"}],"version-history":[{"count":0,"href":"https:\/\/itsupportwale.com\/blog\/wp-json\/wp\/v2\/posts\/4847\/revisions"}],"wp:attachment":[{"href":"https:\/\/itsupportwale.com\/blog\/wp-json\/wp\/v2\/media?parent=4847"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/itsupportwale.com\/blog\/wp-json\/wp\/v2\/categories?post=4847"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/itsupportwale.com\/blog\/wp-json\/wp\/v2\/tags?post=4847"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}