Have you ever wondered how Python finds your application code when running in a Docker container? If your source code lives in src/app/ during development, but Python can import app.main without any path manipulation in production, there’s some interesting machinery at work behind the scenes.

Let’s explore how Python locates and executes installed code, using a real-world example: running a FastAPI application with python -m uvicorn app.main:app --host 0.0.0.0 --port 8080.

The Problem

When your application starts in a Docker container, Python needs to find the app module even though the source code was originally organized in src/app/. How does this work?

Package Installation: From Source to site-packages

During the Docker build process, your application gets installed as a package:

1
RUN pip install *.whl

This command installs the app package into Python’s site-packages directory, typically located at /usr/local/lib/python3.12/site-packages/app/.

The key configuration that makes this work is in your pyproject.toml:

1
packages = [{include = "app", from = "src"}]

This configuration tells Poetry (or pip) to:

  • Take the app folder from the src/ directory
  • Install it as a top-level module named app in site-packages

This transformation is crucial: your code structure changes from src/app/ to a standalone app/ module that Python can import directly.

Python’s Module Search Path

When Python imports a module, it searches in these locations (in order):

  1. Current directory - The directory containing the script being executed
  2. PYTHONPATH environment variable - Additional directories specified by the user
  3. Standard library directories - Python’s built-in modules
  4. site-packages directoryYour installed code lives here!

Since app is installed in site-packages, Python automatically finds it without needing any path manipulation or PYTHONPATH configuration.

You can verify Python’s search path by running:

1
2
import sys
print(sys.path)

This will show you all the directories Python searches when importing modules, with site-packages typically appearing near the end of the list.

Finding Configuration Files: The path.py Challenge

While Python can find your code in site-packages, your application also needs to locate configuration files that live outside the Python package. This is where things get interesting.

The Separation of Concerns

In a Docker container, there’s a clear separation:

  • Application code: Lives in /usr/local/lib/python3.12/site-packages/app/
  • Configuration files: Live in /app/settings/ (copied separately in the Dockerfile)

Development vs. Production

The path.py module handles this distinction with smart logic:

Development Mode:

  • Detects the project root by looking for the src directory in the file path
  • Returns the repository root (e.g., /Users/you/project/)

Production Mode (Docker):

  • Uses the PROJECT_ROOT environment variable (set to /app in the Dockerfile)
  • Falls back to the current working directory if neither method works

Why This Matters

Without the path.py logic, your code wouldn’t know where to find settings/global.yaml and other configuration files! The module search path helps Python find your code, but you need custom logic to find non-Python resources.

The Complete Flow

Here’s how everything fits together:

1
2
3
4
5
6
7
8
9
Docker Build
pip install *.whl
app/ → /usr/local/lib/python3.12/site-packages/app/
Python can import "app.main" automatically
path.py helps find config files in /app/settings/

Code location: Handled by Python’s site-packages mechanism
Config location: Handled by path.py using PROJECT_ROOT environment variable

Key Takeaways

  1. Package installation transforms your code structure - src/app/ becomes a top-level app/ module in site-packages
  2. Python’s module search path is automatic - Once installed, your code is discoverable without path manipulation
  3. Configuration files need special handling - Unlike Python modules, config files require custom logic to locate
  4. Environment variables bridge the gap - PROJECT_ROOT helps your application find resources outside the Python package

Understanding these mechanisms helps you debug import issues, optimize your Docker builds, and design better application architectures. The separation between code (in site-packages) and configuration (in the project root) is a common pattern that provides flexibility while maintaining clean package boundaries.