diff --git a/tech_docs/docker_compose_guide.md b/tech_docs/docker_compose_guide.md index bb098cd..c1f74e7 100644 --- a/tech_docs/docker_compose_guide.md +++ b/tech_docs/docker_compose_guide.md @@ -1,3 +1,150 @@ +Learning-GOAL: “I can read, reason about and harden any Dockerfile or `docker run` incantation I meet—without drowning in trivia.” + +Below is a **minimal, language-agnostic curriculum** expressed as executable **pseudocode**. +Each block is a self-contained kata you can type, break, fix and extend. +```plaintext +-------------------------------------------------- +0. Bootstrapping Sandbox +-------------------------------------------------- +function bootstrap(): + vm = create_ephemeral_vm() // multipass, lima, or cloud instance + install("docker engine") // or podman, nerdctl + alias d="docker" + return vm + +-------------------------------------------------- +1. Core Primitives (must be muscle memory) +-------------------------------------------------- +// 1.1 Image = read-only template +function image_primitives(): + img = build("Dockerfile_hello") // FROM alpine; COPY hello.sh /; CMD ["sh","/hello.sh"] + tag = tag(img, "demo:v1") + id = inspect(tag, ".Id") + layers = history(tag) // list of diff-IDs + return {img, tag, id, layers} + +// 1.2 Container = writable runtime instance +function container_primitives(): + c1 = run("-d --name c1 demo:v1") + top = exec(c1, "ps aux") // what’s running? + delta = diff(c1) // which files changed? + commit(c1, "demo:v1-smeared") // bake delta into new image + rm(c1) + +// 1.3 Registry = image transport +function registry_primitives(): + reg = start_local_registry() // docker run -d -p 5000:5000 registry:2 + push("demo:v1", reg) + rmi("demo:v1") + pull("demo:v1", reg) + +-------------------------------------------------- +2. Storage & State (volumes, bind mounts, tmpfs) +-------------------------------------------------- +function storage_primitives(): + vol = volume_create("db_data") + c2 = run("-d -v db_data:/var/lib/postgresql postgres:15") + c3 = run("-d --mount src=$(pwd),dst=/src,type=bind alpine sh -c 'sleep 3600'") + c4 = run("--tmpfs /tmp:size=100m alpine sh -c 'dd if=/dev/zero of=/tmp/big'") + cleanup([c2,c3,c4]) + +-------------------------------------------------- +3. Networking (CNB model) +-------------------------------------------------- +function networking_primitives(): + net = network_create("demo_net", driver="bridge") + nginx = run("-d --net demo_net --name web nginx") + curl = run("--rm --net demo_net alpine/curl curl http://web") + assert "Welcome to nginx" in curl.output + rm(nginx); network_rm(net) + +-------------------------------------------------- +4. Build Secrets & Multi-stage (no plaintext keys) +-------------------------------------------------- +function build_hardening(): + // Dockerfile.multi + // FROM golang:1.22 AS build + // RUN --mount=type=secret,id=gh_token \ + // git config --global http.extraheader "Authorization: Bearer $(cat /run/secrets/gh_token)" + // COPY . . + // RUN go build -o app . + // FROM gcr.io/distroless/static + // COPY --from=build /src/app /app + // CMD ["/app"] + img = build("--secret id=gh_token,env=GH_TOKEN -f Dockerfile.multi .") + scan(img) // trivy or grype + +-------------------------------------------------- +5. Security Profiles +-------------------------------------------------- +function security_primitives(): + c5 = run("--cap-drop ALL \ + --cap-add NET_BIND_SERVICE \ + --security-opt no-new-privileges:true \ + --user 1000:1000 \ + --read-only \ + --tmpfs /tmp \ + alpine:latest whoami") + assert c5.stdout == "1000" + +-------------------------------------------------- +6. Orchestration Lite (Compose as state-machine) +-------------------------------------------------- +function compose_primitives(): + services = load("compose.yml") // web, redis, db + stack = compose_up(services) + assert http_get("http://localhost:8080") == 200 + compose_down(stack) + +-------------------------------------------------- +7. Observability & Debug (no black boxes) +-------------------------------------------------- +function observability(): + c6 = run("-d demo:v1") + logs_tail(c6) + stats = container_stats(c6) // cpu, mem, blkio + enter(c6, "sh") // nsenter for low-level poke + rm(c6) + +-------------------------------------------------- +8. Cleanup Ritual +-------------------------------------------------- +function cleanup(containers): + for c in containers: + stop(c, timeout=5) + rm(c, volumes=True) + system_prune(all=True) + +-------------------------------------------------- +9. Mastery Checklist +-------------------------------------------------- +can_i: + ▢ explain the difference between an image, a layer, and a container + ▢ build multi-stage with secrets and non-root user + ▢ launch two containers on a custom bridge and capture traffic + ▢ run a read-only container that still writes temporary files + ▢ read `docker inspect` JSON and spot the security-options stanza + ▢ translate a `docker run` one-liner into compose YAML and back + ▢ upgrade base image without cache, then surgically bust only the vulnerable layer + +-------------------------------------------------- +10. Exit Condition +-------------------------------------------------- +if mastery_checklist.all_true(): + print("You now own the primitives. Dive into BuildKit, rootless, or Kubernetes.") +else: + iterate() + +-------------------------------------------------- +Usage Notes +-------------------------------------------------- +- Replace every function with real shell commands (`docker build …`, `docker network create …`). +- No single file is more than 40 lines; the goal is repetition, not rote memorization. +- Re-run the entire pseudocode weekly on a fresh VM to avoid stale muscle memory. +``` + +--- + Ah, I see—you’re asking for a **meta-comparison** that aligns with your framing of *"deterministic serendipity"* (predictable yet flexible configurations) and focuses on **functional parallels** between Docker Compose and Talos Linux’s approach, even if their primary use cases differ. Let’s reframe this as: ---