<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Development Workflow &amp; Best Practices Archives - CodeCraft Diary</title>
	<atom:link href="https://codecraftdiary.com/category/development-workflow-best-practices/feed/" rel="self" type="application/rss+xml" />
	<link>https://codecraftdiary.com/category/development-workflow-best-practices/</link>
	<description></description>
	<lastBuildDate>Sun, 25 Oct 2026 11:57:57 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.3</generator>

<image>
	<url>https://codecraftdiary.com/wp-content/uploads/2025/10/cropped-IMG_3463-32x32.png</url>
	<title>Development Workflow &amp; Best Practices Archives - CodeCraft Diary</title>
	<link>https://codecraftdiary.com/category/development-workflow-best-practices/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>What is Docker? Stop Blaming “It Works on My Machine”</title>
		<link>https://codecraftdiary.com/2026/10/25/what-is-docker/</link>
					<comments>https://codecraftdiary.com/2026/10/25/what-is-docker/#respond</comments>
		
		<dc:creator><![CDATA[codecraftdiary]]></dc:creator>
		<pubDate>Sun, 25 Oct 2026 14:00:00 +0000</pubDate>
				<category><![CDATA[Development Workflow & Best Practices]]></category>
		<category><![CDATA[backend]]></category>
		<category><![CDATA[development]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[software-design]]></category>
		<category><![CDATA[workflow]]></category>
		<guid isPermaLink="false">https://codecraftdiary.com/?p=3403</guid>

					<description><![CDATA[<p>There is a particular kind of bug that every developer eventually gets tired of. The application works perfectly on your laptop. The tests pass, the API responds as expected, and everything looks good. Then someone else pulls the repository, runs the application, and suddenly nothing works. Maybe they have a different version of Python. Maybe [&#8230;]</p>
<p>The post <a href="https://codecraftdiary.com/2026/10/25/what-is-docker/">What is Docker? Stop Blaming “It Works on My Machine”</a> appeared first on <a href="https://codecraftdiary.com">CodeCraft Diary</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">There is a particular kind of bug that every developer eventually gets tired of.</p>



<p class="wp-block-paragraph">The application works perfectly on your laptop. The tests pass, the API responds as expected, and everything looks good. Then someone else pulls the repository, runs the application, and suddenly nothing works.</p>



<p class="wp-block-paragraph">Maybe they have a different version of Python. Maybe their local PostgreSQL installation is configured differently. Or perhaps a dependency was installed months ago and nobody remembers where it came from.</p>



<p class="wp-block-paragraph">You can spend an afternoon fixing these problems, or you can start making your development environment reproducible.</p>



<p class="wp-block-paragraph">That is where Docker comes in.</p>



<h2 class="wp-block-heading">What Docker Actually Does</h2>



<p class="wp-block-paragraph">Docker packages an application and the environment it needs into a portable unit called a container.</p>



<p class="wp-block-paragraph">Instead of asking every developer to install the correct runtime, configure the right dependencies, and manually set up supporting services, you describe the environment in code.</p>



<p class="wp-block-paragraph">There are three concepts worth understanding before writing your first Dockerfile:</p>



<ul class="wp-block-list">
<li><strong>Image:</strong> A template containing your application and the files it needs to run.</li>



<li><strong>Container:</strong> A running instance of an image.</li>



<li><strong>Dockerfile:</strong> The instructions Docker uses to build an image.</li>
</ul>



<p class="wp-block-paragraph">Think of an image as a recipe and a container as the meal prepared from it. You can create multiple containers from the same image without rebuilding the application from scratch each time.</p>



<p class="wp-block-paragraph">Containers are also different from virtual machines. They share the host operating system&#8217;s kernel rather than running a complete guest operating system for every application. That generally makes them lighter and quicker to start.</p>



<p class="wp-block-paragraph">The real benefit, however, is consistency. Your local environment becomes something you can describe, review, and reproduce instead of a collection of undocumented installation steps.</p>



<h2 class="wp-block-heading">Your First Docker Container</h2>



<p class="wp-block-paragraph">Before containerizing your own application, try running an existing image:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-bottom-disabled cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-Roboto-Mono.ttf" style="font-size:1rem;font-family:Code-Pro-Roboto-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(1 * 0.6 * 1rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>docker run --rm hello-world</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #DCDCAA">docker</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">run</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">--rm</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">hello-world</span></span></code></pre><span style="display:flex;align-items:flex-end;padding:10px;width:100%;justify-content:flex-start;background-color:#1E1E1E;color:#c7c7c7;font-size:12px;line-height:1;position:relative">Bash</span></div>



<p class="wp-block-paragraph">Docker downloads the <code>hello-world</code> image if it is not already available locally, creates a container, runs it, and removes the container when it exits.</p>



<p class="wp-block-paragraph">That single command demonstrates the basic workflow. You do not need to install the application inside your own operating system or configure a project-specific runtime.</p>



<p class="wp-block-paragraph">Once Docker is installed and running, you can use the same approach with more useful images, including databases, caches, and application runtimes.</p>



<p class="wp-block-paragraph">But running an existing image is only the beginning. Let&#8217;s build something of our own.</p>



<h2 class="wp-block-heading">Build a Small Python API</h2>



<p class="wp-block-paragraph">For this example, we will create a tiny HTTP API using Flask. You can follow the same general approach with a Node.js service, a Java backend, or another application stack.</p>



<p class="wp-block-paragraph">Create a directory with these two files:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-bottom-disabled cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-Roboto-Mono.ttf" style="font-size:1rem;font-family:Code-Pro-Roboto-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(1 * 0.6 * 1rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>docker-demo/
├── app.py
├── requirements.txt
└── Dockerfile</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #D4D4D4">docker-demo/</span></span>
<span class="line"><span style="color: #D4D4D4">├── app.py</span></span>
<span class="line"><span style="color: #D4D4D4">├── requirements.txt</span></span>
<span class="line"><span style="color: #D4D4D4">└── Dockerfile</span></span></code></pre><span style="display:flex;align-items:flex-end;padding:10px;width:100%;justify-content:flex-start;background-color:#1E1E1E;color:#c7c7c7;font-size:12px;line-height:1;position:relative">JSON</span></div>



<p class="wp-block-paragraph">First, add the following code to <code>app.py</code>:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-bottom-disabled cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-Roboto-Mono.ttf" style="font-size:1rem;font-family:Code-Pro-Roboto-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(1 * 0.6 * 1rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>from flask import Flask

app = Flask(__name__)

@app.get("/")
def home():
    return {"message": "Hello from Docker!"}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #569CD6">from</span><span style="color: #D4D4D4"> flask import Flask</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">app = Flask(__name__)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">@app.get(</span><span style="color: #CE9178">&quot;/&quot;</span><span style="color: #D4D4D4">)</span></span>
<span class="line"><span style="color: #D4D4D4">def home():</span></span>
<span class="line"><span style="color: #D4D4D4">    return {</span><span style="color: #CE9178">&quot;message&quot;</span><span style="color: #D4D4D4">: </span><span style="color: #CE9178">&quot;Hello from Docker!&quot;</span><span style="color: #D4D4D4">}</span></span></code></pre><span style="display:flex;align-items:flex-end;padding:10px;width:100%;justify-content:flex-start;background-color:#1E1E1E;color:#c7c7c7;font-size:12px;line-height:1;position:relative">Dockerfile</span></div>



<p class="wp-block-paragraph">Next, create <code>requirements.txt</code>:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-bottom-disabled cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-Roboto-Mono.ttf" style="font-size:1rem;font-family:Code-Pro-Roboto-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(1 * 0.6 * 1rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>Flask</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #D4D4D4">Flask</span></span></code></pre><span style="display:flex;align-items:flex-end;padding:10px;width:100%;justify-content:flex-start;background-color:#1E1E1E;color:#c7c7c7;font-size:12px;line-height:1;position:relative">Dockerfile</span></div>



<p class="wp-block-paragraph">Now comes the important part: the Dockerfile.</p>



<p class="wp-block-paragraph">Create a file named <code>Dockerfile</code> in the project root, without a file extension:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-bottom-disabled cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-Roboto-Mono.ttf" style="font-size:1rem;font-family:Code-Pro-Roboto-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * 1rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>FROM python:3.13-slim

WORKDIR /app

COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY app.py .

EXPOSE 8000

CMD &#91;"flask", "--app", "app", "run", "--host=0.0.0.0", "--port=8000"&#93;</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #569CD6">FROM</span><span style="color: #D4D4D4"> python:3.13-slim</span></span>
<span class="line"></span>
<span class="line"><span style="color: #569CD6">WORKDIR</span><span style="color: #D4D4D4"> /app</span></span>
<span class="line"></span>
<span class="line"><span style="color: #569CD6">COPY</span><span style="color: #D4D4D4"> requirements.txt .</span></span>
<span class="line"></span>
<span class="line"><span style="color: #569CD6">RUN</span><span style="color: #D4D4D4"> pip install --no-cache-dir -r requirements.txt</span></span>
<span class="line"></span>
<span class="line"><span style="color: #569CD6">COPY</span><span style="color: #D4D4D4"> app.py .</span></span>
<span class="line"></span>
<span class="line"><span style="color: #569CD6">EXPOSE</span><span style="color: #D4D4D4"> 8000</span></span>
<span class="line"></span>
<span class="line"><span style="color: #569CD6">CMD</span><span style="color: #D4D4D4"> &#91;</span><span style="color: #CE9178">&quot;flask&quot;</span><span style="color: #D4D4D4">, </span><span style="color: #CE9178">&quot;--app&quot;</span><span style="color: #D4D4D4">, </span><span style="color: #CE9178">&quot;app&quot;</span><span style="color: #D4D4D4">, </span><span style="color: #CE9178">&quot;run&quot;</span><span style="color: #D4D4D4">, </span><span style="color: #CE9178">&quot;--host=0.0.0.0&quot;</span><span style="color: #D4D4D4">, </span><span style="color: #CE9178">&quot;--port=8000&quot;</span><span style="color: #D4D4D4">&#93;</span></span></code></pre><span style="display:flex;align-items:flex-end;padding:10px;width:100%;justify-content:flex-start;background-color:#1E1E1E;color:#c7c7c7;font-size:12px;line-height:1;position:relative">Dockerfile</span></div>



<p class="wp-block-paragraph">Let&#8217;s break down what happens here.</p>



<p class="wp-block-paragraph"><code>FROM</code> selects the base image, which provides the Python runtime. <code>WORKDIR</code> establishes the working directory inside the image.</p>



<p class="wp-block-paragraph">We copy <code>requirements.txt</code> before copying the application code. This is intentional: Docker can reuse the dependency-installation layer when you change your Python code without changing your dependencies.</p>



<p class="wp-block-paragraph"><code>RUN</code> installs Flask, while <code>COPY</code> adds the application file. <code>EXPOSE</code> documents the port the application listens on, and <code>CMD</code> defines the default command used to start it.</p>



<p class="wp-block-paragraph">Notice the <code>0.0.0.0</code> address. The Flask development server must listen beyond the container&#8217;s loopback interface for connections forwarded from the host to reach it.</p>



<p class="wp-block-paragraph">One important distinction: this setup is intended for local development. Flask&#8217;s built-in development server is not the right server to expose a production application to real users.</p>



<h2 class="wp-block-heading">Build and Run It</h2>



<p class="wp-block-paragraph">Open a terminal in the <code>docker-demo</code> directory and build the image:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-bottom-disabled cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-Roboto-Mono.ttf" style="font-size:1rem;font-family:Code-Pro-Roboto-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(1 * 0.6 * 1rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>docker build -t docker-demo .</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #DCDCAA">docker</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">build</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">-t</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">docker-demo</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">.</span></span></code></pre><span style="display:flex;align-items:flex-end;padding:10px;width:100%;justify-content:flex-start;background-color:#1E1E1E;color:#c7c7c7;font-size:12px;line-height:1;position:relative">Bash</span></div>



<p class="wp-block-paragraph">The <code>-t</code> option gives the image a name. The final dot tells Docker to use the current directory as the build context.</p>



<p class="wp-block-paragraph">Once the build finishes, start a container:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-bottom-disabled cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-Roboto-Mono.ttf" style="font-size:1rem;font-family:Code-Pro-Roboto-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(1 * 0.6 * 1rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>docker run --rm -p 8000:8000 docker-demo</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #DCDCAA">docker</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">run</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">--rm</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">-p</span><span style="color: #D4D4D4"> </span><span style="color: #B5CEA8">8000</span><span style="color: #CE9178">:8000</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">docker-demo</span></span></code></pre><span style="display:flex;align-items:flex-end;padding:10px;width:100%;justify-content:flex-start;background-color:#1E1E1E;color:#c7c7c7;font-size:12px;line-height:1;position:relative">Bash</span></div>



<p class="wp-block-paragraph">Open <code>http://localhost:8000</code> in your browser. You should receive:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-bottom-disabled cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-Roboto-Mono.ttf" style="font-size:1rem;font-family:Code-Pro-Roboto-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(1 * 0.6 * 1rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>{
  "message": "Hello from Docker!"
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #D4D4D4">{</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #9CDCFE">&quot;message&quot;</span><span style="color: #D4D4D4">: </span><span style="color: #CE9178">&quot;Hello from Docker!&quot;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre><span style="display:flex;align-items:flex-end;padding:10px;width:100%;justify-content:flex-start;background-color:#1E1E1E;color:#c7c7c7;font-size:12px;line-height:1;position:relative">JSON</span></div>



<p class="wp-block-paragraph">The <code>-p 8000:8000</code> option maps port 8000 on your machine to port 8000 inside the container. Without this mapping, the service would not be accessible through that host port.</p>



<p class="wp-block-paragraph">You have now built an image from your own source code and run it as a container.</p>



<p class="wp-block-paragraph">That is the foundation. The next step is making the workflow convenient enough to use every day.</p>



<h2 class="wp-block-heading">Why Docker Compose Matters</h2>



<p class="wp-block-paragraph">Most backend applications need more than one process. A typical service might depend on PostgreSQL, Redis, or a message broker.</p>



<p class="wp-block-paragraph">You could start every container manually, remember its ports, configure environment variables, and repeat the process whenever you return to the project.</p>



<p class="wp-block-paragraph">Or you could describe the setup once in a Compose file.</p>



<p class="wp-block-paragraph">Create <code>compose.yaml</code> alongside your existing files:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-bottom-disabled cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-Roboto-Mono.ttf" style="font-size:1rem;font-family:Code-Pro-Roboto-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(1 * 0.6 * 1rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>services:
  api:
    build: .
    ports:
      - "8000:8000"</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #D4D4D4">services:</span></span>
<span class="line"><span style="color: #D4D4D4">  api:</span></span>
<span class="line"><span style="color: #D4D4D4">    build: .</span></span>
<span class="line"><span style="color: #D4D4D4">    ports:</span></span>
<span class="line"><span style="color: #D4D4D4">      - </span><span style="color: #CE9178">&quot;8000:8000&quot;</span></span></code></pre><span style="display:flex;align-items:flex-end;padding:10px;width:100%;justify-content:flex-start;background-color:#1E1E1E;color:#c7c7c7;font-size:12px;line-height:1;position:relative">JSON</span></div>



<p class="wp-block-paragraph">Now run:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-bottom-disabled cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-Roboto-Mono.ttf" style="font-size:1rem;font-family:Code-Pro-Roboto-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(1 * 0.6 * 1rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>docker compose up --build</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #DCDCAA">docker</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">compose</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">up</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">--build</span></span></code></pre><span style="display:flex;align-items:flex-end;padding:10px;width:100%;justify-content:flex-start;background-color:#1E1E1E;color:#c7c7c7;font-size:12px;line-height:1;position:relative">Bash</span></div>



<p class="wp-block-paragraph">Compose builds the image if necessary and starts the API. The configuration is stored in the repository, so another developer can start with the same basic command.</p>



<p class="wp-block-paragraph">When you are finished, press <code>Ctrl+C</code>. To remove the containers created by the Compose project, run:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-bottom-disabled cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-Roboto-Mono.ttf" style="font-size:1rem;font-family:Code-Pro-Roboto-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(1 * 0.6 * 1rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>docker compose down</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #DCDCAA">docker</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">compose</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">down</span></span></code></pre><span style="display:flex;align-items:flex-end;padding:10px;width:100%;justify-content:flex-start;background-color:#1E1E1E;color:#c7c7c7;font-size:12px;line-height:1;position:relative">Bash</span></div>



<p class="wp-block-paragraph">As the application grows, you can add database and cache services to the same file. Compose creates a network for the services, allowing them to communicate using their service names.</p>



<p class="wp-block-paragraph">For example, an API can connect to a PostgreSQL service using <code>db</code> as its hostname if the service is named <code>db</code>. There is no need to hardcode another container&#8217;s IP address.</p>



<p class="wp-block-paragraph">This is one of the reasons Compose is useful even for small projects: the development environment becomes part of the project rather than a separate set of instructions that everyone has to remember.</p>



<h2 class="wp-block-heading">Three Things Beginners Often Get Wrong</h2>



<p class="wp-block-paragraph">Getting an application running in Docker is relatively easy. Building a workflow that stays reliable takes a little more care.</p>



<p class="wp-block-paragraph"><strong>1. Assuming containers are permanent.</strong></p>



<p class="wp-block-paragraph">Containers can be stopped, removed, and recreated. Data written only to a container&#8217;s writable filesystem should not be treated as durable storage.</p>



<p class="wp-block-paragraph">Databases and other stateful services need appropriate persistent storage, such as named volumes, and production data needs a proper backup strategy.</p>



<p class="wp-block-paragraph"><strong>2. Copying the entire project into every image.</strong></p>



<p class="wp-block-paragraph">A careless build context can include local caches, Git metadata, environment files, and other unnecessary files.</p>



<p class="wp-block-paragraph">A <code>.dockerignore</code> file helps exclude them:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-bottom-disabled cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-Roboto-Mono.ttf" style="font-size:1rem;font-family:Code-Pro-Roboto-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(1 * 0.6 * 1rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>.git
.venv
__pycache__
*.pyc
.env</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #DCDCAA">.git</span></span>
<span class="line"><span style="color: #DCDCAA">.venv</span></span>
<span class="line"><span style="color: #DCDCAA">__pycache__</span></span>
<span class="line"><span style="color: #D4D4D4">*.pyc</span></span>
<span class="line"><span style="color: #DCDCAA">.env</span></span></code></pre><span style="display:flex;align-items:flex-end;padding:10px;width:100%;justify-content:flex-start;background-color:#1E1E1E;color:#c7c7c7;font-size:12px;line-height:1;position:relative">Bash</span></div>



<p class="wp-block-paragraph">This keeps the build context smaller and reduces the risk of accidentally including local secrets. Remember that <code>.dockerignore</code> is not a substitute for proper secret management.</p>



<p class="wp-block-paragraph"><strong>3. Treating a development container as production-ready.</strong></p>



<p class="wp-block-paragraph">A working container is not automatically a secure deployment.</p>



<p class="wp-block-paragraph">Production images need appropriate runtime configuration, dependency management, and security review. Use a production-grade application server, avoid running as root where practical, and choose trusted, maintained base images. Pinning versions and regularly rebuilding and scanning images also help make deployments more predictable.</p>



<p class="wp-block-paragraph">Docker does not remove the need for good engineering practices. It gives you a better foundation on which to apply them.</p>



<h2 class="wp-block-heading">The Real Value Is in the Workflow</h2>



<p class="wp-block-paragraph">Docker is often introduced as a deployment tool, but I think its biggest benefit appears much earlier in the development process.</p>



<p class="wp-block-paragraph">When the environment is defined in version control, onboarding becomes easier. Tests can run against a predictable setup. CI pipelines can build the same application image that you intend to deploy. Developers spend less time investigating differences between machines and more time working on the actual software.</p>



<p class="wp-block-paragraph">That does not mean every project needs a complicated container architecture. Start with one service, understand how the image is built, and learn how to inspect logs and troubleshoot networking before adding more moving parts.</p>



<p class="wp-block-paragraph">For a small backend project, a Dockerfile and a Compose file are often enough to make a noticeable difference.</p>



<p class="wp-block-paragraph">And if your team already cares about small pull requests, fast feedback, and reliable deployments, containerizing your development environment is a natural next step.</p>



<p class="wp-block-paragraph">The goal is not to put everything in Docker just because you can. The goal is to make the software easier to build, test, and run consistently — without another developer having to ask which version of Python you installed three months ago.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph"></p>
<p>The post <a href="https://codecraftdiary.com/2026/10/25/what-is-docker/">What is Docker? Stop Blaming “It Works on My Machine”</a> appeared first on <a href="https://codecraftdiary.com">CodeCraft Diary</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://codecraftdiary.com/2026/10/25/what-is-docker/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>The Feature Flag Graveyard: How a Safety Net Becomes Technical Debt</title>
		<link>https://codecraftdiary.com/2026/09/13/feature-flag-graveyard-technical-debt/</link>
					<comments>https://codecraftdiary.com/2026/09/13/feature-flag-graveyard-technical-debt/#respond</comments>
		
		<dc:creator><![CDATA[codecraftdiary]]></dc:creator>
		<pubDate>Sun, 13 Sep 2026 14:00:00 +0000</pubDate>
				<category><![CDATA[Development Workflow & Best Practices]]></category>
		<guid isPermaLink="false">https://codecraftdiary.com/?p=3369</guid>

					<description><![CDATA[<p>Every engineering team starts its feature flag journey with pure intentions. You read about Trunk-Based Development, continuous deployment, and decoupled releases. You wrap your new endpoint in an if (flags.isEnabled("new-checkout-v2")) block, merge it to main on a Tuesday morning, and release it to 5% of users. It works effortlessly. Rollbacks are no longer terrifying deploy [&#8230;]</p>
<p>The post <a href="https://codecraftdiary.com/2026/09/13/feature-flag-graveyard-technical-debt/">The Feature Flag Graveyard: How a Safety Net Becomes Technical Debt</a> appeared first on <a href="https://codecraftdiary.com">CodeCraft Diary</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Every engineering team starts its feature flag journey with pure intentions.</p>



<p class="wp-block-paragraph">You read about Trunk-Based Development, continuous deployment, and decoupled releases. You wrap your new endpoint in an <code>if (flags.isEnabled("new-checkout-v2"))</code> block, merge it to main on a Tuesday morning, and release it to 5% of users. It works effortlessly. Rollbacks are no longer terrifying deploy pipelines; they are a simple toggle in a UI. You feel like an engineering deity.</p>



<p class="wp-block-paragraph">Fast-forward twelve months.</p>



<p class="wp-block-paragraph">Your codebase contains 214 feature flags. Nobody remembers who created <code>enable-legacy-tax-calc-fix-final</code>, whether <code>temp-user-profile-v3</code> is fully rolled out, or why turning off <code>test-dark-mode-override</code> breaks authentication in the staging environment.</p>



<p class="wp-block-paragraph">You haven&#8217;t built a modern, resilient continuous delivery system. You’ve built a dynamic, non-deterministic branching maze—a <strong>Feature Flag Graveyard</strong>.</p>



<pre class="wp-block-preformatted"> <code>         ┌──────────────────────────┐
          │   Feature Flag Created   │
          └────────────┬─────────────┘
                       │
          ┌────────────▼─────────────┐
          │  100% Rolled Out to Prod │
          └────────────┬─────────────┘
                       │
       ┌───────────────┴───────────────┐
       │                               │
┌──────▼──────┐                ┌───────▼───────┐
│ Cleaned Up  │                │   FORGOTTEN   │
└─────────────┘                └───────┬───────┘
                                       │
                         ┌─────────────▼──────────────┐
                         │ Dead Code, Dead Tests,     │
                         │ Exponential Combinatorics  │
                         └────────────────────────────┘
</code></pre>



<h2 class="wp-block-heading has-vivid-cyan-blue-color has-text-color has-link-color wp-elements-4">The Hidden Cost of &#8220;Toggle Bloat&#8221;</h2>



<p class="wp-block-paragraph">When teams talk about technical debt, they usually point to outdated libraries, missing unit tests, or monolithic classes. Feature flag debt is far more insidious because it masquerades as risk mitigation.</p>



<p class="wp-block-paragraph">Here is what actually happens when flags are left to rot:</p>



<h3 class="wp-block-heading">1. Exponential Combinatorial Explosion</h3>



<p class="wp-block-paragraph">If your order processing service has 10 active feature flags, you don&#8217;t have one code path to test. You have $2^{10} = 1,024$ potential system states running in production simultaneously.</p>



<p class="wp-block-paragraph">No QA team or automated pipeline tests 1,000 combinations of code paths. Inevitably, a customer encounters a combination of flag states that no engineer has ever executed locally.</p>



<h3 class="wp-block-heading">2. Cognitive Overhead &amp; Architectural Degradation</h3>



<p class="wp-block-paragraph">Consider a simple backend service method after two years of uncleaned flags:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-bottom-disabled cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-Roboto-Mono.ttf" style="font-size:1rem;font-family:Code-Pro-Roboto-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * 1rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>// A legacy nightmare hiding behind modern tooling
public async processPayment(order: Order): Promise&lt;PaymentResult> {
  if (this.flags.isEnabled("use-stripe-v2")) {
    if (this.flags.isEnabled("bypass-fraud-check-beta")) {
      return this.stripeService.chargeFast(order);
    }
    return this.stripeService.chargeWithFraudCheck(order);
  } else {
    // Is anyone still hitting this branch? 
    // Is the legacy gateway even configured in production environment variables?
    return this.legacyGateway.charge(order);
  }
}
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #6A9955">// A legacy nightmare hiding behind modern tooling</span></span>
<span class="line"><span style="color: #569CD6">public</span><span style="color: #D4D4D4"> async </span><span style="color: #DCDCAA">processPayment</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">order</span><span style="color: #D4D4D4">: Order): Promise&lt;PaymentResult&gt; {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (this</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">flags</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">isEnabled</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&quot;use-stripe-v2&quot;</span><span style="color: #D4D4D4">)) {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (this</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">flags</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">isEnabled</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&quot;bypass-fraud-check-beta&quot;</span><span style="color: #D4D4D4">)) {</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> this</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">stripeService</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">chargeFast</span><span style="color: #D4D4D4">(order);</span></span>
<span class="line"><span style="color: #D4D4D4">    }</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> this</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">stripeService</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">chargeWithFraudCheck</span><span style="color: #D4D4D4">(order);</span></span>
<span class="line"><span style="color: #D4D4D4">  } </span><span style="color: #C586C0">else</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">// Is anyone still hitting this branch? </span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">// Is the legacy gateway even configured in production environment variables?</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> this</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">legacyGateway</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">charge</span><span style="color: #D4D4D4">(order);</span></span>
<span class="line"><span style="color: #D4D4D4">  }</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"></span></code></pre><span style="display:flex;align-items:flex-end;padding:10px;width:100%;justify-content:flex-start;background-color:#1E1E1E;color:#c7c7c7;font-size:12px;line-height:1;position:relative">PHP</span></div>



<p class="wp-block-paragraph">When a developer comes to refactor <code>processPayment</code>, they spend 80% of their time archaeology-ing flags: <em>Can I delete this else block? Who owns <code>use-stripe-v2</code>? Is it 100% enabled in production?</em></p>



<p class="wp-block-paragraph">Because nobody wants to break production, nobody touches it. The dead code remains forever.</p>



<h3 class="wp-block-heading">3. The Knight Capital Disaster (At Micro-Scale)</h3>



<p class="wp-block-paragraph">In 2012, Knight Capital lost $440 million in 45 minutes due to a failed software deployment. The root cause? A repurposed feature flag.</p>



<p class="wp-block-paragraph">They deployed new code that reused an old flag name, turning on obsolete, untested code on one of their servers. While your backend service might not lose half a billion dollars, executing a forgotten code path because someone toggled an abandoned flag in LaunchDarkly or Unleash will ruin your weekend just as effectively.</p>



<h2 class="wp-block-heading has-vivid-cyan-blue-color has-text-color has-link-color wp-elements-5">The Core Rule: A Feature Flag Is an Unpaid Loan</h2>



<p class="wp-block-paragraph">A feature flag is not a permanent architecture design pattern. <strong>It is financial debt with compounding interest.</strong></p>



<pre class="wp-block-code"><code>Total Cost = Cost of Feature + Cost of Flag Cleanup
</code></pre>



<p class="wp-block-paragraph">If you don&#8217;t budget time to remove the flag, you haven&#8217;t finished building the feature. You have merely pushed the labor cost onto future developers—usually with interest.</p>



<h2 class="wp-block-heading has-vivid-cyan-blue-color has-text-color has-link-color wp-elements-6">How to Prevent Your Codebase From Becoming a Graveyard</h2>



<p class="wp-block-paragraph">Solving flag debt isn&#8217;t a technical problem; it&#8217;s an operational discipline problem. Here are four practical patterns high-performing teams use to keep their flag count low:</p>



<h3 class="wp-block-heading">1. Classify Flags by Lifetime</h3>



<p class="wp-block-paragraph">Not all flags are created equal. Treat them according to their actual intent:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><td><strong>Flag Type</strong></td><td><strong>Lifetime</strong></td><td><strong>Purpose</strong></td></tr></thead><tbody><tr><td><strong>Release Flags</strong></td><td>1–4 weeks</td><td>Short-lived safety wrappers for Trunk-Based Development. Must be deleted post-rollout.</td></tr><tr><td><strong>Experiment Flags</strong></td><td>2–8 weeks</td><td>A/B testing. Deleted immediately after statistical significance is reached.</td></tr><tr><td><strong>Ops / Kill Switches</strong></td><td>Permanent</td><td>Circuit breakers for infrastructure (e.g., <code>disable-heavy-search-indexing</code>).</td></tr><tr><td><strong>Permission Flags</strong></td><td>Permanent</td><td>Entitlements and tenant configuration (e.g., <code>enable-enterprise-sso</code>).</td></tr></tbody></table></figure>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><strong>Rule:</strong> 90% of your flags should be <strong>Release Flags</strong>. If a flag is meant for release safety, set an explicit expiration date on day one.</p>
</blockquote>



<h3 class="wp-block-heading">2. Make Flags Expire in CI/CD</h3>



<p class="wp-block-paragraph">Don&#8217;t rely on human memory to clean up code. Use your build pipeline to enforce cleanup:</p>



<ul class="wp-block-list">
<li><strong>Stale Flag Alerts:</strong> Configure your flag management tool (Unleash, LaunchDarkly, ConfigCat) to notify Slack when a flag has been at 100% roll-out for more than 14 days.</li>



<li><strong>Failing Build Triggers:</strong> Some teams add metadata to flags in code:TypeScript<code>// @FeatureFlag(owner: "team-checkout", expires: "2026-10-15")</code> If a build runs past the expiration date and the flag is still in the codebase, the CI pipeline fails.</li>
</ul>



<h3 class="wp-block-heading">3. Pair Flag Creation with Cleanup Tickets</h3>



<p class="wp-block-paragraph">Whenever a developer opens a PR that introduces a new Release Flag, the definition of &#8220;Done&#8221; must require two artifacts:</p>



<ol start="1" class="wp-block-list">
<li>The Jira/GitHub issue for the feature implementation.</li>



<li>A scheduled, prioritized <strong>cleanup ticket</strong> to delete the flag logic and test branches 2 weeks post-launch.</li>
</ol>



<h3 class="wp-block-heading">4. Continuous Refactoring Culture</h3>



<p class="wp-block-paragraph">Make flag removal a lightweight, ongoing task rather than a quarterly &#8220;hackathon&#8221; debt-clearing chore. Removing a fully rolled-out feature flag is usually a 5-minute task: delete the <code>if/else</code>, remove the obsolete test cases, and delete the key from your flag dashboard.</p>



<h2 class="wp-block-heading">Conclusion: Clean Code Means Removing the Scaffolding</h2>



<p class="wp-block-paragraph">When builders construct a skyscraper, they use scaffolding to support the structure during assembly. Once the building stands on its own, they remove the scaffolding. Nobody leaves steel frameworks blocking the front entrance of a finished building.</p>



<p class="wp-block-paragraph">Feature flags are modern software scaffolding. They allow us to move fast, test in production, and deploy without fear. But once your feature is live and stable, tear down the scaffolding.</p>



<p class="wp-block-paragraph">Your future self—and your entire engineering team—will thank you.</p>



<p class="wp-block-paragraph"></p>
<p>The post <a href="https://codecraftdiary.com/2026/09/13/feature-flag-graveyard-technical-debt/">The Feature Flag Graveyard: How a Safety Net Becomes Technical Debt</a> appeared first on <a href="https://codecraftdiary.com">CodeCraft Diary</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://codecraftdiary.com/2026/09/13/feature-flag-graveyard-technical-debt/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Staging Environments Are a Deployment Problem, Not an Infrastructure Problem</title>
		<link>https://codecraftdiary.com/2026/08/23/staging-environments-deployment-problem/</link>
					<comments>https://codecraftdiary.com/2026/08/23/staging-environments-deployment-problem/#respond</comments>
		
		<dc:creator><![CDATA[codecraftdiary]]></dc:creator>
		<pubDate>Sun, 23 Aug 2026 13:18:00 +0000</pubDate>
				<category><![CDATA[Development Workflow & Best Practices]]></category>
		<category><![CDATA[development]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[software-design]]></category>
		<category><![CDATA[workflow]]></category>
		<guid isPermaLink="false">https://codecraftdiary.com/?p=3347</guid>

					<description><![CDATA[<p>This isn&#8217;t an argument against testing before production. It&#8217;s an argument against treating a long-lived shared staging environment as your primary safety mechanism. Every software engineer has lived through this exact Thursday afternoon nightmare: The feature was thoroughly tested on the testing server. The CI/CD pipeline was a sea of reassuring green checkmarks. QA gave [&#8230;]</p>
<p>The post <a href="https://codecraftdiary.com/2026/08/23/staging-environments-deployment-problem/">Staging Environments Are a Deployment Problem, Not an Infrastructure Problem</a> appeared first on <a href="https://codecraftdiary.com">CodeCraft Diary</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">This isn&#8217;t an argument against testing before production. It&#8217;s an argument against treating a long-lived shared staging environment as your primary safety mechanism.</p>



<p class="wp-block-paragraph">Every software engineer has lived through this exact Thursday afternoon nightmare: The feature was thoroughly tested on the testing server. The CI/CD pipeline was a sea of reassuring green checkmarks. QA gave their final stamp of approval. With absolute confidence, you clicked &#8220;Deploy to Production.&#8221;</p>



<p class="wp-block-paragraph">Ten minutes later, the alert channels started screaming, database connections saturated, and customers began reporting broken checkout flows.</p>



<p class="wp-block-paragraph">When the post-mortem rolled around, the consensus was predictable: <em>&#8220;The testing environment diverged from Production again. We need to sync the databases more often. We need to mirror the production setup exactly.&#8221;</em></p>



<p class="wp-block-paragraph">So, the team spends two sprints setting up complex data-anonymization pipelines and mirroring server configurations. Yet, three months later, the exact same production incident happens again.</p>



<p class="wp-block-paragraph">After few years of working on backend systems, debugging production outages, and trying to keep pipelines fast, I&#8217;ve come to a realization that many engineering teams refuse to accept: <strong>staging environments are lying to you—or, more accurately, long-lived shared staging environments often give teams a false sense of confidence.</strong></p>



<p class="wp-block-paragraph">Your testing setup isn&#8217;t failing because your DevOps team is lazy or because you haven&#8217;t automated your database dumps. It is failing because treating a shared testing environment as a safety net is an architectural and process flaw. Staging environments are a deployment problem, not an infrastructure problem.</p>



<p class="wp-block-paragraph">Here you can find last article of this category: <a href="https://codecraftdiary.com/2026/07/26/merge-conflicts-process-problem/" target="_blank" rel="noreferrer noopener">https://codecraftdiary.com/2026/07/26/merge-conflicts-process-problem/</a></p>



<h2 class="wp-block-heading has-vivid-cyan-blue-color has-text-color has-link-color wp-elements-15">The Four Myths of the &#8220;Identical&#8221; Staging Environment</h2>



<p class="wp-block-paragraph">We keep clinging to the idea of a pre-production testing environment because it feels safe. It gives us a comfortable illusion of risk mitigation. But when you look at how modern backend systems operate, the concept of a true &#8220;pre-production&#8221; environment completely falls apart for four main reasons.</p>



<h3 class="wp-block-heading">1. The Data Drift Reality</h3>



<p class="wp-block-paragraph">You can dump your production database, run anonymization scripts, and seed your testing server every single night. It still won&#8217;t matter. Production data is dynamic, unpredictable, and dirty. It contains edge-case state combinations created by real users over years of operating the system.</p>



<p class="wp-block-paragraph">A staging environment usually contains pristine, synthetic test data or a stripped-down database dump. You are unlikely to catch a bug that only triggers when a user has a legacy account created in 2021, three active subscriptions, and a special character in their billing address—because that specific state combination simply doesn&#8217;t exist outside of Production.</p>



<h3 class="wp-block-heading">2. Third-Party Sandboxes Are Half-Baked</h3>



<p class="wp-block-paragraph">Modern backend architectures rely heavily on third-party APIs: payment processors like Stripe, email providers, or external auth services.</p>



<p class="wp-block-paragraph">The sandbox environments provided by these services rarely behave like their production counterparts. A test mode won&#8217;t simulate exact rate limits, network latency, or edge-case webhook delivery failures. When your staging setup relies on mock services or sandbox APIs, you aren&#8217;t testing your application against reality; you are testing it against a simplified toy version of reality.</p>



<h3 class="wp-block-heading">3. Traffic and Concurrency Don&#8217;t Exist on Testing Servers</h3>



<p class="wp-block-paragraph">Unless you are running expensive, continuous load testing against your pre-production environment (which almost nobody actually does), your testing server is dead quiet.</p>



<p class="wp-block-paragraph">Your service running on Staging handles one or two requests at a time while a developer or QA engineer manually clicks through a flow. In Production, that same service handles thousands of concurrent requests, competing for thread pools, database connections, and cache space. Race conditions, deadlocks, and connection pool exhaustion only happen under real load. A staging server is fundamentally blind to them.</p>



<h3 class="wp-block-heading">4. The Maintenance Sinkhole</h3>



<p class="wp-block-paragraph">Maintaining a testing server that somewhat resembles Production takes an immense amount of engineering effort. Databases need maintenance, API keys expire, test accounts get corrupted, and environment variables drift.</p>



<p class="wp-block-paragraph">I’ve seen senior engineers spend 20% of their sprint time debugging issues that turned out to be &#8220;just a test server configuration issue.&#8221; That is wasted engineering effort spent maintaining an environment that doesn&#8217;t generate a single dollar of business value.</p>



<p class="wp-block-paragraph"></p>



<p class="has-vivid-cyan-blue-color has-text-color has-link-color has-large-font-size wp-elements-16 wp-block-paragraph">What Staging Is Actually Good For</p>



<p class="wp-block-paragraph">None of this means staging environments are useless.</p>



<p class="wp-block-paragraph">They still provide value for infrastructure validation, smoke testing after deployments, manual QA of integrated systems, customer demonstrations, and validating deployment pipelines.</p>



<p class="wp-block-paragraph">The mistake isn&#8217;t having a staging environment. The mistake is treating it as the ultimate source of confidence before Production. A shared staging server should support your deployment process—not become the gatekeeper that every change must pass through.</p>



<h2 class="wp-block-heading has-vivid-cyan-blue-color has-text-color has-link-color wp-elements-17">How Staging Bottlenecks Your Entire Workflow</h2>



<p class="wp-block-paragraph">The technical limitations are bad enough, but the cultural impact on your team is worse.</p>



<p class="wp-block-paragraph">When a team relies on a single shared testing server, it becomes a bottleneck for the entire delivery process. Developer A finishes a feature and deploys it to the testing server. Developer B finishes a completely unrelated bug fix and also deploys it there. Now, QA starts testing Developer B’s fix, but something breaks because Developer A’s feature introduced a bug.</p>



<p class="wp-block-paragraph">Suddenly, nobody knows whose code broke the build. Deployment to Production gets blocked for everyone. What started as two small, independent changes transforms into a massive, tangled deployment batch.</p>



<p class="wp-block-paragraph">By trying to make deployment &#8220;safer&#8221; through a shared testing environment, teams inadvertently recreate the exact problem they tried to avoid: <strong>large, high-risk releases.</strong></p>



<h2 class="wp-block-heading has-vivid-cyan-blue-color has-text-color has-link-color wp-elements-18">Shift Your Mindset: From Pre-Production Safety to Production Resilience</h2>



<p class="wp-block-paragraph">If we accept that a testing server can never accurately predict how code will behave in Production, we have to change our approach. The goal shouldn&#8217;t be <em>preventing all bugs from reaching Production through pre-production testing</em>. The goal should be <strong>minimizing the blast radius of changes and recovering instantly when things go wrong.</strong></p>



<p class="wp-block-paragraph">High-performing engineering organizations don&#8217;t rely on long-lived testing servers. They shift their focus toward modern deployment practices.</p>



<h3 class="wp-block-heading has-vivid-cyan-blue-color has-text-color has-link-color wp-elements-19">Ephemeral (Preview) Environments</h3>



<p class="wp-block-paragraph">If you need an environment to test a feature before merging, don&#8217;t use a shared long-lived test server. Use ephemeral environments.</p>



<p class="wp-block-paragraph">Preview environments solve a completely different problem than staging: they validate isolated changes rather than simulating production.</p>



<p class="wp-block-paragraph">Whenever a developer opens a Pull Request, your CI pipeline can spin up a temporary, isolated Docker container or preview environment containing only that branch&#8217;s changes. The developer or QA can test the specific feature, run integration tests, and once the PR is merged or closed, the environment automatically destroys itself. This completely eliminates queueing, environment pollution, and cross-feature interference.</p>



<h3 class="wp-block-heading has-vivid-cyan-blue-color has-text-color has-link-color wp-elements-20">Progressive Delivery and Feature Flags</h3>



<p class="wp-block-paragraph">Instead of holding code back on a testing server for weeks, deploy code to Production immediately—behind a feature flag.</p>



<p class="wp-block-paragraph">Feature flags decouple <em>deployment</em> (moving code to servers) from <em>release</em> (making functionality visible to users). You can deploy a new feature to Production on Monday, test it internally in the live environment using your own staff accounts, turn it on for 1% of real users on Wednesday, and gradually scale it to 100% by Friday. If something breaks at 1%, the blast radius is tiny, and turning off the flag takes milliseconds.</p>



<h3 class="wp-block-heading has-vivid-cyan-blue-color has-text-color has-link-color wp-elements-21">Observability and Fast Rollbacks</h3>



<p class="wp-block-paragraph">Your time is much better spent improving your production observability than fixing a testing server. Invest in proper tracing, structured logging, and automated metric alerts.</p>



<p class="wp-block-paragraph">When you pair strong observability with an automated rollback strategy, catching a micro-bug in Production within 60 seconds of deployment is significantly cheaper and safer than spending three weeks manually validating everything on a test server.</p>



<h2 class="wp-block-heading has-vivid-cyan-blue-color has-text-color has-link-color wp-elements-22">Stop Servicing the Mirage</h2>



<p class="wp-block-paragraph">The next time a release breaks in Production, resist the urge to say, <em>&#8220;We need to fix our testing environment.&#8221;</em></p>



<p class="wp-block-paragraph">Step back and look at your workflow. Ask yourself why your Pull Requests are sitting on a testing server for days. Ask why your deployment process requires a green signal from an environment that doesn&#8217;t process real traffic or real data.</p>



<p class="wp-block-paragraph">Staging environments give us a warm, comfortable feeling of safety, but in practice, they act as a tax on developer productivity and a false security blanket. Shrink your releases, flag your features, test in Production safely, and focus your energy on building resilient systems that recover quickly. Your pipeline—and your team—will thank you. The safest deployment process isn&#8217;t the one that spends the most time on staging. It&#8217;s the one that makes every production change small, observable, and reversible.</p>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph"></p>
<p>The post <a href="https://codecraftdiary.com/2026/08/23/staging-environments-deployment-problem/">Staging Environments Are a Deployment Problem, Not an Infrastructure Problem</a> appeared first on <a href="https://codecraftdiary.com">CodeCraft Diary</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://codecraftdiary.com/2026/08/23/staging-environments-deployment-problem/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
