Weekly Review — Month 6 · Week 2 (Days 148–154)¶
📅 The Week in One Line¶
Took a working Go binary all the way to "production-shippable": tiny multi-stage images, a Compose local stack, distroless hardening, a build/test/scan CI pipeline, 12-factor config & file-based secrets, and signal-driven graceful shutdown.
✅ What I Completed¶
- Day 148 — Multi-stage Docker builds;
CGO_ENABLED=0static binary; cache-friendly layer order;-ldflagsversion stamp - Day 149 —
docker composestack (app + Postgres + Redis); DNS-by-service-name;depends_on+ healthcheck readiness - Day 150 — Image hardening;
scratchvs distrolessstatic; non-root, read-only rootfs, dropped caps; CA roots & tzdata - Day 151 — GitHub Actions:
go vet,go test -race -cover, module cache,govulncheck; least-privilege permissions - Day 152 — 12-factor config;
os.LookupEnv; accumulate-and-errors.Join;*_FILEsecret pattern; redaction - Day 153 — Signals & graceful shutdown;
signal.NotifyContext;srv.Shutdown(ctx);http.ErrServerClosedsentinel - Day 154 — Week review + recall
- Stdlib examples:
graceful,config,secrets,buildinfo - Exercises solved: 3 (
envconfig,secretsource,drain) — allgo testgreen
💡 Lessons Learned¶
- Go's static-binary model is what makes
scratch/distroless possible —CGO_ENABLED=0is the switch that unlocks it. - "Started" ≠ "ready" and "stop" ≠ "stopped": healthchecks gate the first gap, graceful
Shutdownthe second. Both come from refusing to assume timing. - Fail-fast vs drain-slow is the production rhythm: be intolerant of bad config at boot, patient with in-flight work at shutdown.
errors.Joinis the right tool for a config loader — one error value, all causes, stillerrors.Is-matchable.http.ErrServerClosedis success, not failure — guard it witherrors.Is.- Secrets belong in files, not env vars; env leaks through
inspect,/proc, child processes, and dumps. govulncheckbeats naive scanners because it's reachability-based: it only flags CVEs your call graph actually touches.
💪 Strengths (what clicked)¶
- The whole lifecycle now reads as one pipeline around a single binary.
signal.NotifyContext+ goroutine-ListenAndServe+ boundedShutdownis a pattern I can write from memory.- Layer-cache ordering and multi-stage discipline feel natural.
🧩 Weaknesses (what's still fuzzy)¶
- Draining long-lived connections (WebSocket/SSE) that
Shutdownignores. - BuildKit cache mounts behavior across CI runners (persistence, keys).
- Real secret managers (Vault/CSI) vs the plain
*_FILEconvention.
🔁 Spaced-Repetition Re-quiz (topics from earlier weeks)¶
- Q: (Channels, M2) What are the results of send/recv/close on a nil vs a closed channel?
A
nil channel: send and recv block forever; close panics. Closed channel: send panics, recv returns the zero value immediately with ok==false, closing again panics.
2. Q: (Errors, M3) Difference between errors.Is and errors.As? A
errors.Is tests whether any error in the chain matches a target sentinel value. errors.As finds the first error in the chain assignable to a target type and assigns it, so you can read its fields.
3. Q: (Slices, M1) Why can append mutate a slice the caller still holds? A
If capacity allows, append writes into the shared backing array in place, so an aliasing slice sees the change. Only when it must grow does it allocate a new array and decouple. Re-slicing/subslicing shares the same array until a reallocation.
4. Q: (Context, M5) Where does context.Context go in a function signature, and what must you never do with it? A
It's the first parameter, conventionally named ctx. Never store it in a struct for later use, and never pass a nil context — use context.TODO() if you don't have one yet.
5. Q: (gRPC, M5 W2) Which return-value detail is required for a panic-recovery interceptor to surface an error? A
Named return values — the deferred recover() must assign to the named err return so the panic becomes a returned error instead of crashing the server.
🎯 Action Items¶
- Add a graceful-drain path for SSE/WebSocket handlers (propagate shutdown ctx).
- Wire
govulncheckas a blocking CI gate on reachable HIGH/CRITICAL only. - Add a
Makefile/pre-push hook runninggo vet,go test -race -cover,govulncheck. - Pin base image tags by digest in the Dockerfile.
🚀 Next Week Goals¶
- Observability: Prometheus-style metrics, OpenTelemetry tracing, and structured logs correlated by request/trace IDs (building on
slogfrom Week 1). - Connect the graceful-shutdown lifecycle to flushing metrics/traces on exit.
📊 Metrics¶
| Hours | Days hit | Exercises | Commits | Avg confidence |
|---|---|---|---|---|
| 10.5 | 7/7 | 3 | 7 | 3.6/5 |
Suggested commit: docs(journal): month 6 week 2 review