Step 1: Examine the Dockerfile
Open materials/vulnerable-platform/Dockerfile.
The Dockerfile builds the ordering platform image. Read through it. Notice what is there and what is missing. The base image tag, the way dependencies are installed, the runtime configuration. Pay particular attention to whether a USER directive exists.
Read the Dockerfile for the ordering platform. Identify the base image, the user the container runs as, and any security-relevant configuration decisions.
If there is no USER directive, the container runs as root by default. That means an attacker who compromises the web application gets root access inside the container. In Samir's case, the ordering platform handles buyer logins, order data, and pricing terms -- and if compromised, root access extends to anything the container can reach.
Verify the current state.
docker compose exec app whoami
Step 2: Non-root USER
Direct Claude to modify the Dockerfile so the application runs as a non-root user.
Modify the Dockerfile to create a non-root user and run the application as that user. Make sure the user has access to the directories the application needs.
This will likely break things. The application may expect root permissions for writing to log directories, accessing socket files, or binding to certain ports. AI commonly generates a Dockerfile with the USER directive but does not account for all the directories the application writes to. When the container fails to start, read the error logs and fix the permissions issue. Working through these failures is part of the hardening process -- you are learning where the application assumes root.
Rebuild and test.
docker compose build app
docker compose up -d app
docker compose exec app whoami
The output should show the non-root user name, not "root."
Step 3: Read-only filesystem
A container with a read-only root filesystem limits what an attacker can do after compromise. They cannot write scripts, modify application code, or install tools. But the application still needs to write to certain directories -- temporary files, session data, logs.
Direct Claude to configure the container with a read-only filesystem, mounting writable directories as tmpfs volumes for the paths that need write access.
Configure the app container with a read-only root filesystem. Identify which directories need write access and mount them as tmpfs volumes. Update the docker-compose.yml.
Check for hardcoded secrets while you are working with the Dockerfile and compose file. Database credentials in environment variables or Dockerfile ENV directives persist in the image history -- anyone with access to the image can retrieve them using docker history. AI routinely embeds credentials in Dockerfiles without flagging this as a risk. Build-time hardening and runtime hardening are different problems.
Step 4: Trivy vulnerability scanning
Trivy is a container vulnerability scanner. It inspects a container image and reports known vulnerabilities (CVEs) in the operating system packages and application libraries included in the image.
Run a Trivy scan on the ordering platform's Docker image. Review the results by severity.
trivy image kabylie-gold-app
The output will show vulnerabilities you did not introduce. The base image -- the foundation the developer chose when building the platform -- carries its own dependencies, and those dependencies carry their own vulnerabilities. This is supply chain risk. You are responsible for vulnerabilities in code you never wrote, because your application runs on top of it.
Review the results. Note the CVE IDs, severity levels, affected packages, and whether fixed versions exist. AI commonly defaults to scanning only CRITICAL severity when configuring Trivy. Medium-severity vulnerabilities may be individually low-risk but exploitable in combination. The severity threshold you choose for your pipeline is a judgment call.
Step 5: Pin the base image
The Dockerfile uses a mutable tag like python:3.11. That tag points to a different image every time the upstream maintainer pushes an update. Today's python:3.11 is not the same as last month's python:3.11. A mutable tag means your build is not reproducible -- and a compromised upstream image silently changes your application's foundation.
Direct Claude to pin the base image to a SHA digest.
Replace the mutable base image tag in the Dockerfile with a SHA digest pin. Explain why this prevents silent base image changes.
Pinning to a digest means the build always uses the exact same image. Updates require a deliberate decision to change the pin -- not an invisible upstream change.
Step 6: CI pipeline with GitHub Actions
Manual Trivy scans work, but they depend on someone remembering to run them. A CI pipeline runs the scan automatically on every push, transforming container hardening from a manual audit into an automated gate.
Direct Claude to create a GitHub Actions workflow file.
Create a GitHub Actions workflow that runs Trivy on every push to the repository. Configure it to scan the Docker image and fail the build if CRITICAL or HIGH vulnerabilities are found. Place the workflow file in .github/workflows/.
Push a commit and verify the workflow triggers.
The severity configuration matters. Scanning only for CRITICAL misses HIGH-severity vulnerabilities that may be directly exploitable. Scanning for everything generates noise that makes the pipeline impractical. The threshold is a decision you make based on the system's risk profile -- Samir's platform handles commercially confidential buyer data, so a stricter threshold is justified.
Step 7: Document hardening decisions
Every hardening decision needs documented rationale. Not just "what was changed" but "why it was changed and what breaks if someone reverses it."
Document all container hardening decisions. For each change, include: what was modified, why, the verification method, and what would happen if a future maintainer reversed the change.
Refer to materials/threat-model-template.md -- the hardening decisions connect to specific STRIDE threats you identified during the assessment. Non-root USER addresses elevation of privilege. Read-only filesystem addresses tampering. Pinned base image addresses supply chain tampering.
Also review materials/ttp-selection-guide.md -- the container security testing category describes exactly the areas you just hardened: runtime configuration, base image security, and build-time security.
The documentation is operational knowledge. The developer in Algiers who maintains the platform after this engagement needs to understand why the container runs as a non-root user and what will break if they switch it back to root for convenience during a debugging session.
Check: Container runs as non-root user (verify with docker exec whoami). Trivy scan completes and the student can name at least one CVE from the base image. CI pipeline triggers on push.