Configuration reference
This page lists the fields accepted in fed.yaml. Start with
the quickstart if you have not run a stack yet, or the
Compose guide if your dependencies already live in a
Compose file.
The canonical shape
Declare every host or process port that must vary as a type: port
parameter, then interpolate it with {{...}} wherever it appears.
Literal listener and host-mapping ports can't be remapped by
isolation; fixed container-internal ports such as
Postgres's 5432 can stay literal.
parameters:
API_PORT:
type: port
default: 8080
DB_PORT:
type: port
default: 5432
DB_PASSWORD:
type: secret
services:
database:
image: postgres:16-alpine
ports: ["{{DB_PORT}}:5432"]
environment:
POSTGRES_PASSWORD: '{{DB_PASSWORD}}'
POSTGRES_DB: app
api:
process: npm start -- --port {{API_PORT}}
depends_on: [database]
startup_message: 'http://localhost:{{API_PORT}}'
environment:
DATABASE_URL: 'postgres://postgres:{{DB_PASSWORD}}@localhost:{{DB_PORT}}/app'
healthcheck:
http_get: 'http://localhost:{{API_PORT}}/health'
entrypoint: api
Services
Every service has one type-defining field: process or
image. Services imported from a top-level compose entry join the
graph automatically. A service
can also consist of lifecycle hooks alone. (Two specialized
kinds exist besides these: gradle_task runs a Gradle task, and
dependency + service imports a service another project
marked expose: true; see Expose for the
transitive-dependency rule.)
Key names are snake_case. fed 7.2 and earlier used camelCase for keys including
httpGet and gradleTask, and spelled the restart tag !onfailure.
Those legacy spellings still parse in every release, so existing configs keep
working; fed validate points them at the new names. The snake_case
names shown throughout these docs require fed 7.3.0 or later.
Process
Run any command on the host:
services:
api:
process: npm start
cwd: ./api
Hook-only (setup nodes)
A service can be nothing but its lifecycle hooks. You want this when several
services need the same prepared state (a pushed schema, an installed workspace),
because install/migrate on a normal service gate only that
service. Declare the preparation once, and let everyone depend on it:
services:
db-schema:
migrate: npx prisma db push
depends_on: [postgres]
environment:
DATABASE_URL: '{{DATABASE_URL}}'
api:
process: npm start
depends_on: [db-schema] # waits for the push to finish
The node runs after startup polling for its dependencies completes; dependents start
only after its hooks complete. A failed hook aborts fed start with the node's name.
healthcheck and restart are rejected, since completion is its
readiness, and fed status shows it as completed.
Docker container
parameters:
REDIS_PORT:
type: port
default: 6379
services:
redis:
image: redis:7-alpine
ports: ["{{REDIS_PORT}}:6379"]
Command override
command overrides the image's CMD, as a string or an array. The string
form is tokenized the way a shell would: quotes group an argument together, so you
only need the array form when you want to bypass tokenizing entirely:
services:
nats:
image: nats:2
command: "--jetstream --store_dir /data"
# or, for an argument containing spaces:
command: "--jetstream --store_dir '/data with spaces'"
# or the array form, always exact:
command: ["--jetstream", "--store_dir", "/data"]
Docker Compose project
Include an existing project once. Each Compose service becomes an individually addressable Fed service:
compose:
- ./docker-compose.yml
Use the service names already declared by Compose in depends_on,
fed start, fed logs, and fed stop. Stopping one imported
service leaves its siblings running. See the Compose guide
for namespaces, profiles, and project-level environment values.
Volumes
Docker services can mount named volumes and bind mounts:
services:
postgres:
image: postgres:16
volumes:
- postgres_data:/var/lib/postgresql/data # Named volume
- ./init-scripts:/docker-entrypoint-initdb.d # Bind mount
Named volumes with a fed- prefix are automatically cleaned up by fed clean.
Tags
Flexible grouping for service selectors. Reference with @tag:
services:
api:
process: npm start
tags: [backend, critical]
worker:
process: npm run worker
tags: [backend, async]
fed start @backend # Start all services tagged "backend"
fed stop @async # Stop all services tagged "async"
fed install @critical # Install only critical services
Watch
File paths for auto-restart when used with fed start --watch:
services:
api:
process: npm start
watch:
- ./src/**/*.ts
- ./package.json
Restart policy
services:
worker:
process: npm run worker
restart: always # Always restart on failure
api:
process: npm start
restart: !on_failure # Restart on failure, with a retry limit
max_retries: 3 # Restart up to 3 times on failure
Use no (the default) or always for the simple forms. The
retry-limited form uses the !on_failure YAML tag shown above; a nested
on_failure: mapping without the tag is not accepted. fed 7.2 and
earlier spelled the tag !onfailure, which still works.
Plain fed start spawns a background supervisor for any service with a
restart: policy, so always and !on_failure keep
working even after the CLI exits and the terminal closes. fed stop always
wins: it stops the supervisor too, so a stopped service never comes back on its own.
Running fed start --watch or fed tui takes over supervision
while they're active, then the background supervisor resumes once they exit.
Grace period
Graceful shutdown timeout before SIGKILL:
services:
api:
process: npm start
grace_period: "30s" # Default: 10s
Accepts duration strings: "10s", "1m", "500ms".
Startup timeout
Per-service cap on the complete start attempt, including install and migrate hooks,
process or container startup, and health-check polling. It overrides the orchestrator
default (120s) for one service. A health check's own timeout can end polling
sooner.
services:
search:
process: ./bin/search-svc
startup_timeout: "5m" # Default: orchestrator-wide (120s)
healthcheck:
http_get: "http://localhost:{{ES_PORT}}/health"
Accepts the same duration strings as grace_period.
Circuit breaker
Crash loop detection. Requires restart: always or the
!on_failure form, and runs under the same supervision as restart
policies above.
services:
api:
process: npm start
restart: always
circuit_breaker:
restart_threshold: 5 # Trips after 5 restarts... (default: 5)
window_secs: 60 # ...within 60 seconds (default: 60)
cooldown_secs: 300 # Wait 5 minutes before retrying (default: 300)
States: closed (normal, restarts allowed) and open (tripped, restarts blocked until the cooldown expires, then normal restart counting resumes).
Expose
Mark the services other projects are meant to import, via
packages (extends) or dependency +
service, with expose: true. Everything else is private,
and both import paths reject it:
services:
api:
process: npm start
expose: true
Only the service you're importing directly needs expose: true; its
own dependencies come along with it and don't need the flag themselves.
Importing an unexposed service fails with an error naming the service and where to add the flag:
# extends an unexposed package service
Package error: Service 'postgres' in package 'db-pkg' is not marked expose: true.
Mark it exposed in the package's fed.yaml before extending it from 'my-db'
# dependency + service import of an unexposed service
External service 'auth' (dependency 'auth-service') is not marked expose: true in
its own config. Mark it exposed before importing it as 'auth-external'
Parameters
Values referenced throughout your config with {{PARAM}}:
parameters:
API_PORT:
type: port
default: 8080 # See resolution order below
DB_PORT:
type: port # No default, allocates a random available port
API_KEY:
default: "dev-key" # String parameter
Resolution priority
- Explicit
valuefield (set programmatically) env_fileentry (later files override earlier ones)default
Shell environment variables are not consulted. Configuration lives in the file, not in whatever happens to be exported.
Port parameters add a persisted layer: once allocated, a port is cached and reused
across restarts. In normal mode an available default wins over the cache
(so editing the config takes effect); under isolation the cache wins and fresh parameters skip the default entirely, since random ports are the
point. fed isolate rotate re-rolls every allocated port.
Validation with either
parameters:
LOG_LEVEL:
default: "info"
either: [debug, info, warn, error] # Validated after resolution
Built-in: FED_PROJECT_ID
Every config resolves {{FED_PROJECT_ID}} without declaring it: a stable,
cookie-safe identifier for the running stack. It's the checkout directory's name plus a
short hash, with the isolation ID appended when isolation is enabled
(myapp-3f2a9c01, myapp-3f2a9c01-iso-0b34525c).
Use it to namespace anything parallel stacks would otherwise share. The classic case
is cookies: localhost cookies are port-agnostic, so two stacks of the same
app on different ports log each other out. Suffix the cookie name and they stop
colliding:
services:
web:
process: npm run dev
environment:
SESSION_COOKIE: 'session.{{FED_PROJECT_ID}}'
Declaring your own parameter named FED_PROJECT_ID is a validation error;
the name is reserved.
Secrets
A type: secret parameter without source: manual is generated on
the first fed start that needs it. The default is a random 32-character
alphanumeric value, persisted in .fed/secrets.generated.env:
parameters:
DB_PASSWORD:
type: secret
SESSION_KEY:
type: secret
generate: "openssl rand -hex 32"
The optional generate command runs through sh -c. References such
as {{DB_PASSWORD}} create dependencies between generators. Generated
secret results are persisted; a generate parameter without
type: secret is recomputed on every start.
Fed manages .fed/.gitignore and writes secret files with mode 0600. See
Generated secrets for storage, custom generators,
invalidation, and overrides.
Manual secrets
For API keys, OAuth credentials, and other externally supplied values, add
source: manual:
parameters:
GITHUB_CLIENT_SECRET:
type: secret
source: manual
description: "GitHub OAuth client secret"
STRIPE_SECRET_KEY:
type: secret
source: manual
optional: true
Fed does not generate manual secrets. It checks local environment files, then a linked team vault. Required values still missing stop startup; optional values resolve to an empty string. See Team secrets.
Constraints
Secret parameters cannot have default or either constraints.
Dependencies & health checks
Services declare dependencies with depends_on. Fed starts dependencies
before dependents and polls configured health checks during startup.
Simple form
services:
api:
process: npm start
depends_on: [database, cache]
Structured form
Control behavior when a dependency fails:
services:
api:
process: npm start
depends_on:
- database # Simple: stop if database fails
- service: cache
on_failure: ignore # Keep running if cache fails
- service: worker
on_failure: restart # Restart if worker fails
on_failure values: stop (default), restart, ignore.
Health check types
services:
database:
image: postgres:15
healthcheck:
command: pg_isready -U postgres # Runs INSIDE the container
timeout: 10s
api:
process: npm start
healthcheck:
http_get: 'http://localhost:{{API_PORT}}/health'
timeout: 5s # Optional, default 5s
http_get: HTTP request from the host.command: for directimage:services, runs inside the container viadocker exec. For process and Compose-backed services, runs on the host.
The health-check timeout is also the startup polling window. If the
process or container stays alive but the check never passes, fed logs a warning and
lets startup continue with the service in Running state; it does not fail
fed start solely because the check timed out.
Simple string form (uses default 5s timeout):
healthcheck: "curl -f http://localhost:{{API_PORT}}/health"
Environment files
.env files set parameter values (not service environment directly):
parameters:
API_KEY:
default: ""
env_file:
- .env # API_KEY=secret123
- .env.local # Later files override earlier
All .env variables must be declared as parameters.
A missing env_file is not a fatal error: fed logs a warning
and continues without it. Parameters that depend on values from the missing file fall back
to their defaults (or fail later with a clearer "missing parameter value" error if they
have no default). Parse errors and other I/O failures (e.g. permission denied) still abort
startup.
Startup messages
Show where to access services after startup:
services:
api:
startup_message: "API docs: http://localhost:{{API_PORT}}/docs"
frontend:
startup_message: "App: http://localhost:{{NEXT_PORT}}"
╭──────────────────────────────────────────────────╮
│ API docs: http://localhost:8081/docs │
├──────────────────────────────────────────────────┤
│ App: http://localhost:3000 │
╰──────────────────────────────────────────────────╯
Entrypoint services sort last. A warning is emitted if an entrypoint has no startup_message, which is particularly useful when ports are randomized.
Templates
Reusable base configurations:
parameters:
API_PORT:
type: port
templates:
java-service:
image: openjdk:17-slim
environment:
JAVA_OPTS: '-Xmx512m'
healthcheck:
http_get: 'http://localhost:{{API_PORT}}/actuator/health'
services:
auth-service:
extends: java-service
ports: ["{{API_PORT}}:8080"]
Placeholders in templates resolve against the same global parameters as everywhere else; a template can't rename them per service.
Profiles
Conditionally include services:
services:
api:
process: npm start # No profiles = always included
worker:
profiles: [worker] # Only with -p worker
debug-tools:
profiles: [debug] # Only with -p debug
fed start # Starts profileless services only
fed -p worker start # Starts api + worker
fed -p worker -p debug start # Starts api + worker + debug-tools
Packages
Import service configurations across projects:
packages:
- source: "github:org/repo@v1.0"
as: "infra"
services:
database:
extends: "infra.postgres"
environment:
POSTGRES_DB: "myapp"
Packages are cached locally. fed package refresh clears the cached copy
so the next command that needs it fetches fresh; fed --offline start skips
git entirely.
Lifecycle hooks
services:
backend:
process: npm start
cwd: ./backend
install: npm ci # Before first start (offline prep)
migrate: npx prisma migrate deploy # After deps healthy, before start
build: npm run build # Runs with `fed build`
clean: rm -rf node_modules dist # Runs with `fed clean`
Two of these answer the question "when should this run?":
install, once per isolation scope. Runs before the service's
first start, then never again (until fed clean, or on demand with
fed install). Put dependency installation here: work that's done until
you wipe it. Install state is tracked per isolation scope, so an
isolated: true script's throwaway stack reruns it there without touching
your checkout's install state.
migrate, every start. Runs after startup polling for the
service's dependencies completes and before the service itself starts, on every
fed start. Write it idempotent, as migration tools already are, so a current
schema is a no-op. This is why a migration you pulled this morning is applied the next
time you start, with no extra step. The service's full resolved environment is
available, and dependents wait for it to complete.
Need the prepared state before other services, not just this one? That's a hook-only service. Need it only when you ask for it? That's a script.
build runs with fed build.
clean runs with fed clean.
fed clean also removes Docker volumes with fed- prefix and
clears install state.
Resource limits
services:
api:
process: npm start
resources:
memory: "512m" # Hard memory limit
memory_reservation: "256m" # Soft limit (Docker only)
memory_swap: "1g" # Memory + swap limit (Docker only)
cpus: "0.5" # CPU limit (0.5 = 50% of one core)
cpu_shares: 512 # Relative CPU weight (default: 1024)
pids: 100 # Max processes/threads
nofile: 65536 # Max open file descriptors
strict_limits: false # Fail startup if limits can't be set (default: false)
For Docker services, these map to docker run flags. For host process
services on Unix, fed enforces memory and nofile with rlimits,
plus pids on Linux. memory_reservation, memory_swap,
cpus, and cpu_shares are Docker-only.
For host processes, strict_limits controls whether a failed
setrlimit aborts startup. The default, false, warns and continues.
Docker reports invalid or unsupported flags as a normal container-start failure.
Docker image builds
services:
web:
cwd: ./apps/web
build:
image: my-app
# dockerfile: Dockerfile (default)
# args: (optional build arguments)
# NODE_ENV: production
fed build builds all services with a build field. fed docker build builds only Docker images. Images are tagged with the git
short hash by default.
fed build # Build all (shell + Docker)
fed build --tag v1.0.0 # Custom tag
fed build --build-arg KEY=VALUE # Extra build args
fed docker build # Build Docker images only
fed docker build --tag v1.0.0 # Custom tag
fed docker build --json # Machine-readable output
fed docker push # Push images to registry
fed docker push --tag v1.0.0 # Push specific tag
Entrypoint
Declare the main service(s). When no service names are passed, fed start
starts these entrypoints and their dependencies. Entrypoints also sort last in startup
messages:
entrypoint: backend # Single entrypoint
# Or multiple:
entrypoints: [frontend, backend]
Cannot specify both entrypoint and entrypoints.
Scripts
Custom commands with dependencies and environment:
scripts:
test:
script: npm test
cwd: ./api
depends_on: [database]
environment:
NODE_ENV: test
timeout: "5m" # Library execution only; see the Scripts page
integration:
script: npm run test:integration
depends_on: [database, redis]
isolated: true # Fresh ports and direct image-backed resources
scenario:
script: ./seed-scenario.sh
depends_on: [web]
keep_services: true # Leave started services running after the script exits
fed run test # Start deps, run script, stop deps
fed run integration # Runs service deps in an isolated child context
fed run scenario # Start deps, run script, leave deps running
isolated: true allocates fresh random ports, scopes direct
image: containers and named volumes, and cleans up after completion.
Compose-backed services keep Compose's path-derived project namespace; the isolated
script context does not give them a second Compose project.
keep_services: true skips borrow-or-own cleanup so the services the script
starts persist (like fed start) until fed stop. It can't be
combined with isolated: true, since an isolated stack is always torn down.
See Scripts.
See also Isolation for how directory scoping works.