Cloud / DevOps Engineer · Focus area 2: Containers and orchestration

CD1: Containers done well

Goal: package an app as a small, safe container image that builds quickly, runs as an ordinary user, tells Docker when it's ready and shuts down cleanly, so the same image can run on a laptop, a test server and production without surprises.

You'll need: Module 63 (Introduction to Linux), Module 64 (Linux Commands), Module 69 (Docker), Docker installed, and the RouteRani practice kit (download it from this path's Projects page).

Where this is used: almost every modern deploy ships a container image: a food delivery app's order service, a bank's statement generator, a government portal's login page. The image is the thing that moves through testing and into production, so its quality decides how fast, safe and predictable every release is.

How it differs: an app developer writes a Dockerfile that works on their computer. A DevOps engineer makes sure the image is small, rebuilds in seconds, carries no secrets, doesn't run as root and behaves well when hundreds of copies are started and stopped every day.

Time: reading about 2 hours · practice (the Try it tasks) about 3 hours · assignment about 3 hours.

Lessons

  1. 1Images, containers and layersReading 15–25 minutes, plus practice✓
  2. 2A Dockerfile that builds small and fastReading 15–25 minutes, plus practice✓
  3. 3Running containers safelyReading 15–25 minutes, plus practice✓
  4. 4Health checks and clean shutdownsReading 15–25 minutes, plus practice✓
  5. 5Tags, registries and keeping images healthyReading 15–25 minutes, plus practice✓

🛠️Assignment: A production-ready RouteRani image

This is the first part of Project 1.

  1. Rewrite app/Dockerfile with a pinned slim base, two stages, npm ci --omit=dev, and layers ordered for the cache.
  2. Add app/.dockerignore leaving out node_modules, test, .env, logs and the Dockerfile itself.
  3. Run as the node user, add a HEALTHCHECK on /ready, and start with the exec form CMD ["node", "server.js"].
  4. Build as routerani:1.0.0 and record the size before and after in docs/image.md.
  5. Show a clean shutdown: stop a running container and show its log and how long docker stop took.

Self-check before you finish:

✅Module check

Explain in a few sentences, in your own words:

What makes a container image ready for production?

Show a good answer

A good answer mentions: a pinned, slim base image; layers ordered so dependencies are cached; a .dockerignore and multi-stage build so only what the app needs is inside; no secrets in any layer; running as a non-root user with resource limits; a health check on a readiness path; starting the app directly so it receives SIGTERM and shuts down cleanly; and a unique version tag per build, pushed to a registry, scanned and rebuilt regularly.

📚Official references

Official documentation and guidance for this module's topics, chosen on 2 November 2026. Tools, laws and standards change, so check the current version before you rely on a detail. If a link has moved, search the same site.

📝Module quiz

10 questions · 10 marks each · 100 marks in all · pass mark 70% · retry as often as you like ·

1. A team's Dockerfile starts with COPY . . and then RUN npm install. Every one-line code change makes the build reinstall all

Why each option is right or wrong

  • “Switch the base image to a bigger one that already has the packages” (Incorrect): A different base can't know your app's packages, and bigger images are slower to pull.
  • “Run the build with --no-cache so Docker starts fresh each time” (Incorrect): That turns off the cache completely, so every build is slow.
  • “Add RUN npm cache clean --force at the end of the Dockerfile” (Incorrect): That can make the image a little smaller but doesn't stop the reinstall.
  • “Copy the lock file, run npm ci, then copy the code” is correct.

    Docker reuses a cached layer only if everything before it is unchanged. With the lock file copied first, code changes leave the install layer cached.

    • Result: rebuilds take seconds.
    • Also: npm ci installs exactly what the lock file lists.

📖 Revise: Lesson 2: A Dockerfile that builds small and fast

Question ID sp-cloud-devops-01-q1 · Report a mistake in this question
2. A developer runs docker exec into a production container to install a missing system package, and the fix works. A week later the

Why each option is right or wrong

  • “Docker undoes changes made inside a container every few days” (Incorrect): Docker doesn't undo changes on a timer; the container was replaced.
  • “The package was installed for the wrong user inside the container” (Incorrect): The fix worked, so the user wasn't the problem.
  • “docker exec changes only last until someone next signs in” (Incorrect): Changes inside a container last until the container is removed, not until a sign-in.
  • “A redeploy started new containers from the unchanged image” is correct.

    Containers are disposable copies of the image. The change lived in one container's writable layer and disappeared when it was replaced.

    • Fix: add the package in the Dockerfile and build a new image.
    • Habit: treat images as the only source of truth.

📖 Revise: Lesson 1: Images, containers and layers

Question ID sp-cloud-devops-01-q2 · Report a mistake in this question
3. A Dockerfile has ENV DB_PASSWORD=Sup3rS3cret so the app can connect. The image is pushed to the company's private registry. What's the

Why each option is right or wrong

  • “The password is visible only while that container is running” (Incorrect): It's part of the image, readable without running anything.
  • “Docker encrypts environment values, so only the build server knows it” (Incorrect): Docker stores them as plain text in the image's settings.
  • “The registry hides ENV values from users who didn't build the image” (Incorrect): Registries store the image as it is, settings included.
  • “Anyone who can pull the image can read it with docker inspect” is correct.

    Image settings and layers travel with the image. Everyone with pull access, now or later, can read the password.

    • Fix: pass it at run time, or mount it as a secret file.
    • Then: change the password, since it has been exposed.

📖 Revise: Lesson 3: Running containers safely

Question ID sp-cloud-devops-01-q3 · Report a mistake in this question
4. docker stop takes exactly 10 seconds on every RouteRani copy, and requests in progress fail. The Dockerfile ends with

Why each option is right or wrong

  • “The app is too slow to finish its requests within 10 seconds” (Incorrect): Requests take milliseconds; the same 10 seconds every time points to the signal, not the work.
  • “Docker's stop timeout is set too low and should be raised to 60 seconds” (Incorrect): A longer wait just delays the kill, since the app didn't hear the signal.
  • “The health check is still running when the container stops” (Incorrect): Health checks don't delay docker stop.
  • “npm is the main process and doesn't pass SIGTERM on to Node.js” is correct.

    The first process gets the stop signal. If it doesn't forward it, Node.js doesn't start its clean shutdown and is killed after the timeout.

    • Fix: CMD ["node", "server.js"].
    • Check: docker stop now returns in under a second.

📖 Revise: Lesson 4: Health checks and clean shutdowns

Question ID sp-cloud-devops-01-q4 · Report a mistake in this question
5. A RouteRani copy can't reach its database, but the Node.js process is running fine. Which health signal should change, and what should

Why each option is right or wrong

  • “Liveness should fail so Docker restarts the copy straight away” (Incorrect): Restarting doesn't fix a database problem, and if every copy restarts together, the site goes down.
  • “Neither should change, because the process itself is healthy” (Incorrect): Requests to this copy would fail, so users would see errors.
  • “Both should fail so the copy is removed and replaced by a new one” (Incorrect): A new copy hits the same database problem, so replacing it achieves nothing.
  • “Readiness should fail so the copy stops receiving traffic” is correct.

    The process works but can't serve requests. Taking it out of the load balancer protects users; it rejoins when the database answers.

    • RouteRani: /ready checks the database; /health doesn't.
    • Restart: only if liveness fails.

📖 Revise: Lesson 4: Health checks and clean shutdowns

Question ID sp-cloud-devops-01-q5 · Report a mistake in this question
6. A team's images are 2 GB and take four minutes to pull when new copies start during a traffic spike. Which change helps most?

Why each option is right or wrong

  • “Pull images in parallel from two registries at the same time” (Incorrect): Two sources can't make 2 GB small, and few platforms can pull one image from two places.
  • “Compress the image file before pushing it to the registry” (Incorrect): Registries already compress layers; the content is still 2 GB once unpacked.
  • “Add more CPU to each server so unpacking the image goes faster” (Incorrect): The time is mostly downloading; more CPU barely helps.
  • “A slim base, a .dockerignore file and a multi-stage build” is correct.

    Most size comes from a full base, build tools and copied junk. Removing them shrinks the image several times over.

    • RouteRani: about 1.6 GB down to about 240 MB.
    • Bonus: fewer packages to patch.

📖 Revise: Lesson 2: A Dockerfile that builds small and fast

Question ID sp-cloud-devops-01-q6 · Report a mistake in this question
7. The same tag myapp:latest runs on three servers, but users report different behaviour depending on which server they reach. What's the

Why each option is right or wrong

  • “Docker caches images differently on each server's operating system” (Incorrect): Same contents give the same digest wherever they run.
  • “One server ignores the tag and runs its own local copy instead” (Incorrect): Docker runs whatever image the tag points to on that server.
  • “The registry gives each server a slightly different version on purpose” (Incorrect): Registries serve whatever the tag points to; they don't vary it.
  • “The servers pulled latest at different times and run different builds” is correct.

    A tag is a movable name. Each server got whatever latest pointed to when it pulled, so one name covers several builds.

    • Fix: a unique version tag per build.
    • Check: compare image digests on the servers.

📖 Revise: Lesson 5: Tags, registries and keeping images healthy

Question ID sp-cloud-devops-01-q7 · Report a mistake in this question
8. A container runs as root. Someone says "it's fine, it's in a container". What's the strongest reason to add a USER anyway?

Why each option is right or wrong

  • “Root inside a container is slower than an ordinary user” (Incorrect): Speed is the same either way.
  • “Docker refuses to publish ports for containers running as root” (Incorrect): Docker publishes ports either way.
  • “Root is needed only during the build, so it wastes memory at run time” (Incorrect): The user doesn't change memory use.
  • “A container escape would start the attacker as the host's root user” is correct.

    Containers share the host kernel. Without extra protections, root inside has the same user ID as root outside.

    • Fix: USER node before CMD.
    • Bonus: platforms with runAsNonRoot will accept the image.

📖 Revise: Lesson 3: Running containers safely

Question ID sp-cloud-devops-01-q8 · Report a mistake in this question
9. A healthy-looking container suddenly stops with exit code 137 a few times a day, each time under heavy traffic. What does this point to?

Why each option is right or wrong

  • “The app crashed because of a syntax error in its code” (Incorrect): A syntax error stops it at start-up, not only under load.
  • “The health check timed out and Docker stopped the container” (Incorrect): Plain Docker doesn't stop containers for failing health checks.
  • “Someone ran docker stop on it while it was busy with requests” (Incorrect): A normal stop of a well-behaved app ends with exit code 0 or 143.
  • “It went over its memory limit and the kernel killed it” is correct.

    Exit code 137 means killed with SIGKILL; with a memory limit set, the usual cause is the kernel stopping it for using too much.

    • Check: docker inspect shows OOMKilled: true.
    • Next: measure real use, then fix a leak or raise the limit.

📖 Revise: Lesson 3: Running containers safely

Question ID sp-cloud-devops-01-q9 · Report a mistake in this question
10. A team wants to go back to yesterday's version quickly if today's release fails. What should their image process include?

Why each option is right or wrong

  • “Rebuilding yesterday's code from Git whenever a rollback is needed” (Incorrect): Rebuilding takes time and may pull newer dependencies, so it may not match yesterday's image.
  • “Overwriting the stable tag with each release so it's easy to find” (Incorrect): Once overwritten, the previous image has no name to go back to.
  • “Saving each image as a file on the developer's laptop as a backup” (Incorrect): Laptops aren't a reliable or shared place for production images.
  • “Unique tags per build that aren't reused, kept in the registry for a while” is correct.

    Each build keeps its own name. Going back is a deploy of yesterday's exact image, with no rebuild.

    • Plus: lifecycle rules delete only old tags.
    • Used in: Project 2's rollback.

📖 Revise: Lesson 5: Tags, registries and keeping images healthy

Question ID sp-cloud-devops-01-q10 · Report a mistake in this question

ProLearners Assist