Module 4 of 9 Infra
Containers and Orchestration
Why this matters for Univa
Univa ships Next.js apps that fit neatly into PaaS platforms (Vercel, Cloudflare Pages) with zero container knowledge required, and that should stay the default path for most work. Even so, three things make this module worth the evening:
- Not everything fits a JS-first PaaS. A client's existing Python data pipeline, a long-running background worker, a legacy service someone hands over mid-project, or anything with an unusual system dependency will not deploy cleanly to Vercel. Docker is the standard answer, and Univa needs a working, repeatable pattern for it before a deadline forces learning it from scratch.
- Clients and job listings namedrop this constantly. "We run on Kubernetes," "it's containerized," "we use ECS," come up in client conversations and job descriptions well before Ahnaf actually needs to operate a cluster. Knowing what these words actually mean, and how much (or little) they matter for a given project, prevents both under-selling ("I don't know Docker") and over-promising ("sure, we'll set up Kubernetes") on things that do not fit Univa's scale.
- Overbuilding is a real risk here, not just a theoretical one. Kubernetes is the single most over-recommended piece of infrastructure for small teams. Knowing the realistic ladder (PaaS, then serverless containers, then only-if-truly-needed Kubernetes) protects Univa from both a fragile shell-script deployment on a $5 VPS and an unmaintainable K8s cluster nobody has time to run solo.
The bottom-line skill this module builds: recognize when "just Dockerize it and put it on Cloud Run" solves 95% of what a client actually needs, and know exactly what step comes next on the rare occasion it does not.
Core concepts
Docker fundamentals
- Image. A read-only, layered snapshot of everything needed to run an app: code, runtime, libraries, and the relevant OS pieces. Images are immutable. You do not log into a running image and change it; you change the recipe and rebuild.
- Dockerfile. The text recipe that produces an image. In plain terms: start
FROMa base image,COPYyour code in,RUNwhatever install steps are needed, thenCMDthe command that starts the app. Each instruction adds a cached layer, which is why a second Docker build is usually much faster than the first: unchanged layers are reused instead of rebuilt. - Container. A running instance of an image: an isolated process with its own filesystem view, network, and process space, but sharing the host machine's kernel. This is the key difference from a full virtual machine, which boots an entire separate operating system. Sharing the kernel is why containers start in milliseconds and use a fraction of the resources a VM would.
- Registry. Where built images are stored and pulled from. Docker Hub is the public default. Every major cloud also runs its own private registry (Amazon ECR, Azure Container Registry, Google Artifact Registry, Alibaba Cloud Container Registry). The normal CI/CD flow is: build the image, push it to a registry, then have the deploy step pull it from there rather than rebuilding on the server.
- Volumes. Containers are meant to be disposable: when one is destroyed, anything written to its own filesystem is gone. A volume is a separate, persistent storage location a container can read and write to, used for anything that needs to survive a restart (uploaded files, a database's actual data).
Local multi-container development: docker-compose
Real apps rarely run as a single container even before any orchestration is involved. A typical local setup might need an API container, a Postgres container, and a Redis container all running together. docker-compose is a simple tool for exactly this: one YAML file lists each container, its image or Dockerfile, its ports, and which other containers it can talk to, and a single docker compose up command starts everything together on a shared local network. This is the natural stepping stone between "I understand one container" and "I understand why orchestration exists," and it is genuinely useful on its own for local development even on projects that never touch Kubernetes.
Container networking basics
By default, each container is isolated with its own network namespace. Docker's bridge network lets containers on the same host reach each other by name (the service name in a docker-compose file becomes a usable hostname), while port mapping (-p 8080:80) exposes a container's internal port to the host machine or the outside world. In Kubernetes, this same idea scales up: a Service gives a changing set of pods one stable internal DNS name, so other pods can call payments-service without knowing which pod, or which node, currently answers to it. The pattern is the same at every scale: containers talk to each other by name, not by hunting down a specific IP address that could change at any time.
The sidecar pattern (concept only)
A sidecar is a second, helper container that runs alongside a main application container in the same pod, handling a supporting concern like logging, monitoring, or proxying traffic, without the main app needing to know about it. This is a common enough pattern in Kubernetes conversations to recognize by name, even though Univa's current serverless-container use cases rarely need one: it usually only becomes relevant once a system is complex enough to need a dedicated network proxy or log shipper attached to every service.
Health checks: liveness and readiness
Every orchestration layer (Kubernetes, Cloud Run, ECS) needs a way to know whether a container is actually working, not just running. Two related ideas show up everywhere:
- Liveness check. "Is this container still alive, or should it be killed and restarted?" Usually a simple HTTP endpoint (
/health) that returns 200 if the process is not stuck or deadlocked. - Readiness check. "Is this container ready to receive real traffic yet?" Distinct from liveness: a container can be alive but still warming up (loading a large model, running a database migration) and should not receive requests until it reports ready.
Getting these two checks right is a small amount of code that has an outsized effect on reliability: it is what lets a platform safely roll out new versions and route around unhealthy instances without a human watching.
Image size and basic container security
A few habits separate a production-ready Dockerfile from a fragile one:
- Use a minimal base image (an "alpine" or "slim" variant) rather than a full OS image, to reduce both size and the attack surface of unnecessary installed software.
- Run as a non-root user inside the container. The default is root, which means a compromised container process has more power than it needs; adding a dedicated user in the Dockerfile is a few lines and meaningfully reduces risk.
- Multi-stage builds. Build the app in one stage (with all the compilers and dev dependencies), then copy only the final compiled output into a clean, minimal final image. This keeps the shipped image small and avoids bundling build tools into production.
- Scan images for known vulnerabilities before deploying anything client-facing; most registries (Docker Hub, GitHub Container Registry, the cloud-provider registries) offer this as a built-in or easily bolted-on step.
Containers vs serverless functions: what's the actual difference
Module 3 covered serverless functions (AWS Lambda, Google Cloud Functions, Vercel's own functions). It is worth being precise about how these relate to containers, because the two get blurred in casual conversation:
- A serverless function runs a single piece of code in response to one event (an HTTP request, a queue message), for a short, bounded amount of time, in a runtime the platform fully controls. You write a function, not a Dockerfile.
- A container packages an entire runnable environment (your app plus its dependencies plus, if you choose, a long-running process) and can run indefinitely, not just for the duration of one request.
- A serverless container (Cloud Run, Fargate, Container Apps) sits between the two: you package a container like you would for Kubernetes, but the platform runs and scales it the way it would a serverless function, including scaling to zero.
For Univa this distinction decides the tool, not just the vocabulary: a short stateless API route belongs in a serverless function (or just a Vercel API route), a long-running worker or a non-JS runtime belongs in a container, and anything unsure which of the two it is usually turns out to be a container once you try to force it into a function's time limit.
Why orchestration exists
Running a single container by hand (docker run ...) is fine for a hobby project or a quick demo. Orchestration becomes necessary the moment any of the following is true:
- Multiple containers need to work together (a web app, an API, a cache, a background worker) and need a reliable way to find and talk to each other.
- Self-healing matters. If a container crashes, something should notice and restart it automatically, without a human SSH-ing in at 2am.
- Scaling matters. More copies should spin up when traffic rises, and spin back down (to save money) when it falls.
- Zero-downtime deploys matter. A new version should roll out gradually, with an automatic rollback if it turns out to be broken.
- Scheduling matters. Across a fleet of machines, something needs to decide which physical machine each container actually runs on, and rebalance if one machine goes down.
An orchestrator is the layer of software that handles all of the above automatically. Kubernetes is the dominant one, but it is not the only one, and (see below) it is usually not the right starting point for a small team.
Kubernetes core ideas (concept level, not a hands-on cluster walkthrough)
Kubernetes has a large surface area, but a handful of ideas explain most conversations about it:
- Pod. The smallest deployable unit. Almost always one container per pod in practice, though a pod technically holds one or more tightly-coupled containers that always run together on the same machine and share a network address.
- Deployment. A declarative statement of intent: "I want N replicas of this pod, running this image." Kubernetes constantly works to keep reality matching that statement. If a pod dies, a replacement starts automatically. If the image changes, the update rolls out gradually across replicas.
- Service. A stable network address (and basic load balancer) in front of a changing set of pods, so other parts of the system, or the internet via an Ingress, can reach "the API" without caring which specific pod instances are currently alive or where they happen to be running.
- Cluster. The full set of machines (nodes) Kubernetes manages, split into a control plane (the "brain," makes scheduling and health decisions) and worker nodes (where the actual pods run).
- Namespace. A way to logically divide one cluster into separate areas (for example,
stagingandproduction), mostly relevant once a team is big enough to need that separation.
Kubernetes itself is an open-source project originally built at Google, now governed by the Cloud Native Computing Foundation (CNCF), a vendor-neutral home that is why every major cloud (AWS, Azure, Google Cloud, Alibaba Cloud) offers its own managed flavor of the same underlying project rather than a proprietary alternative.
Honest framing: this is roughly the 20% of Kubernetes concepts that explains 80% of conversations about it. Actually operating a real cluster involves considerably more (networking plugins, storage classes, role-based access control, Helm charts for packaging, monitoring stacks) that sits outside the scope of what Univa currently needs and is not covered here.
Quick recap table (for spaced review)
| Term | One-line definition |
|---|---|
| Image | Immutable blueprint for a container, built from a Dockerfile |
| Container | A running instance of an image, sharing the host kernel |
| Registry | Storage/distribution point for images (Docker Hub, ECR, ACR, Artifact Registry) |
| Pod | Smallest Kubernetes unit, usually one container |
| Deployment | Declares how many replicas of a pod should exist and manages rollout |
| Service | Stable network address in front of a changing set of pods |
| Cluster | The full set of machines Kubernetes manages (control plane plus worker nodes) |
| Sidecar | A helper container running alongside the main one in the same pod |
| Liveness/readiness check | How the platform knows a container is alive, and separately, ready for traffic |
The landscape (comparison tables)
Kubernetes alternatives
| Tool | Optimized for | Status in 2026 |
|---|---|---|
| Kubernetes (K8s) | General-purpose container orchestration at any scale; the de facto industry standard | Dominant. Nearly every managed offering (EKS, AKS, GKE) is Kubernetes running underneath a friendlier interface. |
| Docker Swarm | Simple clustering built directly into the Docker engine; the easiest option to pick up if you already know docker run | Still ships with Docker Engine, but adoption has stalled for years. Docker's own guidance steers users toward Kubernetes for anything beyond a small, fixed cluster. Fine for learning, a risky long-term production bet. |
| HashiCorp Nomad | Flexible scheduling of mixed workloads (containers and plain binaries or legacy, non-containerized apps) with a much simpler operating model than Kubernetes | Actively maintained by HashiCorp in 2026, with regular releases through the year. A real option for teams that specifically need to orchestrate non-container workloads alongside containers without taking on full Kubernetes complexity. |
| Apache Mesos | Fine-grained resource sharing across very large, heterogeneous data-center clusters (its heyday was Twitter- and Airbnb-scale infrastructure) | Retired. The Apache Software Foundation moved Mesos to the Apache Attic in 2025 due to inactivity: no new releases, contributors, issues, or security patches. It is now a read-only archived project. Not a viable choice for any new project. |
Managed container services ("serverless containers")
| Service | Provider | What it hides from you |
|---|---|---|
| AWS ECS with Fargate | AWS | The underlying EC2 fleet: you describe a "task," Fargate runs it without you managing servers. You are still deep in AWS-specific configuration (task definitions, IAM roles, VPC networking), so the learning curve is real even though the servers themselves are hidden. |
| Google Cloud Run | Google Cloud | Almost everything. Give it a container image and a port; it scales from zero to many instances automatically, and scales back down to zero (and $0 cost) when idle. No cluster concept is exposed to you at all. |
| Azure Container Apps | Azure | Built on Kubernetes underneath, but Microsoft operates the cluster entirely. You interact with a simple "container app" abstraction, a similar promise to Cloud Run, delivered on top of the same technology that AKS exposes directly. |
Why this whole category exists: it gives you Docker's real packaging benefits (a consistent environment, portable builds, straightforward CI/CD) without any of the cluster-babysitting that raw Kubernetes demands. For a solo developer or a small team, this is almost always the right stopping point, not a compromise on the way to "real" infrastructure.
Container registries by provider
| Provider | Private container registry |
|---|---|
| AWS | Elastic Container Registry (ECR) |
| Microsoft Azure | Azure Container Registry (ACR) |
| Google Cloud | Artifact Registry |
| Alibaba Cloud | Container Registry (ACR, unrelated to Azure's ACR, another naming collision worth double-checking in conversation) |
| Vendor-neutral | Docker Hub, GitHub Container Registry |
Deployment terms you will hear in the same conversations
| Term | What it means |
|---|---|
| Rolling update | Replace old instances with new ones gradually, a few at a time, so the app never goes fully down during a deploy |
| Blue-green deployment | Run the new version fully alongside the old one, then switch traffic over in one step once it is verified, keeping instant rollback available |
| Canary deployment | Send a small percentage of real traffic to the new version first, watch for errors, then gradually increase it if things look healthy |
| Auto-scaling | Automatically adding or removing running instances based on load, the mechanism behind "scales to zero" and "scales up under traffic" |
| DaemonSet (Kubernetes-specific) | Ensures exactly one copy of a pod runs on every node in the cluster, typically used for cluster-wide logging or monitoring agents |
The realistic ladder, with rough operational cost
| Rung | What it is | Setup effort | Ongoing maintenance | When Univa uses it |
|---|---|---|---|---|
| 1. PaaS | Vercel, Cloudflare Pages | Minutes | Near zero | Default. Any standard Next.js/React app, no Docker involved at all. |
| 2. Serverless containers | Google Cloud Run, Azure Container Apps, AWS Fargate | An afternoon (write a Dockerfile, connect to the platform) | Low: platform handles scaling, patching of the underlying host | The moment something needs Docker: a non-JS backend, a specific runtime, a long-running worker. Still no cluster to manage. |
| 3. Managed Kubernetes / ECS without Fargate | AWS EKS, Azure AKS, Google GKE, plain ECS | Days to weeks | High: ongoing cluster upgrades, networking, access control, monitoring | Only for a genuinely large, multi-service, multi-team product with sustained operational investment. Not a current Univa need. |
Each rung up this ladder trades simplicity for control. Univa should only climb it when a specific, named requirement forces the move, never by default and never because it "sounds more professional."
How to choose (decision rules)
- If the app is a standard Next.js/React frontend with API routes, skip containers entirely. Deploy to Vercel or Cloudflare Pages. Docker adds nothing here.
- If the app is a backend service that does not fit a JS-first PaaS (a Python data pipeline, a custom worker, a legacy app handed over mid-project, something needing a specific OS-level dependency), Dockerize it and deploy to Cloud Run, Azure Container Apps, or AWS Fargate. One container, no cluster, minimal new surface area to maintain.
- If the project needs a few coordinated services (an API plus a background worker plus a cache like Redis) but still runs at modest scale, prefer several small serverless-container services talking over HTTP or a queue, rather than standing up Kubernetes. Each container still scales independently, and there is still no cluster to babysit.
- If a client's requirement is genuinely GPU-heavy batch processing, or needs a workload that is not a container at all (a legacy binary, a scheduled job on a specific OS), consider Nomad before reaching for Kubernetes; it was built exactly for mixed container/non-container scheduling with a much smaller learning curve.
- If the product is a genuinely large, multi-team, multi-service system (dozens of interacting services, a need for fine-grained network policy, multiple environments requiring identical tooling), that is when Kubernetes (via a managed offering: EKS, AKS, or GKE) starts to pay for its own complexity. No current Univa client is at this scale.
- If someone suggests Docker Swarm or Apache Mesos for a new project in 2026, redirect gently. Swarm is stagnant and Mesos is formally retired (Apache Attic, 2025). The real choice for anything beyond serverless containers is Kubernetes, or Nomad for the specific mixed-workload case, not either of those two.
A short worked scenario
A client hands Univa a Python script that scrapes a supplier's price list nightly and needs to keep running indefinitely, alongside their existing Next.js storefront. The instinctive "enterprise" answer might be "let's containerize everything and run it on Kubernetes." The actual right-sized answer, following the ladder above: keep the storefront exactly where it is (Vercel or Cloudflare Pages), write a small Dockerfile for the Python script, and deploy just that one container to Cloud Run with a scheduled trigger (Cloud Scheduler, or the equivalent cron-style trigger on whichever serverless container platform is used). Total new infrastructure: one container, one schedule, zero clusters.
Common mistakes when adopting containers for the first time
- Reaching for Kubernetes because a tutorial or a job posting made it sound mandatory, without checking whether a serverless container would do the same job with far less setup.
- Building a Dockerfile that runs as root and copies the entire project (including secrets or
.envfiles) into the image, then pushing that image to a registry others can pull from. - Skipping health checks entirely, so the platform has no way to tell a hung container from a healthy one, and traffic keeps flowing to a broken instance.
- Treating a container's local filesystem as permanent storage, then losing uploaded files or data the moment the container restarts or redeploys, because a volume or external storage was never wired up.
- Copying a client's on-prem Kubernetes setup wholesale into a new, much smaller Univa project purely for consistency, importing all of its operational overhead along with it.
Univa playbook
- Default ladder for every Univa project: PaaS first (Vercel/Cloudflare Pages) for anything that is a normal web app, moving to Cloud Run-style serverless containers the moment something does not fit PaaS, and reaching for ECS or Kubernetes only if a client contract specifically requires it and is large enough to justify the ongoing DevOps overhead. That last rung has not been needed for any Univa client so far, and should stay rare.
- Never propose Kubernetes to an SME client as a default. It is not a selling point for a five-person business; it is a recurring maintenance bill and a new failure surface. If a client already runs Kubernetes in-house and genuinely needs integration with it, that is different, and should be scoped and priced as its own specialist engagement.
- Keep one practice Dockerfile-plus-Cloud-Run pattern ready to go (see the hands-on exercise below), so the first time a client requirement genuinely needs a container, Ahnaf is executing a known pattern rather than learning Docker under deadline pressure.
- If a client already runs on AWS ECS or Azure Container Apps for other systems, build the new piece to fit what they already operate rather than migrating them onto Univa's default stack purely for internal consistency; that migration cost is rarely worth it to the client.
- Treat "we need Kubernetes" from a client as a claim to verify, not a spec to accept at face value. Ask what specific problem it is meant to solve; more often than not, a serverless container answers the same need at a fraction of the setup and ongoing cost.
- Signals that a Univa project genuinely needs Docker (as a quick checklist): the backend is not JavaScript/TypeScript, the workload needs to run continuously rather than per-request, it needs a specific system-level dependency Vercel's runtime does not offer, or the client explicitly hands over an existing containerized service to integrate with.
Hands-on exercise
Take any small script or API you already have, even something as simple as a ten-line Node or Python health-check endpoint that returns {"status": "ok"}. Then:
- Write a Dockerfile for it (base image, copy the code, install dependencies, expose a port, set the start command). Use a slim/alpine base image and add a non-root user, applying the security basics above.
- Build the image locally with
docker build -t practice-app .and run it locally withdocker run -p 8080:8080 practice-app, confirming it responds onlocalhost:8080. - Push the image (or let the platform build it directly from source, most Cloud Run setups can do either) and deploy it to Google Cloud Run's free tier.
- Hit the public Cloud Run URL and confirm the same response comes back from the internet.
The goal is to go from "I have code" to "it is running as a container on a serverless platform" in one sitting, so the mechanics stop being a mystery the next time an actual client requirement calls for it.
Stretch goal (optional, same evening): add a second, tiny service (even a static "hello" endpoint) and deploy it as its own separate Cloud Run service, then have the first service call the second one over HTTP. This is a small taste of what "multiple coordinated services without a cluster" actually looks like in practice. For extra practice with the local multi-container concept, wire both services together in a single docker-compose.yml and run them locally with one command before deploying either.
Self-check
- What is the difference between a Docker image and a Docker container?
- Why do containers start faster and use fewer resources than virtual machines?
- In Kubernetes, what is the difference between a Deployment and a Service?
- Why did Apache Mesos stop being a viable choice for new projects in 2026?
- Name the three managed "serverless container" services covered in this module, and one thing each one hides from the developer.
Answers
- An image is the immutable, read-only blueprint built from a Dockerfile; a container is a running instance of that image, an active process using it.
- Containers share the host machine's kernel and only isolate a process, filesystem view, and network; they do not need to boot an entire separate operating system the way a virtual machine does.
- A Deployment manages how many replicas of a pod should exist and how updates roll out across them; a Service gives that changing set of pods a single stable network address so other things can reliably reach them.
- The Apache Software Foundation formally retired it to the Apache Attic in 2025 due to prolonged inactivity: no new releases, contributors, issues, or security patches, making it unsupported for any new deployment.
- AWS ECS with Fargate hides the underlying EC2 server fleet; Google Cloud Run hides almost the entire cluster and scales all the way to zero; Azure Container Apps hides the Kubernetes cluster it is actually built on.
A note on how fast this landscape changes
Which orchestrator is "the standard," which managed service is cheapest, and which alternative project is still maintained shifts over a few years, not decades: Apache Mesos being retired to the Apache Attic in 2025 is a good example of a tool that was a reasonable answer a decade ago and is not one now. The concepts in this module (images versus containers, why orchestration exists, pods/deployments/services, the PaaS-to-Kubernetes ladder) are stable. The specific tool recommendations are a 2026 snapshot: re-verify a tool's current maintenance status before recommending it to a client, the same way this module verified Mesos's and Nomad's status before writing about them.
Further reading
- Docker overview: https://docs.docker.com/get-started/overview/
- Docker Dockerfile reference: https://docs.docker.com/reference/dockerfile/
- Docker Compose overview: https://docs.docker.com/compose/
- Docker Swarm documentation: https://docs.docker.com/engine/swarm/
- Kubernetes concepts, Pods: https://kubernetes.io/docs/concepts/workloads/pods/
- Kubernetes concepts, Deployments: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/
- Kubernetes concepts, Services: https://kubernetes.io/docs/concepts/services-networking/service/
- Cloud Native Computing Foundation, Kubernetes project page: https://www.cncf.io/projects/kubernetes/
- HashiCorp Nomad documentation: https://developer.hashicorp.com/nomad/docs
- Apache Mesos (archived, Apache Attic): https://attic.apache.org/projects/mesos.html
- AWS ECS documentation: https://docs.aws.amazon.com/ecs/
- Google Cloud Run documentation: https://cloud.google.com/run/docs
- Azure Container Apps overview: https://learn.microsoft.com/en-us/azure/container-apps/overview
- CNCF Cloud Native Landscape (interactive map of the ecosystem): https://landscape.cncf.io/