Microservices Containerization with Docker

Running dozens of microservices on servers creates a packaging problem. Each service needs specific software versions, libraries, and configurations. Docker solves this by packaging each service with everything it needs into a portable unit called a container.

The "Works on My Machine" Problem

A developer builds the Payment Service on a laptop running Python 3.11 with a specific set of libraries. The production server runs Python 3.8. The service works in development and crashes in production. The developer says, "It works on my machine."

Docker eliminates this problem by packaging the service with its exact runtime environment.

Virtual Machines vs Containers

VIRTUAL MACHINES                    CONTAINERS
================                    ==========
+------------------+                +---+ +---+ +---+
| App              |                |App| |App| |App|
+------------------+                +---+ +---+ +---+
| Guest OS (2GB)   |                +---------------+
+------------------+                | Container     |
| Guest OS (2GB)   |                | Runtime       |
+------------------+                +---------------+
| Hypervisor       |                | Host OS       |
+------------------+                +---------------+
| Host OS          |                | Hardware      |
+------------------+                +---------------+
| Hardware         |

Each VM includes its own       Containers share the Host OS.
operating system.              No extra OS per container.
Each VM = 2-5 GB               Each container = 50-200 MB
Starts in minutes              Starts in seconds

Containers are lighter and faster than virtual machines because they share the host operating system instead of each running their own copy of an OS.

What Is a Docker Image

A Docker image is a read-only blueprint for a container. It contains the operating system layer, runtime (like Python or Java), dependencies (libraries), and the application code. An image is like a recipe. A container is the meal made from that recipe.

You build an image once. You run it anywhere — your laptop, a colleague's machine, a cloud server, a test environment. The behavior is identical everywhere.

The Dockerfile

A Dockerfile is a text file with instructions for building a Docker image. Each instruction adds a layer to the image.

EXAMPLE: Dockerfile for an Order Service (Python)
==================================================
# Start with an official Python 3.11 base image
FROM python:3.11-slim

# Set the working directory inside the container
WORKDIR /app

# Copy dependency list into the container
COPY requirements.txt .

# Install dependencies
RUN pip install -r requirements.txt

# Copy the application code
COPY . .

# Tell Docker which port the service listens on
EXPOSE 8080

# Command to run when container starts
CMD ["python", "app.py"]

To build and run this:

docker build -t order-service:1.0 .
docker run -p 8080:8080 order-service:1.0

The Order Service runs inside its container. It is isolated from other containers and from the host machine.

Containers for Each Microservice

MICROSERVICES IN CONTAINERS
============================

HOST SERVER
+----------------------------------------------------------+
| +--------------+ +--------------+ +------------------+   |
| | [Order Svc]  | | [Payment Svc]| | [Inventory Svc]  |   |
| | Python 3.11  | | Java 21      | | Go 1.22          |   |
| | PostgreSQL   | | MySQL        | | Redis            |   |
| | Port 8080    | | Port 9000    | | Port 7070        |   |
| +--------------+ +--------------+ +------------------+   |
|                     Docker Engine                        |
+----------------------------------------------------------+

Each service runs in its own container.
Different languages, runtimes, and dependencies do not conflict.

Docker Registry

A Docker Registry stores images so teams can share them and servers can download them. Docker Hub is the public registry. Companies run private registries for internal images.

WORKFLOW:
Developer builds image --> Pushes to Registry --> Server pulls image --> Runs container

[Developer Laptop]   [Registry]         [Production Server]
docker build         <-- push --        docker pull
docker tag           store image        docker run
docker push -->      ---------> --->    new container running

Docker Compose for Local Development

Running a system of 10 microservices locally by starting each container manually is slow. Docker Compose lets you define all services in one file and start them with one command.

docker-compose.yml (simplified)
=================================
services:
  order-service:
    image: order-service:1.0
    ports: ["8080:8080"]
    depends_on: [postgres]

  payment-service:
    image: payment-service:1.0
    ports: ["9000:9000"]

  postgres:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: localpass

Run all services:
  docker-compose up

Stop all services:
  docker-compose down

Container Benefits for Microservices

  • Consistency — the same image runs in development, testing, and production. No environment-specific bugs.
  • Isolation — services do not interfere with each other's dependencies.
  • Fast startup — containers start in seconds, enabling rapid scaling.
  • Portability — move from AWS to Google Cloud by pushing the same image to a different registry.
  • Immutability — a deployed container never changes. Updates deploy a new container, not patches to a running one.

Container Limitations

Containers solve packaging but not scheduling. When you have 50 microservices and 100 containers spread across 10 servers, you need a system to decide which containers run on which servers, restart crashed containers, and scale containers up and down automatically. That system is an orchestrator. Kubernetes, covered in the next topic, is the most widely used orchestrator.

Leave a Comment

Your email address will not be published. Required fields are marked *