This repository demonstrates a Python-based monorepo architecture for microservices using uv as the package/dependency manager and PEP 621 (hatchling) as the build backend.
It is optimized for:
- Fast and reproducible dependency installation
- Shared package reuse across services
- Clear separation of service boundaries
- Efficient Docker layer caching during CI/CD builds
.
├── services # Actual microservice implementations
│ ├── board_service
│ │ ├── app/ # FastAPI app
│ │ └── pyproject.toml # Service-specific dependencies
│ └── comment_service
│ ├── app/
│ └── pyproject.toml
├── packages # Reusable internal packages
│ ├── exception/
│ │ ├── pyproject.toml
│ │ └── src/shared/exception/
│ ├── logger/
│ │ ├── pyproject.toml
│ │ └── src/shared/logger/
│ ├── middleware/
│ │ ├── pyproject.toml
│ │ └── src/shared/middleware/
│ └── security/
│ ├── pyproject.toml
│ └── src/shared/security/
├── Dockerfile.board # Dockerfile for board_service
├── Dockerfile.comment # Dockerfile for comment_service
├── pyproject.toml # Root pyproject defining shared workspace
└── uv.lock # Single lockfile for all dependenciesThis monorepo uses uv to manage dependencies in a unified, deterministic way.
The root pyproject.toml includes:
-
Shared dependencies (like
fastapi,uvicorn,sqlalchemy, etc.) -
Workspace definitions for shared packages (
packages/*) -
Example of tool.uv.workspace in pyproject.toml:
[tool.uv.workspace]
members = ["packages/*", "services/*"]
[tool.uv.sources]
shared.logger = { workspace = true }
shared.exception = { workspace = true }
shared.middleware = { workspace = true }
shared.security = { workspace = true }The
uv.lockfile ensures consistent and reproducible installations across services.
All shared packages use the shared namespace to maintain a clear and consistent package hierarchy. For example:
shared.securityshared.exceptionshared.loggershared.middleware
This namespace approach helps prevent naming conflicts and makes it clear which packages are internal to the monorepo.
Each service has its own Dockerfile (e.g. Dockerfile.board, Dockerfile.comment) and is built independently. Here’s a minimal structure for building a service:
FROM python:3.12-alpine
# Copy uv binary
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
WORKDIR /app
# Step 1: Copy lockfile and pyproject first (for dependency layer caching)
COPY pyproject.toml uv.lock ./
COPY packages/ ./packages/
# Step 2: Install dependencies (must include shared packages)
RUN uv sync --frozen --no-dev
# Step 3: Copy only the relevant service code
COPY services/board_service ./services/board_service/
EXPOSE 8000
WORKDIR /app/services/board_service
ENTRYPOINT ["uv", "run", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--workers", "2"]
packages/must be copied beforeuv syncso that workspace dependencies can be resolved and installed.