Docker — Containers for DevOps
What you'll learn
- Containers vs virtual machines (why containers won)
- The Dockerfile → image → container flow
- Docker's role in the DevOps pipeline
- The commands you'll run daily
The container, DevOps-style 📦
You met Docker in the backend track as the fix for "works on my machine." For DevOps, it's the standard unit of deployment — the thing pipelines build and ship.
Containers vs Virtual Machines — why containers took over:
- A VM virtualizes a whole computer, including a full guest operating system. It's heavy (gigabytes) and slow to start (minutes).
- A container shares the host's OS kernel and packages just your app + its dependencies. It's tiny (megabytes) and starts in milliseconds.
Think of VMs as separate houses (each with its own foundation, plumbing, everything) and containers as apartments in one building (sharing the structure, but isolated inside). You can run far more containers than VMs on the same hardware — which is exactly what you want when deploying and scaling.
The core flow (recap, now as your daily DevOps loop):
Dockerfile → docker build → Image → docker run → Container
(the recipe) (the box) (running app)
The everyday commands:
docker build -t myapp .— build an image from the Dockerfile in this folder.docker run myapp— start a container from that image.docker ps— list running containers.docker push myapp— upload the image to a registry (like Docker Hub) so servers can pull it.
Where it fits in DevOps: your CI/CD pipeline (next lesson) builds a Docker image on every push, tests it, pushes it to a registry, and deploys that exact image to production. Because the image is identical everywhere, "it worked in testing" reliably means "it works in production." Containers make deployments predictable — the foundation everything else in DevOps stands on.
Layers and the build cache 🧱
A Docker image isn't one solid block — it's built from layers, stacked like transparent sheets. Each line in a Dockerfile creates one layer:
FROM node:20 # layer 1: base OS + Node
COPY package.json . # layer 2: just the manifest
RUN npm install # layer 3: installed dependencies
COPY . . # layer 4: your actual source code
CMD ["node", "app.js"]
Docker caches each layer. If you rebuild and a layer's inputs haven't changed, Docker reuses the cached layer instantly instead of redoing the work — and once one layer changes, every layer after it must be rebuilt.
This is why the order of your Dockerfile matters enormously. Notice package.json is copied and npm install run before the rest of the source code is copied. Your dependencies change rarely; your source code changes on every commit. If you copied everything first, any code change would invalidate the cache and force a full, slow npm install on every single build. Ordering slow, rarely-changing steps first — and fast, frequently-changing steps last — is the single biggest lever for fast CI/CD builds.
Multi-stage builds take this further: use one stage with all the build tools to compile your app, then copy only the finished output into a tiny final image — shrinking a 1GB image with compilers and source code down to a lean, secure production image with just the binary.
Simulate Docker's layer cache. Write layersToRebuild(changedLayerIndex, totalLayers) — given which layer index changed (0-based) and the total number of layers, return an array of the layer indexes that must be rebuilt (the changed layer and everything after it; earlier layers stay cached).
Example: layersToRebuild(2, 5) → [2, 3, 4] (layers 0 and 1 stay cached)
Real-world case study: the leaky secrets in public images 🔍
A recurring, well-documented problem across the Docker ecosystem: developers bake secrets — API keys, AWS credentials, private certificates — directly into an image, usually by COPY-ing a .env file or hardcoding a key in a RUN command, then push that image to a public registry. Security researchers have repeatedly scanned Docker Hub's public images and found thousands containing live, extractable credentials, because every layer of an image is inspectable — even a secret added in one layer and "deleted" in a later layer is still sitting in the earlier layer's history, just like an unwanted file in Git history.
The fix mirrors what you learned about .gitignore: secrets belong in environment variables injected at runtime (docker run -e API_KEY=... or a secrets manager), never baked into the image itself. Multi-stage builds also help here — if your secret is only needed to build the app (like a private package registry token), keep it in an early build stage that never makes it into the final shipped image.
This is why scanning images for embedded secrets (part of the DevSecOps pipeline you'll meet later) is now standard practice at serious engineering organizations.
Question 1 of 5
How does a container differ from a virtual machine?
Question 2 of 5
What's the correct Docker flow?
Question 3 of 5
Why do containers make DevOps deployments predictable?
Question 4 of 5
Why does Dockerfile order matter for build speed?
Question 5 of 5
Why is baking a secret into a Docker image dangerous, even if a later layer 'deletes' it?
Summary
- Containers share the host kernel — tiny and fast vs heavyweight VMs
- Flow: Dockerfile → build → image → run → container; push to a registry
- Docker is the DevOps unit of deployment: build once, run identically everywhere
- Predictable images make "works in prod" reliable
- Images are built from cached layers — ordering rarely-changing steps first speeds up every build; multi-stage builds keep final images small
- You simulated Docker's layer cache, and saw why baked-in secrets leak even after being "deleted"
Next: CI/CD Pipelines — the automation that builds, tests, and ships these containers on every commit.
Practice
Reading is not learning until you run something. Pick one.