Cloud Computing Fundamentals
Containers and Microservices
What a container actually packages, why splitting a monolith into microservices changes how a team ships and scales, and what an orchestrator like Kubernetes adds once you have more containers than you can manage by hand.
- Explain what a container is and why it starts faster than a virtual machine
- Describe how a container image keeps the runtime environment identical from a laptop to production
- Distinguish a monolithic application from a microservices architecture by how each one is deployed and scaled
- Identify what container orchestration adds on top of running individual containers by hand
- Recognize the operational tradeoffs microservices introduce in exchange for independent scaling and deployment
The last lesson answered who runs the code. This one asks how it is built
The last lesson left compute answered: a serverless function, a virtual machine, or something in between runs your code, and the provider handles more or less of the surrounding work depending on which you pick. None of that says anything about how the application itself is put together. An application that started as one large codebase, one deployment, one team, runs into a specific kind of trouble as it grows: a small fix to the checkout flow means testing and redeploying the entire application, catalog, accounts, and all, and a traffic spike in one part of the app forces you to scale the whole thing. Containers and microservices are 2 separate ideas that, together, solve exactly that problem.
What a container actually packages
A container is an isolated process bundled with everything it needs to run: your code, its runtime, and its dependencies, all in one self-contained unit. Package a Node.js application into a container image and that image runs identically whether it started on a developer's laptop, a test server, or a production cluster, because the image carries its own runtime instead of depending on whatever happens to be installed on the host.
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
CMD ["node", "server.js"]
docker build -t checkout-service:1.0 .
docker run -p 3000:3000 checkout-service:1.0
That Dockerfile describes the image; docker build packages it; docker run starts a container from it. The compute lesson already placed containers on the control-versus-convenience spectrum, starting in seconds instead of a virtual machine's minutes, because a container shares the host operating system's kernel instead of booting a full guest OS of its own.
From monolith to microservices
A monolith is one codebase, one deployment, and usually one shared database, no matter how many distinct features it contains. A microservices architecture splits that same application into a suite of small, independently deployable services, each built around one business capability (catalog, cart, checkout, user accounts) and communicating over lightweight APIs rather than in-process function calls. Each service typically owns its own database instead of sharing one, which is what makes it deployable and scalable on its own.
Go back to the flash-sale scenario. In a monolith, a tripling of checkout traffic forces you to scale the entire deployment, catalog and accounts included, since there is only one deployable unit to scale. Split into microservices, the checkout service scales on its own, and catalog and accounts stay exactly as they were, because each one is now a separate, independently deployable piece.
The trade you make: independence for coordination
None of this is free. Splitting a monolith into microservices trades a single codebase's simplicity for a set of new problems that only show up once services are separate. A call from checkout to the user service now crosses a network instead of calling a function in the same process, which means it can time out or fail in ways an in-process call never could. Decentralized data, each service with its own database, means giving up one shared transaction across the whole request in favor of eventual consistency between services. And a single customer request that touches 5 services is harder to debug than one that touches one, since there is no longer a single stack trace to read. More services is not automatically better; it is a deliberate trade of simplicity for independence, one that only pays off once a team has the operational tooling to manage it, which is exactly what the next lesson covers.
Orchestration: managing more containers than a human can track
Run one container by hand and docker run is enough. Run 3 replicas of a checkout service for redundancy, plus a catalog service, plus a cart service, each with its own replicas, and manually watching all of them stops working quickly. A container orchestrator like Kubernetes takes a declarative description of what you want and continuously works to make reality match it.
apiVersion: apps/v1
kind: Deployment
metadata:
name: checkout-service
spec:
replicas: 3
selector:
matchLabels:
app: checkout-service
template:
metadata:
labels:
app: checkout-service
spec:
containers:
- name: checkout
image: checkout-service:2.1
ports:
- containerPort: 8080
That replicas: 3 line is a declaration, not a one-time command. If one of the 3 crashes, Kubernetes notices the mismatch between the declared state and reality and starts a replacement automatically, a capability called self-healing. Kubernetes also handles service discovery and load balancing across those replicas, and rolls updates out gradually instead of all at once, so a team stops babysitting containers by hand and starts describing the state they want instead.
Exam cues: spotting the fit in a scenario
| The scenario says... | Points to |
|---|---|
| "Package once, run identically everywhere" | Container |
| "Independent teams ship independently, each owns its own data" | Microservices |
| "Automatically restart failed instances, roll updates out gradually" | Container orchestration (Kubernetes) |
| "One codebase, one deployment, one database" | Monolith |
Where this leaves you
Splitting an application into microservices and packaging each piece as a container are 2 separate decisions that happen to reinforce each other: the independent deployability microservices promise only holds up if each piece can actually ship and scale on its own, and a container is exactly what makes that practical. None of it, the builds, the rollouts, the ever-growing number of moving pieces, stays manageable by hand for long. The next lesson covers the practices and tooling, DevOps and infrastructure as code, that keep it manageable.
