Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes

Create a Dockerfile for a Python App

Learn to create a Dockerfile for a Python app in this hands-on Docker tutorial. Step by step, you'll build, troubleshoot, and optimize a container for your Python project — perfect for developers progressing through the Docker track.

Focus: create a dockerfile for a python app

Sponsored

You've written a Python app, tested it locally, and everything works. The moment you try to run it on someone else's machine — or worse, deploy it to production — nothing works. Missing dependencies, wrong Python version, conflicting system libraries. Every teammate fights a slightly different environment. That's the problem this lesson solves: a Dockerfile is the single source of truth that packages your Python app with everything it needs to run, anywhere Docker does.

The problem this lesson solves

Without a Dockerfile, sharing a Python app means handing someone your code and a requirements.txt, then hoping they have the right Python version, pip, and system libraries. This "works on my machine" cycle wastes hours. Containers eliminate that by bundling the app, its runtime, and all dependencies into one immutable image. A Dockerfile is the blueprint for that image — a repeatable, version-controlled instruction set that builds your container every time identically.

Pro tip: If you've ever seen ModuleNotFoundError on a teammate's laptop, you've felt the pain. A Dockerfile prevents that by locking the environment inside the image.

Core concept / mental model

Think of a Dockerfile as a recipe. Each line is an instruction that layers a new file-system state on top of the previous one. The resulting image is a stack of read-only layers. When you run a container from that image, Docker adds a writable layer on top.

Key terms

  • Base image — the starting point (e.g., python:3.10-slim). It includes the OS and Python runtime.
  • Instruction — a command like FROM, COPY, RUN, CMD. Each creates a new layer.
  • Layer — a cached snapshot of the filesystem after an instruction. Build speed depends on effective layer caching.
  • Image — the final artifact that can be pushed to a registry or run locally.
  • Container — a running instance of an image.

The mental model: every line in your Dockerfile is permanent unless you change it. Order matters because Docker caches each layer. If you change a COPY instruction early in the file, all subsequent layers invalidate.

How it works step by step

A typical Dockerfile for a Python app follows this sequence:

  1. Start from an official base imagepython:3.10-slim is minimal and secure.
  2. Set a working directory — so instructions like COPY and CMD operate in a known folder.
  3. Copy dependency files firstrequirements.txt before your source code. This leverages caching: if your code changes but dependencies don't, the RUN pip install layer reuses the cache.
  4. Install system dependencies (if any) and Python packages.
  5. Copy the rest of your application code into the image.
  6. Define the runtime command — what happens when a container starts (e.g., CMD ["python", "app.py"]).

Why COPY order matters

# Bad: dependencies copy every time code changes — slow rebuilds
COPY . /app
RUN pip install -r requirements.txt

# Good: dependencies layer is cached until requirements.txt changes
COPY requirements.txt /app/
RUN pip install -r /app/requirements.txt
COPY . /app

The second version builds faster because the pip install layer is only re-executed when requirements.txt changes.

Hands-on walkthrough

Let's build a Dockerfile for a minimal Python Flask app.

Step 1: create the app

Create a file app.py:

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello from Docker!"

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)

Create requirements.txt:

flask==3.0.0

Step 2: write the Dockerfile

Create a file named Dockerfile (no extension) in the same directory:

# Use official Python 3.10 slim image
FROM python:3.10-slim

# Set working directory inside container
WORKDIR /app

# Copy only requirements to leverage caching
COPY requirements.txt .

# Install Python dependencies
RUN pip install --no-cache-dir -r requirements.txt

# Copy the rest of the app
COPY . .

# Expose port 5000 (documentation; does NOT publish)
EXPOSE 5000

# Command to run when container starts
CMD ["python", "app.py"]

Step 3: build and run

# Build the image (tag it as my-python-app)
docker build -t my-python-app .

# Run the container, map host port 5000 to container port 5000
docker run -p 5000:5000 my-python-app

Visit http://localhost:5000 in your browser. You should see "Hello from Docker!".

Pro tip: Use --no-cache-dir in pip install to keep the image small. Every megabyte saved speeds up builds and downloads.

Compare options / when to choose what

Instruction Purpose Recommendation
FROM python:3.10-slim Base image Use slim variants for smaller images. Avoid :latest — pin a version.
WORKDIR /app Default working directory Always set one. Avoid COPY . . without a WORKDIR.
COPY vs ADD Copy files into image Prefer COPY. ADD has extra features (URLs, tar) but hides complexity.
CMD vs ENTRYPOINT Runtime command Use CMD for simple commands. Use ENTRYPOINT + CMD when you want a fixed executable with default arguments.
RUN pip install Install dependencies Always split COPY requirements.txt before RUN pip install for caching.

When to use a multi-stage build

If your Python app has build dependencies (e.g., compiling C extensions), a multi-stage build keeps the final image small:

# Stage 1: builder
FROM python:3.10-slim AS builder
COPY requirements.txt .
RUN pip install --user -r requirements.txt

# Stage 2: final minimal image
FROM python:3.10-slim
COPY --from=builder /root/.local /root/.local
COPY . /app
WORKDIR /app
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "app.py"]

This approach discards build tools after compilation, shrinking the image by hundreds of megabytes.

Troubleshooting & edge cases

1. pip install fails inside container

Cause: missing system libraries (e.g., gcc, libssl-dev). Fix: install them before pip install and remove them later. Or switch to an image that includes them, like python:3.10-slim-bullseye (still slim, but with more build tools).

2. App can't connect to host services

If your container runs but can't reach a database on the host, check the network mode. Use --network host on Linux for simple scenarios, or docker compose for proper networking.

3. Image is too large (>1 GB)

  • Use python:3.10-slim or python:3.10-alpine (Alpine is ~50 MB but may lack glibc).
  • Clean up apt cache: RUN apt-get update && apt-get install -y some-package && rm -rf /var/lib/apt/lists/*.
  • Chain RUN commands to reduce layers: RUN apt-get update && apt-get install -y --no-install-recommends package && rm -rf /var/lib/apt/lists/*.

4. Flask app doesn't listen on 0.0.0.0

If you forget host="0.0.0.0", Flask defaults to 127.0.0.1, which is unreachable from outside the container. Always bind to 0.0.0.0.

What you learned & what's next

You now understand how to create a Dockerfile for a Python app: from selecting a base image, ordering COPY and RUN instructions for cache efficiency, to running the container with port mapping. You can explain why COPY requirements.txt before the rest of the code speeds up builds, and you've seen how to troubleshoot common errors like silent binding issues or oversized images.

Next up: Docker Compose for Python apps — you'll learn how to orchestrate your Python service alongside a database, Redis, or any multi-service stack using a single compose.yml file.

Practice recap

Mini exercise: Create a Dockerfile for a Python script that prints Fibonacci numbers. Use python:3.10-slim, set WORKDIR /app, copy the script, and run it with CMD ["python", "fib.py"]. Build and run it with docker run --rm my-fib. Verify the output is correct.

Common mistakes

  • Copying the entire source code before RUN pip install destroys caching—every code change re-installs dependencies.
  • Using python:latest as the base image leads to unpredictable builds when new Python versions release; always pin a version like python:3.10-slim.
  • Forgetting to set host='0.0.0.0' in your Python app causes the container to listen only on localhost—your host machine can't connect.
  • Installing system packages with apt-get install without --no-install-recommends and cleaning the apt cache bloats the image by hundreds of megabytes.

Variations

  1. Use python:3.10-alpine for the smallest possible image (~50 MB), but test for compatibility with native Python packages that require glibc.
  2. Instead of CMD ["python", "app.py"], a more production-ready approach is ENTRYPOINT ["python"] CMD ["app.py"] to allow argument overrides.
  3. For apps that don't use Flask or web frameworks, you can use CMD ["python", "-m", "my_module"] or even CMD ["python", "-c", "print('Hello')"] for scripts.

Real-world use cases

  • Deploy a Python web API to a Kubernetes cluster—every pod runs the same image built from your Dockerfile.
  • Run a Python data processing job on a CI/CD pipeline (e.g., GitHub Actions) with consistent dependencies across every run.
  • Share a Python-based machine learning model with a team: the Dockerfile ensures identical Python version and libraries are used for inference.

Key takeaways

  • A Dockerfile is a layer-by-layer recipe that packages your Python app with its runtime and dependencies.
  • Copy requirements.txt before source code to cache the pip install layer and accelerate rebuilds.
  • Always pin the base image version (e.g., python:3.10-slim) to guarantee reproducible builds.
  • Use EXPOSE for documentation only; map ports at runtime with -p host:container.
  • Multi-stage builds reduce image size by separating build tools from the final runtime image.
  • Bind Flask/Gunicorn to 0.0.0.0 inside the container to make the app reachable from outside.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.