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:
|
|
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:
|
|
This configuration tells Poetry (or pip) to:
- Take the
appfolder from thesrc/directory - Install it as a top-level module named
appinsite-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):
- Current directory - The directory containing the script being executed
PYTHONPATHenvironment variable - Additional directories specified by the user- Standard library directories - Python’s built-in modules
site-packagesdirectory ← Your 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:
|
|
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
srcdirectory in the file path - Returns the repository root (e.g.,
/Users/you/project/)
Production Mode (Docker):
- Uses the
PROJECT_ROOTenvironment variable (set to/appin 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:
|
|
Code location: Handled by Python’s site-packages mechanism
Config location: Handled by path.py using PROJECT_ROOT environment variable
Key Takeaways
- Package installation transforms your code structure -
src/app/becomes a top-levelapp/module insite-packages - Python’s module search path is automatic - Once installed, your code is discoverable without path manipulation
- Configuration files need special handling - Unlike Python modules, config files require custom logic to locate
- Environment variables bridge the gap -
PROJECT_ROOThelps 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.