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.
