Setting Up a Python Project: Local vs. Containerized Development

When starting a new Python project, one of the first decisions you'll face is how to set up your development environment. Should you go with a traditional local setup using virtual environments, or should you containerize your development with Docker? Let's explore both approaches through a practical example.

The Project Setup

We'll use a simple FastAPI project as our example. Here's what we need to get started:

                
                    mkdir hello-world
                    pyenv local 3.9.18 
                    python -m venv .venv
                    source ./.venv/bin/activate
                    python -m pip install --upgrade pip
                    pip install "fastapi[standard]"
                    pip freeze > requirements.txt
                
            

This gives us a basic Python project with FastAPI installed. But how should other developers run this project? Let's look at two approaches.

The Traditional Way: Local Development

The traditional approach involves each developer setting up Python and managing dependencies on their local machine. This is what most Python developers start with, and it looks familiar:

  1. Install the correct Python version
  2. Create a virtual environment
  3. Install dependencies

Simple, right? Well, not always. Consider these scenarios:

Enter Containerized Development

Here's where Docker comes in. Instead of managing the environment locally, we can define it in code:

                
                ARG PYTHON_VERSION=3.9.18
                FROM python:${PYTHON_VERSION} as base
                WORKDIR /usr/src/app
                RUN --mount=type=cache,target=/root/.cache/pip \
                --mount=type=bind,source=requirements.txt,target=requirements.txt \
                python -m pip install -r requirements.txt
                CMD uvicorn src.main:app --reload --port 8000 --host 0.0.0.0
                
            

And orchestrate it with Docker Compose:

                
                    services:
                        fastapi:
                            image: fastapi
                            build:
                                context: .
                                dockerfile: Dockerfile.dev
                            ports:
                                - 8000:8000
                            volumes:
                                - ./src:/usr/src/app/src:z
                            restart: 'no'
                
            

Now, any developer can start the project with just:

docker compose up --build

Why Choose Containerization?

After setting up both approaches, I recommend containerized development for most team projects. Here's why:

  1. Environment Consistency: The Docker configuration ensures everyone runs the exact same environment. No more "works on my machine" problems.
  2. Simple Onboarding: New team members don't need to worry about Python versions or virtual environments. They just need Docker installed.
  3. Version Control: The development environment itself becomes code that can be version controlled. Changes to the environment are explicit and tracked.
  4. Production Similarity: Docker-based development environments are closer to how the application will run in production, catching environment-related issues earlier.

Conclusion

The choice between local and containerized development isn't just about technical preferences—it's about team workflow and project needs. While containerization adds a layer of abstraction, the benefits of consistency and reproducibility make it worth considering for team projects. Whether you choose local or containerized development, the key is to document your choice and its rationale clearly. This helps team members understand not just how to run the project, but why it's set up that way. Remember: the goal is to spend less time fighting with environment issues and more time writing code. Choose the approach that best helps your team achieve that goal.