This project follows the Mirror-Environment Pattern: your development environment (DevContainer) matches your testing environment (GitHub Actions) by running verification inside the same Docker image.
The project uses a Clean Verification Flow to ensure reproducibility and avoid polluting the host workspace with root-owned artifacts (like __pycache__ or .pytest_cache).
make docker-buildThe standard verification target mounts your source code as read-only and performs the installation and testing inside a temporary directory in the container:
make docker-verifyThis command:
- Mounts the current directory as
:ro. - Copies the source to
/tmp/workto avoid permission issues during installation. - Creates a virtual environment in
/tmp/venv. - Installs the package with
[test]extras. - Runs tests and the entrypoint.
pip install '.[test]': Minimal dependencies for running tests (used in CI).pip install '.[dev]': Full development environment, including documentation tools (Sphinx) and testing utilities.
This template is designed to be "CI-native" by avoiding common pitfalls with Docker permissions and volume mounts.
While mapping the host UID/GID to the container is a common local development pattern, we avoid it in CI for several reasons:
- Identity Mismatch: In environments like GitHub Actions, the runner UID (typically 1001) may not exist in the container's
/etc/passwd, leading to "I have no name!" errors and broken tool behavior. - Home Directory Access: Many tools (pip, git, etc.) expect a valid
$HOME. If you force a UID that doesn't have a home directory defined in the image, these tools may fail or try to write to/, which is restricted. - Portability: The template should work regardless of the host's UID. By running as the container's internal user (
app) and isolating all write operations to/tmp, we ensure consistent behavior across local and CI environments.
To make the container execution truly ephemeral and secure:
HOME=/tmp: We override the home directory to/tmp. Since/tmpis world-writable, this guarantees that any tool attempting to write configuration or cache to~will succeed without needing complex volume permission management.PYTHONNOUSERSITE=1: This prevents Python from loading packages from the user-specific site-packages directory (usually~/.local). This ensures that the environment is strictly defined by the virtual environment created during verification, preventing "poisoning" from the container's global state.
If you need to use a different non-root user or if you encounter permission issues:
- Use
make docker-verify-rwto run with a read-write mount and inspect the artifacts. - Verify that your container user has write access to
/tmp. - Check if your source code contains pre-existing
*.egg-infoor__pycache__directories owned byroot, which might interfere with thecp -acommand or tool execution.
If you previously ran commands that created files inside the container with a read-write mount, they might be owned by root on your host. Use this to clean them:
make cleanSome legacy Python packaging tools might try to write to the source directory even during installation (e.g., updating src/*.egg-info).
If make docker-verify fails due to read-only restrictions, you can use the debug target which uses a read-write mount:
make docker-verify-rwNote: This may create root-owned artifacts in your workspace.
CI runs the same make docker-verify step.