<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ docker images - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ docker images - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Tue, 28 Jul 2026 09:15:37 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/docker-images/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Build Slim and Fast Docker Images with Multi-Stage Builds ]]>
                </title>
                <description>
                    <![CDATA[ Apps don’t stay simple forever. More features mean more dependencies, slower builds, and heavier Docker images. That’s where things start to hurt. Docker helps, but without the right setup, your builds can quickly get bloated. Multi-stage builds make... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-slim-fast-docker-images-with-multi-stage-builds/</link>
                <guid isPermaLink="false">6824b1638a2b26e9d2d0fe86</guid>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ docker images ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Dockerfile ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Daniel Adeboye ]]>
                </dc:creator>
                <pubDate>Wed, 14 May 2025 15:06:11 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1747235146559/0bce7dc3-0abe-4241-a188-1c05c773e810.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Apps don’t stay simple forever. More features mean more dependencies, slower builds, and heavier Docker images. That’s where things start to hurt.</p>
<p>Docker helps, but without the right setup, your builds can quickly get bloated.</p>
<p>Multi-stage builds make things smoother by keeping your images fast, clean, and production-ready. In this guide, you'll learn how to use them to supercharge your Docker workflow.</p>
<p>Let’s get into it.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To follow this guide, you should have:</p>
<ul>
<li><p>Docker installed and running</p>
</li>
<li><p>Basic understanding of Docker</p>
</li>
<li><p>Some Python knowledge (or any language, really)</p>
</li>
<li><p>Familiarity with the terminal</p>
</li>
</ul>
<h2 id="heading-heres-what-well-cover"><strong>Here's what we'll cover:</strong></h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-what-are-docker-images">What are Docker Images?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-implement-multi-stage-builds">How to Implement Multi-Stage Builds</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-chunky-single-stage-build">The Chunky Single-Stage Build</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-when-to-use-multi-stage-builds">When to Use Multi-Stage Builds</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-what-are-docker-images">What are Docker Images?</h2>
<p>Before we dive into optimization, let’s quickly get clear on what Docker images actually are.</p>
<p>A Docker image is a lightweight, standalone package that has everything your app needs to run – code, dependencies, environment variables, and config files. Think of it as a snapshot of your app, ready to spin up anywhere.</p>
<p>When you run an image, Docker turns it into a container: a self-contained environment that behaves the same on your machine, in staging, or in production. That consistency is a huge win for development and deployment.</p>
<p>Now that we’ve got the basics, let’s talk about making those images smaller and faster.</p>
<h2 id="heading-how-to-implement-multi-stage-builds"><strong>How to Implement Multi-Stage Builds</strong></h2>
<p>Let’s get hands-on by creating a basic Flask app and using a multi-stage build to keep our Docker image slim.</p>
<h3 id="heading-step-1-create-apppyhttpapppy">Step 1: Create <a target="_blank" href="http://app.py"><code>app.py</code></a></h3>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> flask <span class="hljs-keyword">import</span> Flask

app = Flask(__name__)

<span class="hljs-meta">@app.route('/')</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">hello</span>():</span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"Hello, Docker Multi-stage Builds! 🐳"</span>

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">'__main__'</span>:
    app.run(host=<span class="hljs-string">'0.0.0.0'</span>, port=<span class="hljs-number">5000</span>)
</code></pre>
<h3 id="heading-step-2-install-and-save-dependencies">Step 2: Install and save dependencies</h3>
<p>Install Flask and Gunicorn using pip:</p>
<pre><code class="lang-bash">pip install flask gunicorn
</code></pre>
<p>Then freeze your environment into a <code>requirements.txt</code> file:</p>
<pre><code class="lang-bash">pip freeze &gt; requirements.txt
</code></pre>
<p>This file is what Docker will use to install dependencies inside your container.</p>
<h3 id="heading-step-3-create-the-multi-stage-dockerfile">Step 3: Create the multi-stage <code>Dockerfile</code></h3>
<pre><code class="lang-docker"><span class="hljs-comment"># Stage 1: Build Stage</span>
<span class="hljs-keyword">FROM</span> python:<span class="hljs-number">3.9</span>-slim AS builder

<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>

<span class="hljs-keyword">COPY</span><span class="bash"> requirements.txt .</span>

<span class="hljs-keyword">RUN</span><span class="bash"> python -m venv /opt/venv &amp;&amp; \\
    . /opt/venv/bin/activate &amp;&amp; \\
    pip install --no-cache-dir -r requirements.txt</span>

<span class="hljs-comment"># Stage 2: Production Stage</span>
<span class="hljs-keyword">FROM</span> python:<span class="hljs-number">3.9</span>-slim

<span class="hljs-keyword">COPY</span><span class="bash"> --from=builder /opt/venv /opt/venv</span>

<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>

<span class="hljs-keyword">COPY</span><span class="bash"> . .</span>

<span class="hljs-keyword">ENV</span> PATH=<span class="hljs-string">"/opt/venv/bin/:$PATH"</span>

<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">5000</span>

<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"gunicorn"</span>, <span class="hljs-string">"--bind"</span>, <span class="hljs-string">"0.0.0.0:5000"</span>, <span class="hljs-string">"app:app"</span>]</span>
</code></pre>
<p>In the Dockerfile above, we’ve defined both a development and a production stage for our application. The first stage, the <strong>Build Stage</strong>, uses the <code>python:3.9-slim</code> base image, sets up a working directory, adds all the necessary files, and creates a virtual environment. All dependencies are installed inside that virtual environment.</p>
<p>In the <strong>Production Stage</strong>, we again start from <code>python:3.9-slim</code>, but this time we copy only the virtual environment from the build stage along with the application code. Then we configure the environment to use that virtual environment and run the app using Gunicorn.</p>
<p>Now, in a multi-stage build, you can experiment with using different Python versions across stages – but here’s why I didn’t go that route:</p>
<ul>
<li><p>Some packages may have different dependencies, depending on the Python version.</p>
</li>
<li><p>My <code>requirements.txt</code> file contains version-specific dependencies, so sticking to the same Python version across both stages helps avoid compatibility issues.</p>
</li>
</ul>
<p>Once the multi-stage Dockerfile is ready, go ahead and build the images. You’ll clearly see the size difference.</p>
<h3 id="heading-step-4-build-and-run-your-image">Step 4: Build and run your image</h3>
<p>To build and run your image container, use the following command:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Build the image</span>
docker build -t my-python-app .

<span class="hljs-comment"># Run the container</span>
docker run -p 5000:5000 my-python-app
</code></pre>
<p>If everything works correctly, your Flask app should now be live at <a target="_blank" href="http://localhost:5000"><code>http://localhost:5000</code></a> in your browser.</p>
<p>You’ll know your build succeeded when Docker completes without errors and starts the container. You should see terminal logs from Gunicorn indicating the app is up and running.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1746875902903/9e8348ac-d21c-4371-bb42-e514457a12ff.png" alt="9e8348ac-d21c-4371-bb42-e514457a12ff" class="image--center mx-auto" width="1964" height="788" loading="lazy"></p>
<h2 id="heading-the-chunky-single-stage-build">The Chunky Single-Stage Build</h2>
<p>Let’s compare with a traditional one-stage Docker build that includes everything in one go:</p>
<pre><code class="lang-docker"><span class="hljs-keyword">FROM</span> python:<span class="hljs-number">3.9</span>-slim

<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>

<span class="hljs-keyword">RUN</span><span class="bash"> apt-get update &amp;&amp; apt-get install -y \\
    build-essential \\
    python3-dev \\
    gcc \\
    &amp;&amp; rm -rf /var/lib/apt/lists/*</span>

<span class="hljs-keyword">COPY</span><span class="bash"> requirements.txt .</span>

<span class="hljs-keyword">RUN</span><span class="bash"> python -m venv /opt/venv</span>
<span class="hljs-keyword">ENV</span> PATH=<span class="hljs-string">"/opt/venv/bin:$PATH"</span>

<span class="hljs-keyword">RUN</span><span class="bash"> pip install --no-cache-dir -r requirements.txt</span>

<span class="hljs-keyword">COPY</span><span class="bash"> . .</span>

<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">5000</span>

<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"gunicorn"</span>, <span class="hljs-string">"--bind"</span>, <span class="hljs-string">"0.0.0.0:5000"</span>, <span class="hljs-string">"app:app"</span>]</span>
</code></pre>
<p>The Dockerfile above uses a straightforward build process: it starts from the <code>python:3.9-slim</code> image, sets a working directory, installs system dependencies, creates a virtual environment, installs Python packages, copies over the app code, exposes port 5000, and runs the app using Gunicorn. This kind of Dockerfile is common and works fine, but it can lead to unnecessarily large and bloated images.</p>
<p>Let’s build our image to compare the size with that of the multi-stage build:</p>
<pre><code class="lang-bash">docker build -t my-chunky-app .
</code></pre>
<p>You’ll notice that this Dockerfile takes longer to build compared to the previous one, which was much faster.</p>
<p>Before we continue, confirm your Docker image was successfully built.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1746886030667/5b83915e-b5b5-4927-9981-f35dad8fb1ff.png" alt="5b83915e-b5b5-4927-9981-f35dad8fb1ff" class="image--center mx-auto" width="2048" height="760" loading="lazy"></p>
<p>Now, let’s compare build sizes:</p>
<pre><code class="lang-bash">docker images | grep <span class="hljs-string">'my-'</span>
</code></pre>
<p>In case you're wondering why we used "my" to search for the images, it's because we named our Docker images <code>my-python-app</code> and <code>my-chunky-app</code>, so using "my" as a keyword makes it easy to filter them.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1746885989703/1e3667ad-b2fd-4fff-a0e2-31d4705582a7.png" alt="1e3667ad-b2fd-4fff-a0e2-31d4705582a7" class="image--center mx-auto" width="1334" height="76" loading="lazy"></p>
<p>The image above compares the build sizes of our single-stage and multi-stage Docker images. As you can see, <code>my-python-app</code> – the multi-stage build – is small and lightweight, while <code>my-chunky-app</code> is significantly larger. If you dig a bit deeper, you’ll notice that the multi-stage image built in just 1.2 seconds, whereas the single-stage one took a full 1 minute and 21 seconds. Pretty impressive difference, right?</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1746885947258/9584255b-c6aa-4d25-8a4a-e4a841808b57.png" alt="9584255b-c6aa-4d25-8a4a-e4a841808b57" class="image--center mx-auto" width="1882" height="290" loading="lazy"></p>
<p>In my opinion, these are solid reasons to use a multi-stage build – but it's not always necessary. There are cases where a single-stage build makes more sense. Let’s take a look at those.</p>
<h2 id="heading-when-to-use-multi-stage-builds">When to Use Multi-Stage Builds</h2>
<p><strong>Use multi-stage builds if:</strong></p>
<ul>
<li><p>Your app needs build tools (for example, compilers, dev dependencies)</p>
</li>
<li><p>You want smaller, faster Docker images</p>
</li>
<li><p>You care about image security and performance</p>
</li>
</ul>
<p><strong>Use single-stage builds if:</strong></p>
<ul>
<li><p>You're just testing or prototyping</p>
</li>
<li><p>Your app is tiny and doesn’t need external tools</p>
</li>
<li><p>You’re still learning the basics</p>
</li>
</ul>
<p>Pick what fits your project’s scale and complexity.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Multi-stage builds are an easy win. They help keep your Docker images clean, fast, and secure – especially as your app grows.</p>
<p>Not every project needs them, but when you do, they make a big difference. So next time you're Dockerizing something serious, reach for multi-stage. Your future self will thank you.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
