Scripts
A script is a command that knows what it needs: fed test:integration starts the database, runs its configured health check, executes your tests, and stops what it
started. Define scripts in the scripts section of your config and run them
with fed run <name> — or the shorthand, fed <name>.
Defining scripts
scripts:
db:migrate:
depends_on: [database]
script: npx prisma db push
test:integration:
depends_on: [db:migrate, api]
cwd: ./tests # Working directory (optional)
timeout: "5m" # Only for non-interactive runs; see Timeout below
script: npm run test:e2e -- "$@" # "$@" passes arguments from CLI
Running scripts
fed run db:migrate # Run a script
fed db:migrate # Shorthand (if no command collision)
fed test:integration -- -t "auth" # Pass arguments after --
Arguments after -- reach the script either way: a script that mentions "$@" or $1 receives them as shell positional parameters; any
other script gets them appended to the command, shell-escaped. So script: npx prisma is enough to make fed prisma db push work.
Output modes
fed run --output <mode> controls where dependency-service logs go: captured (default — kept in a ring buffer, shown on failure), passthrough (streamed live), or file (written to a log file). Useful in CI.
Service lifecycle ("borrow or own")
A script is a good guest: it stops the services it started, and leaves alone the services that were already running.
- If a dependency was not running, the script starts it and stops it again when the script finishes — including transitive dependencies, and even if the script fails or you interrupt it with Ctrl+C.
- If a dependency was already running (e.g. you ran
fed startfirst), the script borrows it and leaves it running.
Cleanup runs on every exit path: success, failure, and interruption. The first Ctrl+C lets the script shut down and then tears down what it started; a second Ctrl+C force-quits a script that ignores the first (the services it started are still stopped).
So fed start is how you keep a service up across many script runs:
fed start database # database is now session-owned
fed test:integration # borrows database, leaves it running
fed test:integration # ...and again, no slow restart
fed run db:migrate # nothing was running, so db is started…
# …and stopped again when the migration finishes
By default the lifecycle is decided at runtime by who started the service — no
per-service configuration needed. When a script depends on another script, only the
outermost run performs cleanup, so nested script-dependencies never tear down services
mid-run. A script can opt out of ownership entirely with keep_services.
Keeping services running (keep_services)
Sometimes you want a script to leave its services up — a seed or scenario
script that sets up state, prints a few URLs, and expects you to keep poking at the
running stack afterward. Set keep_services: true and the run skips its
borrow-or-own cleanup:
scripts:
scenario:
keep_services: true
depends_on: [web]
script: ./seed-scenario.sh
fed scenario # starts web (+ its deps), seeds state, and leaves them running
# ...poke at the app in a browser...
fed stop # tear the stack down when you're done
The services the run started are left on the same footing as a fed start:
they persist until you stop them. Services that were already running are still
borrowed, exactly as without the flag.
keep_services is read only for the script you invoke directly. When a keep_services script is pulled in as another script's dependency, the
outermost run owns cleanup and its setting wins — a non-keeping parent still tears
everything down.
keep_services can't be combined with isolated: true: an isolated script runs in
a throwaway stack that is always cleaned up on exit, so there is nothing to keep running.
fed rejects that combination at config-validation time.
Isolated scripts
For integration tests that need a throwaway stack without interfering with your running dev services:
scripts:
test:integration:
isolated: true # Fresh ports and direct image-backed Docker resources
depends_on: [database, api]
script: npm run test:e2e
fed start # Dev stack stays running
fed test:integration # Tests get their own stack, cleaned up after
When isolated: true is set:
- Fresh ports are allocated, independent of your dev stack.
- Direct
image:containers and named volumes are scoped with a unique isolation ID. Bind mounts are unchanged. - Cleanup happens automatically on every exit path — success, failure, or Ctrl+C — so resources started in the child context are stopped.
Service dependencies run in the isolated child stack. A dependency that names another script runs according to that script's own configuration; it does not automatically inherit the parent's isolated context.
isolated: true dependency while the same Compose project is running in
the parent stack.For process and direct image-backed dependencies, this is the recommended way to run integration tests: your dev stack is untouched and each test run gets a clean child environment.
Environment variables
A script inherits fed's process environment. Entries mapped under
environment: are added or overridden, but parameters are not exported
automatically. Mapped values, and the script: command itself, accept
{{PARAM}} placeholders resolved to the ports and secrets this
directory was actually allocated:
scripts:
psql:
environment:
DATABASE_URL: '{{DATABASE_URL}}' # Interpolated parameter
TEST_MODE: "true" # Plain value
script: psql {{DATABASE_URL}}
That resolution is the reason to route commands through fed instead of running them
bare: fed psql in an isolated worktree connects to that worktree's database, while a bare psql hits
whichever stack owns the default port. Wrap anything that reads DATABASE_URL or a service port in a script, and the right values follow it across checkouts.
Working directory
Scripts run in the project root by default. Use cwd to change this:
scripts:
test:e2e:
cwd: ./tests/e2e
script: npx playwright test
In v5.2.1 a relative script cwd is resolved by the spawned shell relative
to the directory where you invoked fed, not automatically against the config file. Run
fed from the project root when using a relative script cwd.
Timeout
In v5.2.1, timeout is used by fed's library API but is not enforced by the
fed run CLI or its nested script dependencies. Do not rely on it to bound a
command launched from the CLI:
scripts:
long-migration:
depends_on: [database]
timeout: "30m"
script: ./run-heavy-migration.sh
Supports milliseconds, seconds, minutes, or bare seconds: "500ms",
"30s", "5m", "300". Hours such as
"1h" are not accepted.
Script dependencies
Scripts can depend on both services and other scripts:
scripts:
db:migrate:
depends_on: [database]
script: npx prisma migrate deploy
db:seed:
depends_on: [db:migrate]
script: npx prisma db seed
test:e2e:
depends_on: [db:seed, api]
script: npm run test:e2e
Dependencies are started in order. Service dependencies are started and health-checked before the script runs.