StudyToCert

All certifications / AI-200 / Lessons

Microsoft Certified: Azure AI Cloud Developer Associate AI-200 (replaced AZ-204) · Domain 1: Develop containerized solutions on Azure

Dockerfiles for Python apps: base images, multi-stage builds, non-root users, .dockerignore

▶ Watch the overview video

Last reviewed September 25, 2026 · Leer en español

A Dockerfile is the recipe that turns your Python source code into a container image: a read-only, layered package holding an operating system userland, the Python runtime, your dependencies and your code. Every Azure container service in this exam (App Service, Container Apps, Container Apps jobs and AKS, the Azure Kubernetes Service) runs images built this way, so a clean Dockerfile is the first skill the rest of the domain builds on.

Start with the base image in the FROM line. Official Python images come in variants: the full Debian-based image is large but has build tools; -slim images drop most of those tools and are the usual choice for production; Alpine images are tiny but use musl instead of glibc, so many Python wheels must be compiled from source, which often makes builds slower and bigger in practice. Pin a specific version tag (for example a Python minor version plus -slim) rather than latest, so rebuilds are predictable.

Each instruction (RUN, COPY, ADD) creates a layer, and Docker caches layers. Order instructions from least to most frequently changing: copy requirements.txt first, run pip install --no-cache-dir -r requirements.txt, and only then copy the rest of your code. A code change then reuses the cached dependency layer instead of reinstalling everything.

A multi-stage build uses more than one FROM. The first stage (the builder) has compilers and headers and installs wheels into a virtual environment; the final stage starts from a small runtime image and uses COPY --from=builder to bring over only the finished virtual environment and your code. Build tools, caches and secrets used during the build never reach the final image, which shrinks size and attack surface.

FROM python:3.12-slim AS builder
WORKDIR /app
RUN python -m venv /opt/venv
ENV PATH=/opt/venv/bin:$PATH
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

FROM python:3.12-slim
RUN useradd --create-home appuser
COPY --from=builder /opt/venv /opt/venv
ENV PATH=/opt/venv/bin:$PATH
WORKDIR /app
COPY . .
USER appuser
EXPOSE 8000
CMD ["gunicorn", "-b", "0.0.0.0:8000", "app:app"]

By default a container process runs as root. If an attacker exploits your app, root inside the container makes escaping or tampering easier. Create an unprivileged user and switch to it with USER before CMD, as above. Listen on a port above 1024, because non-root users cannot bind low ports without extra capabilities.

Finally, .dockerignore works like .gitignore for the build context: it lists files that are never sent to the builder. Exclude .git, __pycache__, virtual environments, test data and especially .env files or local credentials. This speeds up builds (especially remote builds that upload the context, such as az acr build) and keeps secrets out of COPY . . layers, where anyone who pulls the image could read them.

Key terms

Base image
The image named in FROM that your image builds on, such as a slim Python image.
Multi-stage build
A Dockerfile with several FROM stages where the final stage copies only needed artifacts from earlier stages.
Build context
The set of files sent to the builder with a build; .dockerignore removes files from it.
Layer cache
Docker's reuse of unchanged instruction results, which makes instruction order matter for build speed.
USER instruction
Sets the user that later instructions and the running container use, so the app need not run as root.
Real-world example

A team's FastAPI image was 1.1 GB and rebuilt all dependencies on every commit. They switched to a slim base, copied requirements.txt before the code, added a builder stage for compiled wheels and a .dockerignore excluding .git and .env. The final image dropped to a few hundred megabytes, rebuilds after code-only changes took seconds, and a security scan no longer flagged a leaked .env file.

Exam tip: If a question asks how to keep compilers or secrets out of the final image, the answer is a multi-stage build; if it asks how to stop files from being sent to the build at all, the answer is .dockerignore.

Check yourself

Why should you copy requirements.txt and run pip install before copying the rest of the source code?

Because layers are cached in order; code changes then reuse the cached dependency layer instead of reinstalling every package.

What does a multi-stage build improve, and how?

Image size and security: build tools live only in an earlier stage, and the final stage uses COPY --from to take just the built artifacts.

Why run the app with a USER other than root?

To limit what an attacker can do if the app is compromised; root in the container makes tampering and escape attempts easier.

Study AI-200 for free
A week-by-week plan with every lesson, quizzes, checkpoint tests, a practice exam and hands-on labs.
Open the AI-200 study plan