prongo.yaml
Reference
prongo.yaml
One file at the root of your repo that describes your services and how they connect. Optional, but the fastest way to define a whole project.
Minimal example
Name it prongo.yaml and
put it at the repo root. Push, and Prongo builds it. Repos that still use the old name,
forkdeploy.yaml, keep working;
if both exist, prongo.yaml wins.
project: my-app
services:
web:
build: .
port: 3000 Fields
All fields except project and
services live under a service.
Use either image or
build, not both.
| Field | Required | What it does |
|---|---|---|
| project | yes | Project name. Becomes the URL slug (lowercase, hyphens). |
| services | yes | One entry per service, keyed by its name. |
| image | one of | Ready-made Docker image, e.g. postgres:16. |
| build | one of | Path to a folder with a Dockerfile. |
| port | no | Port the container listens on. |
| replicas | no | Number of instances, 1 to 10. Default 1. Each replica uses its own memory and CPU and counts against your plan limits. |
| depends_on | no | Services that must be running first. |
| pre_deploy | no | Commands to run before each rollout (every deploy), e.g. migrations or idempotent seeds. |
| env | no | Plain, non-sensitive settings. May use {{ service.url }}. |
Linking services
Write {{ name.url }}
in an env value. At deploy time
Prongo swaps it for that service's internal address. It stays the same when the service restarts, so no hard-coded IPs.
In a project called “shop”
API_URL: "{{ api.url }}" resolving… http://api.shop.internal The link is an http address without credentials or a port. It is meant for services that talk HTTP to each other, like web → api. For a database connection string, use a secret.
Deploy order
depends_on sets the order. A service
starts only after what it needs is running; unrelated services deploy in parallel. Circular dependencies fail the deploy immediately.
Replicas & limits
replicas runs several copies of one service.
You set the number yourself; the platform never scales it up or down for you.
- Every replica is a full copy. Two replicas of a service limited to 384 MB use 768 MB of your project's memory, and the same goes for CPU.
- Replicas count against your plan: the maximum memory, CPU and number of running instances per project. Past that limit a deploy is refused.
- A rollout briefly starts one extra instance next to the old ones, so leave some room below your project limit.
- Volumes belong to the service, not to a replica. A service with persist: (a database, for example) must run exactly 1 replica, because several instances sharing one data folder can corrupt the data. Setting replicas above 1 on such a service is rejected.
- Today extra replicas cost nothing beyond using up your plan limits. If paid plans ever charge for extra capacity, it will be stated on the pricing page first.
Migrations & seeds
pre_deploy runs commands on every deploy,
after the services it depends on are running and before the new version goes live. Use it for database migrations and idempotent seeds.
api:
build: ./apps/api
depends_on: [database]
pre_deploy:
- npm run migrate
- npm run seed Make the seed safe to repeat by letting the database skip rows that already exist:
-- seed.sql: a second run changes nothing
INSERT INTO plans (id, name) VALUES (1, 'free')
ON CONFLICT (id) DO NOTHING; - Runs in a one-off container from the same image, with the same env and secrets (so DATABASE_URL works).
- Commands run in order. If one fails or takes longer than 5 minutes, the deploy fails and the previous version keeps running.
- Migrations are safe to repeat: tools like Prisma, Drizzle and Alembic record what already ran. Seeds are not, unless you write them that way (ON CONFLICT DO NOTHING, or check for existing data first).
- Never DROP, TRUNCATE or reset tables in pre_deploy. Run one-off or destructive data changes yourself, outside the deploy.
- Replicas and branch previews each run it, so use your migration tool's lock if two deploys can overlap.
- The output shows up in the deploy log.
Full example
A database, an API that needs it, and a frontend that needs the API.
project: shop
services:
database:
image: postgres:16
port: 5432
env:
POSTGRES_DB: shop
# POSTGRES_PASSWORD is a secret, set in the dashboard
api:
build: ./apps/api
port: 8080
depends_on: [database]
pre_deploy: [npm run migrate]
env:
NODE_ENV: production
# DATABASE_URL is a secret, set in the dashboard
web:
build: ./apps/web
port: 3000
depends_on: [api]
env:
API_URL: "{{ api.url }}" What stays out of the file
The yaml lives in your repo, so anyone with access sees it. Add sensitive values in the dashboard (Projects → Secrets). They are encrypted, and injected into the service as an environment variable with the same name.
✓ In the yaml
- Services, ports and replicas
- Which service depends on which
- Non-sensitive settings (NODE_ENV, DB name)
- {{ service.url }} links
✕ Never in the yaml
- Passwords
- API keys and tokens
- Database URLs with credentials
- Anything you would not commit publicly
More in Secrets & env vars.