CSE Roadmap

Common tier · about 8 hours · 7 parts

Part 6 of 7 · 4 min read

Exercises

Try each exercise before opening its hint.

Easy

Easy 1: Build and run it (30 minutes)

Install Docker. In code/ship-it run docker build -t ship-it:dev ., start the container, and call /health and /echo with curl. Run docker images and note the image size.

Done when: curl localhost:8000/health returns {"status":"ok"} from inside a container.

Hint

on Windows keep Docker Desktop running; if port 8000 is busy, map -p 8080:8000.

Easy 2: Prove the cache works (20 minutes)

Rebuild once (everything cached). Change one line in app.py and rebuild: note which steps say CACHED. Then add a comment line to requirements.txt and rebuild again. Write one sentence explaining the difference.

Done when: you can predict which layers rebuild before you run the build.

Hint

docker build prints CACHED next to reused steps; --progress=plain shows more detail.

Easy 3: Image or container? (10 minutes)

Decide whether each is an image or a container: (a) what docker build produces, (b) what docker run starts, (c) the thing you push to a registry, (d) the thing that has a running process and a writable layer.

Done when: all four are right and you can explain the relationship in one sentence.

Medium

Medium 1: Configure without rebuilding (20 minutes)

Start the container twice, once with -e APP_GREETING=Namaste and once with the default. Show that /echo changes with no rebuild. Then run docker history ship-it:dev and check that no greeting value is stored in the layers.

Done when: the same image gives two behaviours.

Hint

docker history lists the command that created each layer.

Medium 2: Compose it (25 minutes)

Run docker compose up --build with the provided file. Add a second service redis (image redis:7-alpine) and, without using Redis yet, show with docker compose ps that both run. Stop everything cleanly.

Done when: both services are listed as running and docker compose down removes them.

Medium 3: Check the image is hardened (30 minutes)

The example already runs as a non-root user and has a .dockerignore. Prove it. Run docker run --rm ship-it:dev id and read the user. Put a file named .env in the folder, rebuild, and show it is not inside the image (docker run --rm ship-it:dev ls -a /app). Then remove .env from .dockerignore, rebuild, and observe what changes. Put the line back.

Done when: you can show the non-root user, show .env is absent with the ignore file, and explain what the ignore file protects against.

Hint

if id prints uid=0, you are root; appuser has a different number.

Hard

Hard 1: Make CI real (60 minutes)

Put ship-it in a new GitHub repository with the workflow at .github/workflows/ci.yml. Push and watch the Actions tab go green. Then push a branch that breaks a test, open a pull request and see it fail. Turn on branch protection so the pull request cannot be merged while red.

Done when: a red pull request is blocked, and the fix turns it green.

Hint

Settings, Branches, "Add rule", then require the status check named test.

Hard 2: Catch your own leak (45 minutes)

On a branch, add the line API_KEY = "abcdefghijklmnopqrstuv" to a Python file and push. Confirm the secret-scan step fails the pipeline. Replace the home-made scanner with gitleaks (use its official GitHub Action) and compare what each catches. Use a fake key only.

Done when: both scanners fail on the planted key and pass on the fixed branch, and you have listed one thing each catches that the other misses.

Hint

never push a real key, even to test.

Hard 3: A health check that means something (50 minutes)

The Dockerfile already has a HEALTHCHECK that calls /health, but /health always says ok. Make /health return 503 until the app can reach a Redis service in Compose. Start the stack with Redis stopped and show the container reported as unhealthy, then start Redis and show it turn healthy.

Done when: docker compose ps shows unhealthy, then healthy, driven by a real dependency.

Hint

the curl program may not exist in a slim image; use a short Python one-liner in the check command instead. Compose can also use depends_on with a health condition.