Back to path
IntermediateLedgerline · Project 7 of 12 ~6h· 5 milestones

Harden the container it ships in

Continues from the last build: Dependencies are scanned, an SBOM exists, and updates are automated.

Last rung you got dependency risk under control: Trivy scans the lockfile, an SBOM lists every package that ends up in the image, and Dependabot opens PRs when a CVE lands.

multi-stage Docker buildsdistroless and minimal base imagesnon-root container usersread-only root filesystemsTrivy image and secret scanningcontainer layer forensics with docker historyCI/CD build gates and least-privilege workflow permissionspinning GitHub Actions to commit SHAs

What you'll build

The image is a multi-stage build on a distroless or slim runtime base with no toolchain and no shell where you can avoid it, and no secrets in any layer, verified with docker history and trivy image --scanners secret. It runs as a non-root USER with a read-only root filesystem and a scoped tmpfs. Trivy image scanning is wired into the pipeline as a hard gate: HIGH and CRITICAL findings fail the build and the image is never pushed to the registry. You can point to a real before and after finding count, and explain exactly what remains in the after count and why it cannot be fixed by changing your Dockerfile.

See how we teach, before you sign up

You don't just get code dumped on you. Every starter file and every solution is explained line-by-line, in plain English. Here's one real file from this project:

Dockerfiledockerfile
FROM node:20
WORKDIR /app
COPY . .
COPY .env /app/.env
RUN npm install
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]

Reading this file

  • FROM node:20full Debian-based image, ships apt, python3, build-essential, and a shell, all of it ends up in the image you run in production
  • COPY .env /app/.envbakes the secret file into a layer permanently, deleting it in a later RUN would not remove it from image history
  • RUN npm installsingle unstaged install pulls in devDependencies too, and there is no later stage to leave them behind
  • CMD ["npm", "start"]runs as whatever user the base image defaults to, root, because there is no USER instruction anywhere in this file

The current production Dockerfile. Single stage, full Debian base, root user, and a secret copied straight into a layer.

That's 1 of 8 explained code blocks in this single project.

The build, milestone by milestone

  1. 1

    Scan the image and establish the baseline

    5 guided steps

    You cannot prove you hardened anything without a before number. Teams that skip this end up hand-waving 'much more secure' with nothing to show a reviewer or an auditor.

  2. 2

    Multi-stage build and minimize the runtime image

    5 guided steps

    Every extra binary in the final image, a shell, a compiler, curl, apt, is a tool a compromised process can use to explore, pivot, or exfiltrate. The build toolchain has zero reason to exist in production.

  3. 3

    Drop root and privileges

    5 guided steps

    A root process in a container is one kernel exploit or misconfigured mount away from root on the host. The injection and IDOR bugs fixed in earlier rungs become far less dangerous to an attacker if the shell they eventually land in has no write access and no root.

  4. 4

    Re-scan and compare, document what remains

    5 guided steps

    A green scan is not the same as secure. A reviewer or an auditor will ask what is still open and why you accepted it, and an honestly documented exception beats a silently ignored finding every time.

  5. 5

    Gate image builds in the pipeline

    6 guided steps

    A hardened Dockerfile that nobody enforces drifts back toward root and bloat within a few pull requests. The gate is what makes the hardening durable instead of a one-time cleanup.

What's inside when you start

3 starter files, ready to clone
5 guided milestones
5 full reference solutions
8 code blocks explained line-by-line
5 "is it working?" checks
6 interview questions it prepares you for

You'll walk away with

A multi-stage Dockerfile with a build stage and a minimal (distroless or slim) runtime stage
A complete .dockerignore that keeps .git, .env, and node_modules out of the build context
A hardened runtime that runs as a non-root user with a read-only root filesystem and a tmpfs for /tmp
A documented before/after Trivy scan report (security/container-hardening-report.md) with real finding counts
A .trivyignore file where every entry has a CVE id, a justification, and an expiry date
A CI workflow that scans the built image with Trivy and fails the job, blocking push, on HIGH or CRITICAL findings

This is portfolio-grade. Build it free.

Sign up to unlock every milestone step-by-step, the code skeletons, full reference solutions, and checkable tasks, with your progress saved as you build.

Start building