Skip to content

timothy-jeong/monorepo-example

Repository files navigation

Monorepo Microservices with uv and Hatchling

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

🧱 Project Structure

.
├── 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 dependencies

⚙️ Dependency Management with uv

This 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.lock file ensures consistent and reproducible installations across services.

📦 Package Namespace

All shared packages use the shared namespace to maintain a clear and consistent package hierarchy. For example:

  • shared.security
  • shared.exception
  • shared.logger
  • shared.middleware

This namespace approach helps prevent naming conflicts and makes it clear which packages are internal to the monorepo.

🐳 Docker Usage

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 before uv sync so that workspace dependencies can be resolved and installed.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages