{"id":4774,"date":"2026-04-29T22:18:17","date_gmt":"2026-04-29T16:48:17","guid":{"rendered":"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-and-secure-your-containers\/"},"modified":"2026-04-29T22:18:17","modified_gmt":"2026-04-29T16:48:17","slug":"docker-best-practices-optimize-and-secure-your-containers","status":"publish","type":"post","link":"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-and-secure-your-containers\/","title":{"rendered":"Docker Best Practices: Optimize and Secure Your Containers"},"content":{"rendered":"<p>It was 3:14 AM when the heap dump hit the fan.<\/p>\n<p>I was three hours into a deep REM cycle when the PagerDuty &#8220;EHEHEHEHE&#8221; alert ripped through my skull like a rusted chainsaw. I didn&#8217;t even have to look at the dashboard to know what was happening. The <code>payment-processor-v2<\/code> service, the one the &#8220;rockstar&#8221; developers pushed on Friday afternoon while I was busy fixing the staging ingress, had finally decided to commit ritual suicide.<\/p>\n<p>By 3:20 AM, I was staring at a Grafana dashboard that looked like a crime scene. Latency was spiking into the tens of seconds. The CPU usage on the worker nodes was pegged at 100%, but the actual throughput was near zero. Then came the dreaded <code>OOMKilled<\/code> events. One by one, the pods were being executed by the kernel like prisoners in a dystopian novel.<\/p>\n<pre class=\"codehilite\"><code class=\"language-bash\">$ kubectl get pods -n production\nNAME                                 READY   STATUS             RESTARTS   AGE\npayment-processor-v2-7f8d9b6-x4j2l   0\/1     CrashLoopBackOff   12         45m\npayment-processor-v2-7f8d9b6-z9k1p   0\/1     OOMKilled          8          42m\npayment-processor-v2-7f8d9b6-m2n5q   0\/1     CrashLoopBackOff   15         50m\n<\/code><\/pre>\n<p>I pulled the logs. It was a wall of garbage. But then I saw it\u2014the smoking gun.<\/p>\n<pre class=\"codehilite\"><code class=\"language-text\">2024-05-20T03:15:22.441Z ERROR: Failed to load configuration. \nError: Cannot find module '\/app\/config\/secrets.json'\n    at Function.Module._resolveFilename (node:internal\/modules\/cjs\/loader:1077:15)\n...\n2024-05-20T03:15:23.102Z INFO: Attempting to pull latest image...\n<\/code><\/pre>\n<p>The &#8220;latest&#8221; image. My blood ran cold. Some junior dev had configured the deployment to use <code>image: payment-processor:latest<\/code>. Overnight, a CI\/CD job had finished, pushed a broken build to the registry, and because the image pull policy was set to <code>Always<\/code>, the cluster dutifully pulled the poison pill and distributed it to every node. <\/p>\n<p>I spent the next 44 hours manually rolling back tags, purging bloated layers from our local registry that had hit 98% disk utilization, and rewriting Dockerfiles that were so poorly constructed they should have been considered a violation of the Geneva Convention. <\/p>\n<p>If you think you&#8217;re following docker best practices while still using <code>:latest<\/code> or running your processes as root, you\u2019re delusional. You\u2019re not a developer; you\u2019re a liability.<\/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-69f2a0f939980\" 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-69f2a0f939980\"  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-and-secure-your-containers\/#1_THE_AUTOPSY_OF_A_24GB_MONSTROSITY\" >1. THE AUTOPSY OF A 2.4GB MONSTROSITY<\/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-and-secure-your-containers\/#2_THE_RULES_OF_ENGAGEMENT_NON-NEGOTIABLE_MANDATES\" >2. THE RULES OF ENGAGEMENT: NON-NEGOTIABLE MANDATES<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-and-secure-your-containers\/#I_THOU_SHALT_PIN_THY_VERSIONS\" >I. THOU SHALT PIN THY VERSIONS<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-and-secure-your-containers\/#II_THOU_SHALT_NOT_RUN_AS_ROOT\" >II. THOU SHALT NOT RUN AS ROOT<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-and-secure-your-containers\/#III_THOU_SHALT_USE_MULTI-STAGE_BUILDS\" >III. THOU SHALT USE MULTI-STAGE BUILDS<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-and-secure-your-containers\/#IV_THOU_SHALT_RESPECT_THE_LAYER_CACHE\" >IV. THOU SHALT RESPECT THE LAYER CACHE<\/a><\/li><\/ul><\/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-and-secure-your-containers\/#3_THE_REFACTOR_TEARING_DOWN_THE_AMATEUR_HOUR\" >3. THE REFACTOR: TEARING DOWN THE AMATEUR HOUR<\/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-and-secure-your-containers\/#4_THE_HIDDEN_KILLER_DOCKERIGNORE\" >4. THE HIDDEN KILLER: .DOCKERIGNORE<\/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-and-secure-your-containers\/#5_VULNERABILITY_SCANNING_TRUST_BUT_VERIFY\" >5. VULNERABILITY SCANNING: TRUST BUT VERIFY<\/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-and-secure-your-containers\/#6_THE_LONG-TERM_SURVIVAL_PLAN\" >6. THE LONG-TERM SURVIVAL PLAN<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-11\" href=\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-and-secure-your-containers\/#Related_Articles\" >Related Articles<\/a><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"1_THE_AUTOPSY_OF_A_24GB_MONSTROSITY\"><\/span>1. THE AUTOPSY OF A 2.4GB MONSTROSITY<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Once the bleeding stopped and the service was stabilized on a pinned version, I went back to see how the &#8220;latest&#8221; image had grown to 2.4GB for a simple Node.js API. I ran <code>docker history<\/code> on the offending image. It was a horror show.<\/p>\n<pre class=\"codehilite\"><code class=\"language-bash\">$ docker history payment-processor:latest\nIMAGE          CREATED        CREATED BY                                      SIZE      COMMENT\n&lt;missing&gt;      2 hours ago    \/bin\/sh -c #(nop)  CMD [&quot;npm&quot; &quot;start&quot;]          0B        \n&lt;missing&gt;      2 hours ago    \/bin\/sh -c apt-get update &amp;&amp; apt-get install\u2026   850MB     \n&lt;missing&gt;      2 hours ago    \/bin\/sh -c #(nop) COPY dir:7e3... in \/app       1.2GB     \n&lt;missing&gt;      3 hours ago    \/bin\/sh -c npm install                          300MB     \n&lt;missing&gt;      4 hours ago    \/bin\/sh -c #(nop) FROM node:latest              1.1GB     \n<\/code><\/pre>\n<p>Look at that. <code>FROM node:latest<\/code>. That\u2019s the first mistake. On that particular day, <code>node:latest<\/code> resolved to a full Debian Bullseye image packed with build tools, compilers, and headers that have no business being in a production environment. <\/p>\n<p>Then, look at the <code>COPY<\/code> command. 1.2GB. Why? Because the developer didn&#8217;t bother with a <code>.dockerignore<\/code> file. They copied the <code>.git<\/code> folder, the local <code>node_modules<\/code>, the <code>tests<\/code> folder, and even a few 200MB heap dumps they had lying around in their local workspace.<\/p>\n<p>I ran a <code>docker inspect<\/code> to see the security posture. It was exactly what I expected: non-existent.<\/p>\n<pre class=\"codehilite\"><code class=\"language-bash\">$ docker inspect payment-processor:latest | grep -i &quot;user&quot;\n            &quot;User&quot;: &quot;&quot;,\n<\/code><\/pre>\n<p>Empty string. That means it\u2019s running as <code>root<\/code>. If an attacker finds a remote code execution (RCE) vulnerability in that Node.js app\u2014and given the state of the <code>package.json<\/code>, they will\u2014they aren&#8217;t just inside a container. They are <code>root<\/code> inside a container. From there, escaping to the host is just a matter of time and a few unpatched kernel vulnerabilities.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"2_THE_RULES_OF_ENGAGEMENT_NON-NEGOTIABLE_MANDATES\"><\/span>2. THE RULES OF ENGAGEMENT: NON-NEGOTIABLE MANDATES<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>If you want to survive in this industry without being the person who wakes me up at 3:00 AM, you will follow these rules. These aren&#8217;t suggestions. They are the baseline for professional-grade containerization.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"I_THOU_SHALT_PIN_THY_VERSIONS\"><\/span>I. THOU SHALT PIN THY VERSIONS<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Stop using <code>:latest<\/code>. Stop using <code>:major<\/code> versions like <code>:20<\/code>. You pin to the specific, immutable tag. If you\u2019re using Python, you use <code>python:3.12.1-slim-bookworm<\/code>. If you\u2019re using Node, you use <code>node:20.11.0-bookworm-slim<\/code>. <\/p>\n<p>Why? Because <code>node:20<\/code> might be <code>20.11.0<\/code> today and <code>20.12.0<\/code> tomorrow. If <code>20.12.0<\/code> introduces a regression in how it handles OpenSSL, your production environment just became a laboratory for unforced errors.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"II_THOU_SHALT_NOT_RUN_AS_ROOT\"><\/span>II. THOU SHALT NOT RUN AS ROOT<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>There is zero excuse for this. None. Most official images provide a non-privileged user (like the <code>node<\/code> user in the Node image). If they don&#8217;t, you create one.<\/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:10001\n<\/code><\/pre>\n<h3><span class=\"ez-toc-section\" id=\"III_THOU_SHALT_USE_MULTI-STAGE_BUILDS\"><\/span>III. THOU SHALT USE MULTI-STAGE BUILDS<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Your build tools (compilers, git, ssh-keys for private repos) do not belong in your final image. If I see <code>gcc<\/code> or <code>python3-dev<\/code> in a production image, I\u2019m revoking your merge rights. Multi-stage builds allow you to compile your binaries in a heavy image and then copy only the artifacts into a minimal, hardened runtime image.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"IV_THOU_SHALT_RESPECT_THE_LAYER_CACHE\"><\/span>IV. THOU SHALT RESPECT THE LAYER CACHE<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Docker builds are cached based on the order of operations. If you change a file, every subsequent layer must be rebuilt. If you <code>COPY . .<\/code> before you <code>RUN npm install<\/code>, you are invalidating your cache every time you change a comment in a README file. You are wasting CI minutes and my patience.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"3_THE_REFACTOR_TEARING_DOWN_THE_AMATEUR_HOUR\"><\/span>3. THE REFACTOR: TEARING DOWN THE AMATEUR HOUR<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Let\u2019s look at the &#8220;naive&#8221; Dockerfile that killed our cluster. It\u2019s a classic example of &#8220;it works on my machine&#8221; syndrome.<\/p>\n<p><strong>The Failure (Dockerfile.bad):<\/strong><\/p>\n<pre class=\"codehilite\"><code class=\"language-dockerfile\">FROM node:latest\nWORKDIR \/app\nCOPY . .\nRUN apt-get update &amp;&amp; apt-get install -y vim git curl\nRUN npm install\nEXPOSE 3000\nCMD [&quot;npm&quot;, &quot;start&quot;]\n<\/code><\/pre>\n<p>This is garbage. It\u2019s heavy, it\u2019s insecure, and it\u2019s slow. Now, let\u2019s apply docker best practices to rebuild this into something that won&#8217;t make me want to throw my laptop into a wood chipper.<\/p>\n<p><strong>The Professional Version (Dockerfile.good):<\/strong><\/p>\n<pre class=\"codehilite\"><code class=\"language-dockerfile\"># Stage 1: Build\nFROM node:20.11.0-bookworm-slim AS builder\n\n# Set build-time environment variables\nENV NODE_ENV=production\n\nWORKDIR \/build\n\n# Copy only the package files first to leverage layer caching\nCOPY package.json package-lock.json .\/\n\n# Install dependencies including devDependencies for build steps\n# We use npm ci for deterministic builds\nRUN npm ci\n\n# Copy the rest of the source code\nCOPY . .\n\n# Run build scripts (e.g., TypeScript compilation)\nRUN npm run build\n\n# Stage 2: Runtime\nFROM node:20.11.0-bookworm-slim\n\n# Labels for metadata - useful for automated cleanup and tracking\nLABEL maintainer=&quot;sre-team@company.com&quot;\nLABEL version=&quot;1.4.2&quot;\n\n# Set production environment\nENV NODE_ENV=production\nENV PORT=3000\n\nWORKDIR \/app\n\n# Create a non-root user and group\nRUN groupadd -r appgroup &amp;&amp; useradd -r -g appgroup -s \/sbin\/nologin appuser\n\n# Copy only the necessary artifacts from the builder stage\n# We copy the node_modules and the compiled dist folder\nCOPY --from=builder --chown=appuser:appgroup \/build\/dist .\/dist\nCOPY --from=builder --chown=appuser:appgroup \/build\/node_modules .\/node_modules\nCOPY --from=builder --chown=appuser:appgroup \/build\/package.json .\/package.json\n\n# Use a specific signal for graceful shutdown\nSTOPSIGNAL SIGTERM\n\n# Switch to the non-root user\nUSER appuser\n\n# Expose the port\nEXPOSE 3000\n\n# Use the exec form of CMD to ensure signals are passed to the process\n# We use 'node' directly, not 'npm start', to avoid zombie processes\nCMD [&quot;node&quot;, &quot;dist\/index.js&quot;]\n<\/code><\/pre>\n<p>Let\u2019s break down why this version is superior.<\/p>\n<ol>\n<li><strong>Base Image:<\/strong> We used <code>node:20.11.0-bookworm-slim<\/code>. It\u2019s based on Debian 12, it\u2019s patched, and it\u2019s small. We didn&#8217;t use Alpine because Node.js on Alpine (musl) can have weird performance edge cases with memory allocation that I don&#8217;t want to debug at 4:00 AM.<\/li>\n<li><strong>Layer Caching:<\/strong> We copied <code>package.json<\/code> and ran <code>npm ci<\/code> <em>before<\/em> copying the rest of the code. This means if you change a line of code but don&#8217;t change your dependencies, Docker skips the <code>npm ci<\/code> step entirely.<\/li>\n<li><strong>Multi-Stage:<\/strong> The final image doesn&#8217;t contain <code>git<\/code>, <code>vim<\/code>, or any of the build-time junk. It only contains the compiled code and the production dependencies.<\/li>\n<li><strong>Security:<\/strong> We created <code>appuser<\/code>. We used <code>--chown<\/code> during the <code>COPY<\/code> phase so the files are owned by the user running the process. We set a shell of <code>\/sbin\/nologin<\/code> so the user can&#8217;t even be used for an interactive session if someone breaks in.<\/li>\n<li><strong>Signal Handling:<\/strong> We used the &#8220;exec form&#8221; <code>[\"node\", \"dist\/index.js\"]<\/code>. If you use the &#8220;shell form&#8221; <code>CMD node dist\/index.js<\/code>, Docker runs your app as a sub-process of <code>\/bin\/sh<\/code>. When you try to stop the container, <code>\/bin\/sh<\/code> receives the <code>SIGTERM<\/code> but doesn&#8217;t pass it to your app. Your app keeps running until Docker loses patience and <code>SIGKILL<\/code>s it after 10 seconds. That\u2019s how you get corrupted databases and lost state.<\/li>\n<\/ol>\n<h2><span class=\"ez-toc-section\" id=\"4_THE_HIDDEN_KILLER_DOCKERIGNORE\"><\/span>4. THE HIDDEN KILLER: .DOCKERIGNORE<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>If you don&#8217;t have a <code>.dockerignore<\/code> file, you are essentially uploading your entire hard drive to the Docker daemon every time you build. <\/p>\n<p>I checked the build logs from the incident. The &#8220;context&#8221; sent to the Docker daemon was 1.8GB. <\/p>\n<pre class=\"codehilite\"><code class=\"language-bash\">$ docker build -t payment-processor:latest .\nSending build context to Docker daemon  1.842GB\n<\/code><\/pre>\n<p>That 1.8GB has to be compressed and sent over the socket. It slows down builds, bloats the layers, and leaks secrets. Your <code>.dockerignore<\/code> should be the first thing you write.<\/p>\n<p><strong>Example .dockerignore:<\/strong><\/p>\n<pre class=\"codehilite\"><code class=\"language-text\">.git\n.gitignore\nnode_modules\nnpm-debug.log\nDockerfile\n.dockerignore\n.env\n.aws\n.ssh\ndist\ntests\ndocs\n*.md\n*.pdf\nheapdump.*.heapsnapshot\n<\/code><\/pre>\n<p>By excluding the <code>.git<\/code> directory alone, you often save hundreds of megabytes. By excluding <code>.env<\/code> and <code>.ssh<\/code>, you prevent yourself from becoming the next headline in a data breach report.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"5_VULNERABILITY_SCANNING_TRUST_BUT_VERIFY\"><\/span>5. VULNERABILITY SCANNING: TRUST BUT VERIFY<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>After I refactored the image, I ran a scan using <code>trivy<\/code>. This is what the &#8220;latest&#8221; image looked like:<\/p>\n<pre class=\"codehilite\"><code class=\"language-bash\">$ trivy image payment-processor:latest\nTotal: 482 (UNKNOWN: 5, LOW: 182, MEDIUM: 190, HIGH: 95, CRITICAL: 10)\n<\/code><\/pre>\n<p>Ten critical vulnerabilities. Ten open doors for any script kiddie with a Metasploit modules list. After the refactor to <code>bookworm-slim<\/code> and proper dependency management, the results were different:<\/p>\n<pre class=\"codehilite\"><code class=\"language-bash\">$ trivy image payment-processor:pro-v1.4.2\nTotal: 12 (LOW: 10, MEDIUM: 2, HIGH: 0, CRITICAL: 0)\n<\/code><\/pre>\n<p>Zero criticals. Zero highs. That\u2019s a production-ready image. If you aren&#8217;t running vulnerability scans in your CI pipeline, you are just waiting for the inevitable.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"6_THE_LONG-TERM_SURVIVAL_PLAN\"><\/span>6. THE LONG-TERM SURVIVAL PLAN<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Maintaining these standards is harder than setting them. Developers will complain that <code>slim<\/code> images are missing &#8220;useful tools&#8221; like <code>ping<\/code> or <code>netstat<\/code>. They will complain that multi-stage builds are &#8220;too complex.&#8221; They will try to sneak <code>:latest<\/code> back into the Helm charts because they are too lazy to update a version number.<\/p>\n<p>Here is how you stop them:<\/p>\n<ol>\n<li><strong>Admission Controllers:<\/strong> Use something like Kyverno or OPA Gatekeeper in your Kubernetes cluster. Set a policy that rejects any pod using an image with the <code>:latest<\/code> tag. If the deployment doesn&#8217;t have a pinned version, it doesn&#8217;t get scheduled. Period.<\/li>\n<li><strong>Hadolint:<\/strong> Integrate <code>hadolint<\/code> into your CI pipeline. It\u2019s a linter for Dockerfiles. If someone tries to use <code>apt-get update<\/code> without <code>rm -rf \/var\/lib\/apt\/lists\/*<\/code>, the build fails. If they don&#8217;t specify a version for the base image, the build fails.<\/li>\n<li><strong>Automated Dependency Updates:<\/strong> Use tools like Renovate or Dependabot. They will automatically open Pull Requests to update your base image tags and your <code>package.json<\/code> versions. This takes the &#8220;it&#8217;s too much work to stay updated&#8221; excuse off the table.<\/li>\n<li><strong>Ephemeral Debug Containers:<\/strong> When developers complain they can&#8217;t debug because the image is too small, teach them to use <code>kubectl debug<\/code>. It allows them to spin up a &#8220;sidecar&#8221; container with all the tools they want (like <code>nmap<\/code>, <code>tcpdump<\/code>, <code>curl<\/code>) that shares the process namespace of the production pod. They get their tools; I get my secure, slim image.<\/li>\n<\/ol>\n<p>I\u2019ve spent the last 48 hours cleaning up a mess that could have been avoided with ten minutes of forethought. I\u2019ve seen entire companies go offline because a single <code>docker build<\/code> command was run with too much ego and too little discipline. <\/p>\n<p>The &#8220;docker best&#8221; path isn&#8217;t the easy path. It requires you to actually understand how Linux works, how networking works, and how the kernel handles processes. But the alternative is a 3:00 AM phone call from me, and trust me, you don&#8217;t want to hear my voice when I haven&#8217;t slept.<\/p>\n<p>Now, if you&#8217;ll excuse me, I&#8217;m going to go find a dark room, a bottle of cheap bourbon, and a way to forget the sight of a 2.4GB container layer. Don&#8217;t break anything. If I see a <code>:latest<\/code> tag in the registry when I wake up, there will be blood.<\/p>\n<p>The cluster is stable. For now. But the OOM killer is always watching, and it has no mercy for the lazy. <\/p>\n<p><strong>The Final Word on Image Sizes:<\/strong><\/p>\n<pre class=\"codehilite\"><code class=\"language-bash\">$ ls -lh images.txt\n-rw-r--r--  1 sre-god  staff   2.4G May 20 03:15 payment-processor-latest.tar\n-rw-r--r--  1 sre-god  staff   184M May 22 11:00 payment-processor-optimized.tar\n<\/code><\/pre>\n<p>From 2.4GB to 184MB. That\u2019s not just &#8220;optimization.&#8221; That\u2019s the difference between a fragile toy and a professional tool. Build better, or get out of the way.<\/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\/tuning-nextcloud-for-better-performance\/\">Tuning Nextcloud For Better Performance<\/a><\/li>\n<li><a href=\"https:\/\/itsupportwale.com\/blog\/how-to-install-latest-php-7-3-on-ubuntu-18-04\/\">How To Install Latest Php 7 3 On Ubuntu 18 04<\/a><\/li>\n<li><a href=\"https:\/\/itsupportwale.com\/blog\/microsoft-launcher-preview-6-0-arrives-with-more-features\/\">Microsoft Launcher Preview 6 0 Arrives With More Features<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>It was 3:14 AM when the heap dump hit the fan. I was three hours into a deep REM cycle when the PagerDuty &#8220;EHEHEHEHE&#8221; alert ripped through my skull like a rusted chainsaw. I didn&#8217;t even have to look at the dashboard to know what was happening. The payment-processor-v2 service, the one the &#8220;rockstar&#8221; developers &#8230; <a title=\"Docker Best Practices: Optimize and Secure Your Containers\" class=\"read-more\" href=\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-and-secure-your-containers\/\" aria-label=\"Read more  on Docker Best Practices: Optimize and Secure 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-4774","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 and Secure 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\/docker-best-practices-optimize-and-secure-your-containers\/\" \/>\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 and Secure Your Containers - ITSupportWale\" \/>\n<meta property=\"og:description\" content=\"It was 3:14 AM when the heap dump hit the fan. I was three hours into a deep REM cycle when the PagerDuty &#8220;EHEHEHEHE&#8221; alert ripped through my skull like a rusted chainsaw. I didn&#8217;t even have to look at the dashboard to know what was happening. The payment-processor-v2 service, the one the &#8220;rockstar&#8221; developers ... Read more\" \/>\n<meta property=\"og:url\" content=\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-and-secure-your-containers\/\" \/>\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-04-29T16:48:17+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\/docker-best-practices-optimize-and-secure-your-containers\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-and-secure-your-containers\/\"},\"author\":{\"name\":\"Techie\",\"@id\":\"https:\/\/itsupportwale.com\/blog\/#\/schema\/person\/8c5a2b3d36396e0a8fd91ec8242fd46d\"},\"headline\":\"Docker Best Practices: Optimize and Secure Your Containers\",\"datePublished\":\"2026-04-29T16:48:17+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-and-secure-your-containers\/\"},\"wordCount\":1656,\"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-and-secure-your-containers\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-and-secure-your-containers\/\",\"url\":\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-and-secure-your-containers\/\",\"name\":\"Docker Best Practices: Optimize and Secure Your Containers - ITSupportWale\",\"isPartOf\":{\"@id\":\"https:\/\/itsupportwale.com\/blog\/#website\"},\"datePublished\":\"2026-04-29T16:48:17+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-and-secure-your-containers\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-and-secure-your-containers\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-and-secure-your-containers\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/itsupportwale.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Docker Best Practices: Optimize and Secure 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":"Docker Best Practices: Optimize and Secure 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\/docker-best-practices-optimize-and-secure-your-containers\/","og_locale":"en_US","og_type":"article","og_title":"Docker Best Practices: Optimize and Secure Your Containers - ITSupportWale","og_description":"It was 3:14 AM when the heap dump hit the fan. I was three hours into a deep REM cycle when the PagerDuty &#8220;EHEHEHEHE&#8221; alert ripped through my skull like a rusted chainsaw. I didn&#8217;t even have to look at the dashboard to know what was happening. The payment-processor-v2 service, the one the &#8220;rockstar&#8221; developers ... Read more","og_url":"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-and-secure-your-containers\/","og_site_name":"ITSupportWale","article_publisher":"https:\/\/www.facebook.com\/Itsupportwale-298547177495978","article_published_time":"2026-04-29T16:48:17+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\/docker-best-practices-optimize-and-secure-your-containers\/#article","isPartOf":{"@id":"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-and-secure-your-containers\/"},"author":{"name":"Techie","@id":"https:\/\/itsupportwale.com\/blog\/#\/schema\/person\/8c5a2b3d36396e0a8fd91ec8242fd46d"},"headline":"Docker Best Practices: Optimize and Secure Your Containers","datePublished":"2026-04-29T16:48:17+00:00","mainEntityOfPage":{"@id":"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-and-secure-your-containers\/"},"wordCount":1656,"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-and-secure-your-containers\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-and-secure-your-containers\/","url":"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-and-secure-your-containers\/","name":"Docker Best Practices: Optimize and Secure Your Containers - ITSupportWale","isPartOf":{"@id":"https:\/\/itsupportwale.com\/blog\/#website"},"datePublished":"2026-04-29T16:48:17+00:00","breadcrumb":{"@id":"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-and-secure-your-containers\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-and-secure-your-containers\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/itsupportwale.com\/blog\/docker-best-practices-optimize-and-secure-your-containers\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/itsupportwale.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Docker Best Practices: Optimize and Secure 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\/4774","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=4774"}],"version-history":[{"count":0,"href":"https:\/\/itsupportwale.com\/blog\/wp-json\/wp\/v2\/posts\/4774\/revisions"}],"wp:attachment":[{"href":"https:\/\/itsupportwale.com\/blog\/wp-json\/wp\/v2\/media?parent=4774"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/itsupportwale.com\/blog\/wp-json\/wp\/v2\/categories?post=4774"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/itsupportwale.com\/blog\/wp-json\/wp\/v2\/tags?post=4774"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}