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:
- ~10 layers total — 4 from the
python:3.11-slimbase image plus 6 from the Dockerfile instructions (WORKDIR, COPY requirements.txt, RUN pip, COPY app.py, EXPOSE, CMD). - 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. - 4 layers come from
python:3.11-slim. You can see these by runningdocker history python:3.11-slim— they appear as the oldest entries in theurl-api:0.1history too. - Compressed image size shown by
docker images url-api:0.1is typically ~175–190 MB. The uncompressed size fromdocker 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:
useraddruns beforeWORKDIRso the user exists when file ownership is applied.COPY --chown=app:appsets file ownership at layer creation time — cheaper than a separateRUN chown -Rwhich would create another layer.USER appswitches the runtime user. TheRUN pip installstep still runs as root (a build-time operation) — that is fine and intentional.- Verify:
docker exec url-api idshould printuid=1000(app) gid=1000(app) groups=1000(app).
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:
COPY requirements.txt .— CACHED (file content unchanged)RUN pip install ...— CACHED (parent layer unchanged)COPY app.py .— REBUILDS (file content changed, even though it's just a comment)EXPOSEandCMDmetadata — rebuild (tiny, near-zero cost)
(b) Edit a comment in requirements.txt:
COPY requirements.txt .— REBUILDS (file content changed)RUN pip install ...— REBUILDS (its input layer changed; Docker cannot know the output would be identical without actually running pip)COPY app.py .— rebuilds (parent layer changed)
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 .:
- Every layer from the swap point onward REBUILDS. Cache keys include the instruction’s position in the Dockerfile as well as its text. Moving an instruction invalidates everything downstream of it, even if the instruction text is identical.
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.