We're building a collection of small services that are deployed independently, mostly as AWS Lambda functions. The repository contains JavaScript, TypeScript, and Python, with Python currently making up the largest share. We use a monorepo so developers and AI coding tools can understand how the services fit together, and we've also migrated a few applications from a visual app-building platform into the same repository.
At the moment, every project is built and deployed with Makefiles. We're also developing an internal registry that acts as the source of truth for available tools and services, with the goal of keeping new additions consistent and making our inventory easy to analyze.
Make works, but we're starting to run into duplicated configuration and inconsistent conventions. I'm considering whether to keep Make as a thin wrapper or move to something like Bazel, Pants, Nx, Turbo, mise, or another orchestration tool. What approaches have worked well for teams managing a multi-language monorepo with independently deployable services?
5 Answers
I’d be cautious about replacing Make just because the repository is a monorepo. For a relatively small number of Lambda-sized services, keep Make as the developer-facing entry point and standardize a small contract for every service: build, test, package, deploy, and perhaps smoke test. The registry can generate or maintain a small shared include so each service exposes the same commands while language-specific details stay local. If the registry becomes a second source of truth, make it generate those definitions rather than duplicating them by hand.
For a lighter solution, take a look at mise or Just. Mise can manage language and tool versions while providing standardized tasks across a monorepo, which may fit well if you have only a few services in several languages. Just has nicer task-file ergonomics than Make, but it is mainly a command runner and does not provide Make-style file dependency tracking or a real build cache. If you need incremental builds, use it only as a front end to something that understands the dependency graph.
If Python is the dominant language, Polylith is worth investigating. It focuses on organizing reusable components and deployable applications in a monorepo without forcing you into a particular build system. Its tooling can identify what changed and help determine which applications need rebuilding or deployment. That could complement your registry while leaving you free to keep Make, use another task runner, or introduce a more advanced build tool later.
The real question is whether you need dependency-graph awareness and caching. Make is fine when builds are small, but it won’t naturally determine which services are affected or share results between machines. Pants is often a more approachable choice than Bazel for a multi-language repository and can handle dependency detection without the same level of setup and maintenance. Bazel is extremely capable for large repositories, with strong caching, testing, and container integrations, but the learning and troubleshooting cost is significant.
I’d choose Bazel only once rebuilding unaffected projects or running CI has become a measurable problem. For a modest repository, its setup cost may outweigh the benefits.
I’d also separate code organization from deployment boundaries. A service being easy for an AI tool to understand does not automatically make it cheap to operate. Define explicit ownership, reproducible local builds, affected-target detection, and a shared contract-testing path. Otherwise, splitting everything into small services can create a lot of extra CI, deployment, and maintenance work even if the code generation feels easier.

That may be the right direction. We’re also seeing some Windows compatibility issues, although I’m not sure a heavier polyglot build system would actually make that simpler.