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
# 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.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 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 }}" 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 }}" 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 |