Offered by Dead Brands
Containers package an application and its dependencies into one portable, immutable unit that runs the same everywhere.
Use it over MCP: connect to https://mcp.deadbrands.co/hub/mcp and call skills_get with slug containers-and-deployment-basics.
# Containers and Deployment Basics Credit: SteTy — published free in the Skills Hub Containers package an application and its dependencies into one portable, immutable unit that runs the same everywhere. This skill covers the mental model — images, containers, registries, layers — plus the craft around it: Dockerfile caching, twelve-factor design, healthchecks, environment-based config, and rolling vs blue-green deployment. ## Overview Three nouns explain most of it. An **image** is a read-only template: your code, runtime, libraries, and filesystem, built once and identified by a content hash (digest). A **container** is a running instance of an image — the image's read-only layers plus one thin writable layer on top, isolated by kernel features. A **registry** (Docker Hub, GHCR, ECR, or a private one) is the warehouse that stores images and serves them by name and tag so any machine can pull and run them. Build the image once, push it, and every environment runs the *identical bits* — that is the entire promise, and most container pain comes from accidentally breaking it (config baked in, `latest` tags, state stored inside). ## Core Principles - **Images are immutable; containers are disposable.** Never patch a running container — rebuild the image, replace the container. Anything written to a container's writable layer vanishes when it's removed. - **A container is not a VM.** A VM runs a full guest OS on virtual hardware via a hypervisor — gigabytes, minutes to boot. A container shares the **host kernel**, isolated by **namespaces** (its own view of PIDs, network, mounts) and limited by **cgroups** (CPU/memory quotas). No guest OS: megabytes, milliseconds to start, near-native speed. Corollary: containers must match the host kernel family — Linux containers on a Windows kernel need a Linux VM underneath. - **One concern per container.** One process (or one tight process family) per container; compose services from containers, not services inside one container. No SSH-ing in to fix things — that's VM thinking. - **Config comes from the environment, never the image.** The same image must run in dev, staging, and production; only environment variables differ. Secrets doubly so: an image is copied everywhere, so anything baked in is effectively published. - **Stateless processes, state in backing services.** Databases, object storage, and queues hold state *outside* containers, so any container can be killed and replaced at any time. ## Techniques **Layers and the build cache.** Nearly every Dockerfile instruction (`FROM`, `COPY`, `RUN`) produces a **layer** — an immutable filesystem diff, content-addressed by SHA-256 and shared between images (ten images on one base store the base once). Layers stack via a union filesystem (Overlay2) and present as one filesystem; the running container adds its writable layer on top. The build cache reuses a layer when its inputs are unchanged — but a cache miss **invalidates every layer after it**. Hence the golden rule: **order instructions from least-changing to most-changing.** **The dependency-first pattern.** Copy only the dependency manifest, install, then copy source: ```dockerfile FROM node:22-alpine WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci COPY . . CMD ["node", "server.js"] ``` Editing application code now reuses the expensive `npm ci` layer; only the final `COPY` rebuilds. Add a `.dockerignore` (node_modules, .git, logs) so irrelevant files don't bust the cache or bloat the image. Related cache craft: combine related `RUN` steps with `&&` and clean up in the *same* layer (`apt-get update && apt-get install -y … && rm -rf /var/lib/apt/lists/*`) — deleting files in a later layer hides them from view but the bytes still ship in the earlier layer. Use **multi-stage builds**: compile in a fat builder stage, `COPY --from=builder` only the artifacts into a slim runtime stage. Pin base images by tag *and* digest (`node:22-alpine@sha256:…`); `latest` is a reproducibility bug. In CI, the local cache is empty each run — use a registry or platform cache backend (`--cache-from` / `--cache-to`) to keep builds fast. **The twelve-factor app** (the design contract containers assume): I. one codebase, many deploys · II. declare dependencies explicitly · III. config in environment · IV. backing services as attached resources · V. strictly separate build, release, run · VI. stateless, share-nothing processes · VII. export services via port binding · VIII. scale by running more processes · IX. disposability — fast startup, graceful shutdown · X. dev/prod parity · XI. logs as event streams to stdout · XII. admin tasks as one-off processes. **Healthchecks.** Two different questions, two different checks — confusing them causes restart loops: - **Liveness:** "Is this process wedged?" If it fails, restart the container. Must be cheap and dependency-free (a dead dependency shouldn't make every container suicide). - **Readiness:** "Can this instance take traffic right now?" If it fails, remove it from the load balancer *without* restarting — it's still warming caches or waiting on a database. Docker's `HEALTHCHECK` instruction (e.g., `HEALTHCHECK CMD curl -f http://localhost:8080/health || exit 1`) lets the runtime report health; orchestrators (Kubernetes readiness/liveness probes, ECS health checks) act on the same idea. During deploys, readiness is the gate that makes zero-downtime possible. **Rolling deployment.** Replace instances gradually — a few at a time (Kubernetes: `maxSurge: 1`, `maxUnavailable: 0` starts new before stopping old, so capacity never dips). New instances must pass readiness before old ones drain (stop new traffic, let in-flight requests finish, then terminate). Cost: one pool of machines. Consequences: old and new versions run side by side, so changes must be backward-compatible — database changes use **expand/contract** (add the new column, deploy code that writes both, backfill, then drop the old column in a later release). Rollback is a reverse roll: slower than a switch flip. **Blue-green deployment.** Run two identical environments. Blue serves production; deploy the new version to green, test it there, then flip the router/load balancer so all traffic goes green. Rollback is flipping back — instant, which is blue-green's whole appeal. Costs: roughly 2× infrastructure during the switch, and discipline — the environments drift if anyone hand-tweaks one, and shared state (databases, background workers, feature-flag caches) couples them anyway. Canary releases are the middle path: send 1–5% of traffic to the new version, watch error rates and latency against pre-agreed go/no-go thresholds, then widen or roll back. ## Worked Examples *Cache-busting Dockerfile.* `COPY . .` before `RUN npm ci` means every one-line code change re-downloads every dependency: a 6-minute build. Reordering to dependency-first: the same change builds in 40 seconds because only the source layer and nothing above it changed. *Rolling update, 4 replicas.* Surge 1: a 5th pod (new version) starts, passes readiness → one old pod drains and exits. Fleet is briefly 4+1, then 3 old + 1 new + 1 starting… until all four run the new image. At no point does capacity drop below 4, and a bad readiness result halts the roll with three healthy old pods still serving. *Blue-green rollback.* Green (v2) takes traffic at 14:00; at 14:06 the error rate crosses the threshold. The router flips back to blue (v1, untouched and still warm): users are off the bad version in seconds, and v2 can be debugged in green without pressure. ## Common Pitfalls - **Storing data inside the container.** Uploads and SQLite files in the writable layer are lost on the first redeploy. Volumes or external services, always. - **`latest` and unpinned bases.** Two builds of the same Dockerfile produce different software; rollbacks stop being exact. - **One giant image with everything installed "just in case."** Bigger images pull slower, and every extra tool is attack surface. Multi-stage builds exist precisely to fix this. - **Running several services plus an init system in one container.** You've rebuilt a bad VM: no independent scaling, no independent restarts, invisible failures. - **Liveness probes that check dependencies** — the database hiccups, every container restarts at once, and the restart storm becomes the outage. - **Schema change + rolling deploy in one step.** Old pods still running can't read the new schema. Expand/contract across two releases. - **Forgetting graceful shutdown.** If the process dies on SIGTERM without draining, every deploy drops in-flight requests. Handle the signal; finish the request; then exit. ## Practice Drills 1. Take any small app you own. Write the naive Dockerfile, then the dependency-first multi-stage version. Compare image sizes and rebuild times after a one-line source change. 2. Add a `HEALTHCHECK` plus separate `/live` and `/ready` endpoints; make `/ready` fail while a dependency is down and confirm the container is unlisted from traffic but *not* restarted. 3. Deploy the app twice (blue/green) behind a simple reverse proxy. Flip traffic, break green deliberately, flip back, and time the rollback. 4. Do a rolling update with a deliberately incompatible second version and observe what breaks; then redo the change expand/contract style and roll cleanly. ## When to Use and Limits - **Use it when:** a task calls for containers and deployment basics, as laid out in the Techniques and Worked Examples above. - **Watch first for:** Storing data inside the container.; `latest` and unpinned bases.; One giant image with everything installed "just in case." (see Common Pitfalls). - **Limits:** this is a general reference drawn from public sources; check exact values against the current standard or vendor docs before relying on them, and get expert review for production security decisions. ## Quick Reference | | Container | Virtual machine | |---|---|---| | Isolation | Namespaces + cgroups on shared kernel | Hypervisor, full guest OS | | Size / boot | MBs / milliseconds–seconds | GBs / minutes | | Overhead | Near-native | Noticeable | | Immutability unit | Image (digest-pinned) | Disk image / snapshot | Deploy choice in one line: **rolling** for cheap, gradual, compatible changes; **blue-green** when instant rollback is worth a second environment; **canary** when you trust metrics more than tests. Whatever you choose, readiness gates traffic, versions coexist safely, and rollback is a rehearsed action — not a plan invented during an incident. --- *Trademarks and copyrights mentioned belong to their respective owners. Educational reference only; not affiliated with or endorsed by them.*