Containers and orchestration
Shrinking an image
A worked example that takes a 1.2 GB Python image under 150 MB. Measure each layer first, then fix the largest costs one at a time.
You inherit a FastAPI service. docker images reports 1.21 GB for its image. This page shrinks it. The method works for any image: measure first, then fix the biggest cost, then measure again.
Walking through the original
FROM python:3.12
WORKDIR /app
COPY . .
RUN apt-get update && apt-get install -y build-essential libpq-dev
RUN pip install -r requirements.txt
CMD uvicorn main:app --host 0.0.0.0 --port 8080Investigate before you change anything. docker history <image> lists the size of each layer. The open-source tool dive lets you browse what is inside each one. A typical breakdown looks like this. The sizes are approximate.
| Layer | Approx. size | Problem |
|---|---|---|
python:3.12 base | ~1,000 MB | Full Debian with compilers and headers |
COPY . . | ~60 MB | .git, a local .venv, test fixtures |
apt-get install | ~100 MB | Build tools the app does not need at runtime |
pip install | ~90 MB | Dependencies plus pip's download cache |
Step 1: add a .dockerignore
Exclude .git, .venv, __pycache__, tests/, *.md and .env*. That saves roughly 60 MB and stops the cache from breaking on every commit.
Step 2: multi-stage build on -slim
Compile in a builder stage that has build-essential. Install into a virtualenv, then copy only the virtualenv into a fresh slim stage. Swapping psycopg2 for psycopg[binary] may remove the need for libpq-dev and a compiler, because the binary package ships a prebuilt wheel.
FROM python:3.12-slim AS build
RUN python -m venv /venv
ENV PATH="/venv/bin:$PATH"
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt
FROM python:3.12-slim
ENV PATH="/venv/bin:$PATH" PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
COPY --from=build /venv /venv
WORKDIR /app
COPY src/ ./src/
RUN useradd --uid 10001 --no-create-home app
USER 10001
EXPOSE 8080
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8080"]Step 3: add it up
python:3.12-slim base ~125 MB
/venv (fastapi, uvicorn, pydantic,
psycopg[binary], httpx) ~ 20-30 MB
src/ ~ 1 MB
-------------------------------------------
total ~145-155 MBThat is roughly an 8x reduction. To go further, drop dependencies the code does not import. Teams often find pandas or boto3 pulled in for one helper. You can also move to distroless/python3-debian12 and copy the virtualenv in. The venv's Python version must match the distroless interpreter.
A heavy scientific stack is different. NumPy, SciPy and PyTorch wheels can be hundreds of MB to several GB alone, and no Dockerfile trick removes that.
Common mistakes
- Shrinking without measuring. Guessing often fixes the wrong layer.
- Leaving build tools in the runtime image. The compiler is needed once, so keep it in the build stage.
- Trimming the base but not the dependencies. Unused packages can be most of the remaining size.
Check yourself
- Which layer is largest in the original, and why?
Answer: The
python:3.12base, about 1,000 MB, because the full Debian image includes compilers and headers. - Why does a smaller image also help shutdown?
Answer: The exec-form
CMDletsSIGTERMreach uvicorn, so the service shuts down gracefully instead of being killed.
Key takeaways
- Measure each layer with
docker historyanddivebefore you change anything. - Use a
.dockerignore, a multi-stage build and a slim base. Together they cut this image by about 8x. - Remove unused dependencies, since they are often the last large cost.
- Smaller images also pull faster, so scaling and node replacement are faster.
Further learning
The next step is to see how Kubernetes schedules these containers and restarts them: Kubernetes core.
Related
Writing good Dockerfiles
Order Dockerfile steps so the cache works for you, build in stages so compilers stay out of the final image, and run as a non-root user.
Image layers and digests
An image is a stack of read-only layers named by their content hash. Learn why deleting a file does not shrink an image, and why production should pin digests, not tags.
Kubernetes core concepts
The control loop at the heart of Kubernetes, the objects you use every day, and how a request reaches a pod. Start here for the map of Kubernetes pages.