Skip to Content
Course content

355: Containerizing a Python Application with Docker

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was mentoring a developer who spent an entire Friday night chasing a bug that only existed on our staging server. Locally, his data processing script ran perfectly. On the server, it crashed with a cryptic ImportError related to a C-extension in the pandas library. It turned out the server was running an older version of Debian with a different glibc version than his macOS machine. He had the right Python version, the right requirements file, and the right code—but the underlying operating system was sabotaging him. This is the "it works on my machine" trap, and it's exactly why we use Docker.

At its core, Docker lets us package the application code, the runtime, the system libraries, and the configuration into a single immutable artifact called an image. When you run that image, it becomes a container. Because the container carries its own environment, it doesn't matter if your server is running Ubuntu, CentOS, or Alpine; the code inside the container sees the exact same environment every single time.

Building a Reproducible Environment via the Dockerfile

To containerize a Python app, we write a Dockerfile. Think of this as a recipe. I'll use a Flask-based API that calculates portfolio risk as our example, as it requires a few specific dependencies. Instead of using the default python:3.11 image—which is massive because it includes build tools you rarely need in production—I always recommend starting with python:3.11-slim. It keeps your image size down and reduces the attack surface for security vulnerabilities.

FROM python:3.11-slim

# Set the working directory inside the container
WORKDIR /app

# Install system dependencies required for some Python packages (like psycopg2 or pandas)
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    && rm -rf /var/lib/apt/lists/*

# Copy only the requirements first to leverage Docker's layer caching
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy the rest of the application code
COPY . .

# Tell Docker which port the app listens on
EXPOSE 5000

# The command to run the app
CMD ["python", "app.py"]

You'll notice I copied requirements.txt and ran pip install before copying the rest of the code. This is a crucial optimization. Docker caches each line (layer). If you change a line of code in app.py but don't change your dependencies, Docker will skip the expensive pip install step and jump straight to copying the updated code. It turns a three-minute build into a three-second build.

Pruning the Build with .dockerignore

One mistake I see constantly is developers copying their entire project folder into the image without a filter. If you have a .git folder, __pycache__ directories, or a .env file containing secret API keys, you don't want those inside your image. Not only does it bloat the image size, but it's a massive security risk to bake secrets into an image that might be stored in a shared registry.

Create a .dockerignore file in your root directory. It works exactly like a .gitignore. I usually keep mine simple:

__pycache__/
*.pyc
.git/
.env
venv/
.vscode/

Once your files are ready, you build the image using docker build -t portfolio-api . and run it with docker run -p 5000:5000 portfolio-api. The -p flag maps your machine's port 5000 to the container's port 5000, allowing you to hit the API in your browser while the actual process remains isolated inside the container.




📋 Practical Task

Exercise: Containerizing the "Weather-Aggregator" Service

You have been handed a small Python project called weather-aggregator. It is a Flask app that uses the requests library to pull data from an external API. Your goal is to create a production-ready Docker configuration for it.

Project Structure:

  • app.py (The Flask application)
  • requirements.txt (Contains flask and requests)
  • .env (Contains a secret API_KEY)
  • tests/ (A folder containing local Pytest files)

Requirements:

  1. Write a Dockerfile using a slim Python 3.11 base image.
  2. Ensure the requirements.txt is installed in a way that leverages layer caching (install dependencies before copying the rest of the source code).
  3. Use --no-cache-dir during the pip installation to keep the image lean.
  4. Create a .dockerignore file that prevents the .env file and the tests/ folder from being baked into the final image.
  5. Set the WORKDIR to /usr/src/app.

Validation: Your solution is successful if the image builds without errors and the final image does not contain the .env file or the tests directory when inspected via docker run -it [image_name] ls -R /usr/src/app.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.