Incident timeline

    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.

    database api web

    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.

    Not just the first deploy. Redeploys and branch previews run it too, and there is no "already ran" marker. A seed that inserts without checking, or that wipes tables first, will duplicate or reset your data on every deploy.
    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.