Guide
How to deploy a monorepo with multiple services (frontend, API and database)
Deploy a monorepo as one connected unit: describe each service once, link them with generated URLs, and let dependencies decide the deploy order.
· 3 min read
In short
- Describe every service once in a single prongo.yaml
- depends_on sets the deploy order, {{ api.url }} links the services
- pre_deploy runs migrations before the new version goes live
A monorepo with a frontend, an API and a database is easy to run on your laptop and surprisingly awkward to deploy. Most hosting platforms assume one repository means one app. The moment you have three services that depend on each other, you are suddenly managing deploy order, internal URLs and shared secrets by hand.
This guide shows the general approach, and then how it looks with a monorepo-aware platform where the whole repo is the unit of deployment.
What makes a monorepo hard to deploy
Three things go wrong again and again:
- Order. The API cannot start before its database is reachable, and the frontend should not go live before the API it calls. Starting everything at once (“it will retry”) works until it doesn’t.
- Discovery. The frontend needs the API’s address. In production that address is generated, so hardcoding
http://localhost:8080stops working the moment you leave your machine. - Secrets. The same
DATABASE_URLis needed by the API and a worker. Copy-pasting it into three dashboards is how credentials drift and leak.
Everything below is about making those three problems disappear.
Step 1: give every service a Dockerfile
Each service becomes a container, built from its own folder. A typical layout:
shop/
├── apps/
│ ├── api/ # Dockerfile, listens on 8080
│ └── web/ # Dockerfile, listens on 3000
└── prongo.yaml
The database does not need a Dockerfile. It is a ready-made image such as postgres:16.
Step 2: describe the whole system in one file
Instead of one config per service, write one file at the repo root:
project: shop
services:
database:
image: postgres:16
port: 5432
env:
POSTGRES_DB: shop
api:
build: ./apps/api
port: 8080
depends_on: [database]
pre_deploy: [npm run migrate]
web:
build: ./apps/web
port: 3000
depends_on: [api]
env:
API_URL: "{{ api.url }}"
Three ideas carry the whole file:
depends_ondeclares order. The platform deploysdatabasefirst, waits until it runs, thenapi, thenweb.{{ api.url }}is service discovery. It is replaced with the real internal address at deploy time.pre_deployruns your migrations after the dependencies are up and before the new version receives traffic. See running migrations on every deploy for how to write them safely.
The full list of fields is in the prongo.yaml reference.
Step 3: keep secrets out of the file
The yaml lives in git, so passwords and tokens must not. Set them once in the dashboard and they are injected into the services as environment variables with the same name. More on this in sharing environment variables between services.
Step 4: push
With the file committed, a push builds every service, deploys them in dependency order, and starts them with zero downtime. If a pre_deploy command fails, the deploy fails and the previous version keeps running, so a broken migration does not take your site down.
What this replaces
Doing the same on Docker Compose gives you no readiness-aware ordering (depends_on only controls start order) and a single machine. Doing it on Kubernetes means separate Deployment, Service, Ingress and Secret manifests for every service. The side-by-side is on the comparison page.
When this approach is not the right fit
If your repo is one static site or one Next.js app, any host works and you do not need this. A monorepo platform pays off when you have two or more services that must be deployed together, which is exactly the case most hosts treat as an afterthought.
Frequently asked questions
Can you deploy a monorepo to one server?
Yes. Build one container per service from its folder, run them together, and connect them over an internal network. The hard parts are deploy order, service discovery and shared secrets, which is what a monorepo-aware platform automates.
Do I need Kubernetes to deploy a monorepo with several services?
No. Kubernetes solves the problem but needs many manifests per service. A platform like Prongo runs on Kubernetes for you and reduces the setup to one prongo.yaml file.
How do services in a monorepo find each other after deploying?
Through generated internal URLs. In Prongo you write {{ api.url }} in another service's env and the real address is filled in at deploy time, so nothing is hardcoded.
What order should services in a monorepo deploy in?
Dependencies first: database, then the API that needs it, then the frontend that calls the API. Declaring depends_on lets the platform work this order out instead of you scripting it.