A monorepo for Flask microservices isn’t just about putting code in one place; it’s about treating your collection of small, independent services as a single, cohesive unit that can be developed, tested, and deployed with shared tooling and consistent practices.
Let’s see how this looks in practice. Imagine a monorepo directory. Inside, you’d have a services folder, and within that, subdirectories for each microservice.
monorepo/
├── services/
│ ├── user_service/
│ │ ├── app.py
│ │ ├── requirements.txt
│ │ └── Dockerfile
│ ├── product_service/
│ │ ├── app.py
│ │ ├── requirements.txt
│ │ └── Dockerfile
│ └── order_service/
│ ├── app.py
│ ├── requirements.txt
│ └── Dockerfile
├── .gitignore
├── README.md
└── pyproject.toml
Your user_service/app.py might look like this:
from flask import Flask, jsonify
app = Flask(__name__)
@app.route('/users/<int:user_id>')
def get_user(user_id):
# In a real app, this would query a database
return jsonify({"id": user_id, "name": f"User {user_id}"})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5001)
And product_service/app.py:
from flask import Flask, jsonify
app = Flask(__name__)
@app.route('/products/<int:product_id>')
def get_product(product_id):
# In a real app, this would query a database
return jsonify({"id": product_id, "name": f"Product {product_id}"})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5002)
The core problem this solves is the operational overhead of managing many separate repositories. Instead of coordinating releases, dependency updates, and CI/CD pipelines across dozens of git repos, you have one. This significantly reduces boilerplate and allows for easier cross-service refactoring and shared library management.
Internally, the key is a build system or a set of scripts that understand the monorepo structure. Tools like Bazel, Pants, or even simple shell scripts can be used. For a simpler setup, you might use a Makefile in the root:
.PHONY: build-all test-all run-user run-product run-order
build-all:
$(MAKE) -C services/user_service build
$(MAKE) -C services/product_service build
$(MAKE) -C services/order_service build
test-all:
$(MAKE) -C services/user_service test
$(MAKE) -C services/product_service test
$(MAKE) -C services/order_service test
run-user:
docker run -d -p 5001:5001 user_service
run-product:
docker run -d -p 5002:5002 product_service
run-order:
docker run -d -p 5003:5003 order_service
Each service’s Dockerfile would be standard:
services/user_service/Dockerfile:
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
CMD ["python", "app.py"]
And its requirements.txt would list its direct dependencies:
services/user_service/requirements.txt:
Flask==2.3.7
The pyproject.toml at the root can manage linters, formatters, and testing frameworks across all services, ensuring consistency.
[tool.black]
line-length = 88
target-version = ['py39']
[tool.isort]
profile = "black"
[tool.pytest.ini_options]
pythonpath = "."
The real power comes when you introduce shared libraries or common configurations. You can create a libs directory:
monorepo/
├── libs/
│ ├── common_utils/
│ │ ├── __init__.py
│ │ └── helpers.py
│ └── db_connector/
│ ├── __init__.py
│ └── connection.py
├── services/
│ ├── user_service/
│ │ ├── app.py
│ │ ├── requirements.txt
│ │ └── Dockerfile
│ └── ...
├── .gitignore
├── README.md
└── pyproject.toml
Now, services/user_service/requirements.txt might include:
Flask==2.3.7
../libs/common_utils
This relative path dependency allows services to directly import code from shared libraries within the monorepo, simplifying dependency management for internal components.
When you’re building or testing, your build system or scripts need to be aware of these interdependencies. A Makefile can be extended to handle this, or a more sophisticated tool like Bazel would manage the dependency graph automatically. For instance, to build the user_service if its dependencies (including shared libs) have changed, you’d run make build-user (assuming your Makefile is updated to understand this).
A common pattern is to have a single entry point for deployment that orchestrates the startup of all services, or to deploy each service independently using its own Docker image built from the monorepo. The latter is more aligned with microservice principles.
The one thing that trips most people up is how to handle versioning and independent deployment when everything is in one repo. You don’t necessarily deploy the entire monorepo at once. Instead, your CI/CD pipeline should be smart enough to detect which service(s) have changed and only build/test/deploy those specific components. This often involves using tools that can analyze the Git diff against the monorepo’s structure and trigger actions only for affected services.
Next, you’ll explore how to manage configuration across these services in a unified way within the monorepo.