Lesson 01 Code to Container
Theme:

Solutions — Code to Container

Lesson 01 · Cloud Infrastructure & DevOps

Solution to Task 1 — Inspect the layers in detail

Representative output from a fresh build of url-api:0.1:

docker history --no-trunc url-api:0.1
# IMAGE          CREATED       CREATED BY                                               SIZE
# sha256:abc...  5 min ago     CMD ["uvicorn", "app:app", "--host", "0.0.0.0", ...]     0B
# sha256:def...  5 min ago     EXPOSE map[8000/tcp:{}]                                  0B
# sha256:234...  5 min ago     COPY app.py .                                            2.1kB
# sha256:567...  5 min ago     RUN pip install --no-cache-dir -r requirements.txt       58.4MB
# sha256:890...  5 min ago     COPY requirements.txt .                                  64B
# sha256:abc...  5 min ago     WORKDIR /app                                             0B
# sha256:...     2 weeks ago   (python:3.11-slim base — 4 layers)                      ~120MB total

Answers:

  1. ~10 layers total — 4 from the python:3.11-slim base image plus 6 from the Dockerfile instructions (WORKDIR, COPY requirements.txt, RUN pip, COPY app.py, EXPOSE, CMD).
  2. The pip-install layer is the largest, at approximately 58 MB. The instruction that produced it: RUN pip install --no-cache-dir -r requirements.txt.
  3. 4 layers come from python:3.11-slim. You can see these by running docker history python:3.11-slim — they appear as the oldest entries in the url-api:0.1 history too.
  4. Compressed image size shown by docker images url-api:0.1 is typically ~175–190 MB. The uncompressed size from docker image inspect --format '{{.Size}}' url-api:0.1 (in bytes) is usually 5–10% larger.
Common mistake: assuming layer sizes add up exactly to the image size. They almost do, but Docker deduplicates identical layers across images. Pull a second image based on the same python:3.11-slim digest and you will see the four base layers listed in both histories but stored once on disk.

Solution to Task 2 — Make the container not-quite-root

The patched Dockerfile:

FROM python:3.11-slim

# Create a non-root user before WORKDIR so the user exists when file ownership is set.
RUN useradd --create-home --uid 1000 app

WORKDIR /app

COPY --chown=app:app requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY --chown=app:app app.py .

USER app

EXPOSE 8000
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]

Key points:

Common mistake: placing USER app before RUN pip install. pip then runs as a non-root user and either fails with a permission error or installs into ~/.local, which complicates $PATH and may not be found by uvicorn at runtime.

Solution to Task 3 — Predict cache behaviour

(a) Edit a comment in app.py:

(b) Edit a comment in requirements.txt:

This is the "I just changed a comment and waited 60 seconds" frustration. Docker’s cache key is input content, not output semantics. Lesson 02 introduces BuildKit cache mounts that mitigate this for pip specifically.

(c) Swap WORKDIR /app and COPY requirements.txt .:

Lesson 02 turns this ~180 MB image into a <100 MB one without changing the app code — by separating build-time and runtime dependencies into two stages. The cache principles from Task 3 apply equally to multi-stage builds.