Deployment & Infra
Image layers: optimise a real Dockerfile
Reading about cache prefixes and multi-stage builds is not the same as making a four-minute CI build into a twenty-second one. Take a deliberately bad Dockerfile, drive it into all three failure modes — cache thrash, image bloat, and a leaked secret — then rewrite it and prove each fix with measurements.
Turn the unit’s mental model into a reproducible engineering loop: measure a baseline build, reorder for the cache prefix, split it multi-stage to ship slim, filter the build context, eliminate a secret from layer history, and verify every change with before/after numbers.
Take a deliberately bad Dockerfile (your own app or the starter described below) and rewrite it so a routine source edit rebuilds in seconds instead of minutes, the published image drops by at least half, and no secret survives in layer history — proving each step with measurements, not claims.
- A before/after table: cold build time, warm-rebuild-after-one-edit time, final image size, and transferred build-context size — measured, not estimated.
- The warm rebuild after a one-line source edit no longer re-runs the dependency install (visible as 'CACHED' in build output), and the final image is at least 50% smaller than the single-stage baseline.
- docker history (or layer extraction) on the final image shows no recoverable token and no .env file in any layer.
- A short write-up mapping each change to the mechanism it exploits: cache prefix, RUN-text caching, multi-stage discard, build-context filtering, and immutable-layer secret safety.
- Add a CI cache: enable BuildKit registry or GitHub Actions cache (--cache-from / --cache-to) so the warm-rebuild win survives across ephemeral CI runners, and show the install layer hitting cache on a fresh runner.
- Add a size-and-secret CI gate: fail the build if the final image grows beyond a threshold, and run a secret scanner (e.g. trivy or dockle) over the image layers so a re-introduced secret breaks the pipeline.
- Repeat the multi-stage exercise for a second language (a Go binary on scratch/distroless/static and a Node app on distroless) and compare how small each runtime can get and why.
- Pin the base image by digest and add a renovate/dependabot rule, then show how a digest change invalidates exactly the expected prefix of layers and nothing earlier.
This is the loop you will run on every real Dockerfile: measure first, reorder for the cache prefix so the expensive install stays a hit, merge update-and-install, go multi-stage to ship slim, filter the context with .dockerignore, and get secrets out of layer history with a secret mount or a discarded builder stage — verifying each change with before/after numbers under the same conditions. Doing it once on a deliberately bad Dockerfile makes the production rewrite muscle memory: order, slim, ignore, and never rm a secret.