Day 1: The Minimal Service Image
Welcome to the start of our journey. Over the next several weeks, we will build a distributed system from the ground up, moving from a single container to a resilient, multi-service stack.
Today, we establish our foundation. We aren't just running a process; we are defining a boundary.
The Problem: The "It Works on My Machine" Mirage
In professional environments, the most dangerous phrase is "it works on my machine." This usually happens because your local environment has hidden dependencies—a specific version of a library, a global configuration file, or a stray environment variable—that your production server lacks.
When you package an application as a Docker image, you aren't just bundling code; you are defining a deterministic environment. You are freezing the OS-level dependencies so that the binary execution is identical, whether it runs on your laptop or a 64-core server in a data center.
The Concept: The Immutable Execution Unit
The core concept today is Immutability. An image is a read-only template. Once you build it, it does not change. If you need to update the application, you build a new image.
This is the design principle behind systems like Google’s Borg or Netflix’s Titus. By ensuring the image is immutable, you eliminate "configuration drift," where servers slowly become unique, snowflake-like entities that are impossible to replicate when they fail.
The Architecture: Packaging the Producer
Our system will eventually consist of multiple services. We start with the producer, a small Python service. In a production system, this component would be responsible for ingesting raw data and serializing it into a common format (like JSON) before handing it off to a message bus.
Here is how we define the boundary in our Dockerfile:
The Failure Demo: The "Missing Dependency" Trap
To understand why this boundary matters, consider a production outage where a developer updates a system library on a host server, inadvertently breaking every application relying on that library.
In our failure demo, we will simulate this by attempting to run our code without the container's isolated environment. We will manually purge a required library from the host system. You will observe that the application crashes immediately. Then, we will run the same code inside the container. Because the container carries its own "world," it remains blissfully unaware of the damage we inflicted on the host.
Production Stakes: The Cost of State
In a hyperscale system, we treat containers as ephemeral. If a container crashes, the orchestrator replaces it. If we store data inside the container’s writable layer, that data vanishes the moment the container restarts. This is how data loss occurs.
We cut a corner today by not yet attaching persistent volumes (we will do that in a later lesson). For now, our container is a "stateless" worker. If it dies, we lose its local memory, but the system remains consistent because the state lives elsewhere.
Forward Momentum
Today, you build an isolated, immutable container. Tomorrow, we will connect this producer to a network, allowing it to talk to other services. You are building the individual bricks before we assemble the cathedral.
Assignment
Your task is to build and run the producer image.
Build: Create the image tagged as
producer:v1.Run: Start the container in detached mode.
Verify: Use
docker logsto ensure the producer is emitting JSON transactions.Break: Attempt to kill the process inside the container using
docker killand observe how the container exits. Restart it and observe that it resumes perfectly.
Solution
Steps:
Navigate to the
/producerdirectory in the repo.Run
docker build -t producer:v1 .Run
docker run -d --name my-producer producer:v1Run
docker logs my-producerto see the output.Run
docker kill my-producerto simulate a crash.Run
docker start my-producerto bring it back.