F ForkDeploy

Reference

forkdeploy.yaml

A single config file at the root of your repo that tells ForkDeploy how your monorepo is structured — which services exist, how they wire together, and what order to deploy them.

vs Kubernetes

The same three-service app (database + API + frontend), in both formats. Kubernetes requires one manifest per resource type — Deployment, Service, Ingress, Secret — for every service. ForkDeploy is one file that describes the whole project.

8+

Kubernetes files

~150

Kubernetes lines

1

ForkDeploy files

18

ForkDeploy lines

Kubernetes 8 files · ~150 lines
# api-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
  labels:
    app: api
spec:
  replicas: 1
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
      - name: api
        image: my-registry/api:latest
        ports:
        - containerPort: 8080
        env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: url
        resources:
          requests:
            memory: "64Mi"
            cpu: "250m"
          limits:
            memory: "128Mi"
            cpu: "500m"
---
# api-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: api
spec:
  selector:
    app: api
  ports:
  - port: 80
    targetPort: 8080
---
# db-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: db-secret
type: Opaque
stringData:
  url: postgres://shop:shop@db:5432/shop
---
# ... repeat for web, database,
# ingress, PersistentVolumeClaim…
ForkDeploy 1 file · 18 lines
# forkdeploy.yaml — the whole project
project: shop

services:
  database:
    image: postgres:16

  api:
    build: ./apps/api
    port: 8080
    depends_on: [database]
    env:
      DATABASE_URL: "{{ database.url }}"

  web:
    build: ./apps/web
    port: 3000
    depends_on: [api]
    env:
      API_URL: "{{ api.url }}"

No Deployments, Services, Ingress, or Secrets manifests

No label selectors that silently mismatch

No image registry — ForkDeploy builds from your Dockerfile

Service discovery built in — no DNS plugins needed

Deploy order resolved automatically from depends_on

ForkDeploy still runs on Kubernetes under the hood — you get the reliability without ever writing a manifest. The complexity doesn't disappear; it moves out of your repo.

What is it?

Drop forkdeploy.yaml at the repo root alongside your package.json (or go.mod, etc.) and ForkDeploy will:

  • Know which services your project contains
  • Build or pull each service's container image
  • Wire services together using stable internal URLs
  • Deploy them in the right order based on their dependencies

The file is optional — you can also manage services through the dashboard or the REST API directly. But the yaml is the fastest way to define a full project from scratch.

Minimal example

One service, one file. Push the repo and ForkDeploy builds it.

# forkdeploy.yaml
project: my-app

services:
  web:
    build: .
    port: 3000

Full schema

project: <string>       # required — becomes the project slug

services:
  <service-name>:       # required — one entry per service
    image: <string>     # Docker image to pull. Mutually exclusive with build.
    build: <path>       # Relative path to build context (must contain Dockerfile).
    port: <number>      # Port the container listens on.
    replicas: <number>  # Running instances. Default: 1.
    depends_on:         # Services that must be running first.
      - <service-name>
    env:                # Environment variables injected at runtime.
      KEY: value
      OTHER: "{{ <service-name>.url }}"  # service discovery
Field Type Required Description
project string yes Project name. Becomes the URL slug. Use lowercase and hyphens.
services map yes Map of service name → config. At least one entry required.
services.*.image string one of Pre-built Docker image (e.g. postgres:16). Cannot be used with build.
services.*.build string one of Relative path to the build context. A Dockerfile must exist there.
services.*.port number no Port the container process listens on. Used for internal routing.
services.*.replicas number no Number of instances to run. Defaults to 1.
services.*.depends_on string[] no Service names this service waits for before starting.
services.*.env map no Key/value env vars. Values may use {{ service.url }} templates.

Service discovery

Services in the same project can reference each other using template variables in env values:

{{ <service-name>.url }}

ForkDeploy resolves this at deploy time to:

http://<service-name>.<project-slug>.internal
Example: In a project with slug shop, writing {{ database.url }} in your env becomes http://database.shop.internal at runtime. If a service restarts or moves, the DNS stays the same — no hard-coded IPs.

Examples

Three-service monorepo

Database, API that needs it, frontend that needs the API.

project: shop

services:
  database:
    image: postgres:16
    port: 5432
    env:
      POSTGRES_DB: shop
      POSTGRES_USER: shop
      POSTGRES_PASSWORD: shop

  api:
    build: ./apps/api
    port: 8080
    depends_on: [database]
    env:
      DATABASE_URL: "{{ database.url }}"
      NODE_ENV: production

  web:
    build: ./apps/web
    port: 3000
    depends_on: [api]
    env:
      API_URL: "{{ api.url }}"
Deploy order: database → api → web

With replicas and a worker

Scale individual services independently.

project: saas-prod

services:
  cache:
    image: redis:7-alpine
    port: 6379

  worker:
    build: ./apps/worker
    replicas: 3
    depends_on: [cache]
    env:
      REDIS_URL: "{{ cache.url }}"

  api:
    build: ./apps/api
    port: 8080
    replicas: 2
    depends_on: [cache, worker]
    env:
      REDIS_URL: "{{ cache.url }}"
Secrets tip: Don't write passwords or tokens directly in forkdeploy.yaml. Add them through the dashboard (Projects → Secrets) and they'll be injected at runtime without appearing in your repo.

Deploy order

ForkDeploy reads all depends_on fields, builds a dependency graph, and deploys services in topological order.

  • Services with no depends_on deploy first, in parallel.
  • A service only starts after all its dependencies reach running state.
  • Two services with no relationship between them deploy in parallel.
  • Circular dependencies (A → B → A) are rejected and the deploy fails immediately.

Where to put it

Always at the repo root. The file must be named exactly forkdeploy.yaml or forkdeploy.yml.

my-monorepo/
├── forkdeploy.yaml     ← here, at the root
├── package.json
├── apps/
│   ├── api/
│   │   └── Dockerfile
│   └── web/
│       └── Dockerfile
└── packages/
    └── ...

Relationship to the API

Every field in forkdeploy.yaml maps directly to a field in the ForkDeploy data model. You can manage the same resources through the dashboard or REST API if you prefer not to use the file at all.

YAML field API resource
project Project.name / Project.slug
services.<name> Service.name
build Service.buildContext
image Service.image
port Service.port
replicas Service.replicas
depends_on Service.dependsOn
env Service.env