Skip to main content
Fabro separates environments from sandboxes:
  • An environment is reusable desired configuration: provider, image, resources, network, lifecycle, labels, volumes, and environment variables.
  • A sandbox is the concrete runtime instance Fabro creates for a run from the selected environment.
Older pre-v1.0 config files that still use [run.sandbox] are temporarily auto-migrated when Fabro loads them from disk. Fabro writes a sibling *.legacy-sandbox-migration.bak file, rewrites the config to [run.environment] plus an environment definition, and then continues startup.Similarly, [environments.*] tables in the server’s active settings.toml are auto-migrated on startup: each entry is extracted into a sibling environments/<id>.toml file, with a .settings-environments-migration.bak backup written first.These compatibility rewrites only handle direct field mappings. Unsupported legacy fields fail with a migration message that lists the keys to edit manually. The rewrite paths will be removed before v1.0.
Runs select environments by slug:
workflow.toml
[run.environment]
id = "fabro-dev"
Environments are server-managed. The server keeps one TOML file per environment in an environments/ directory next to its settings.toml, seeded on first startup with built-in default, local, docker, and daytona environments. Manage them by editing those files or through the /api/v1/environments REST API. Workflow and project TOML can additionally define [environments.<slug>] catalog entries that merge with the server catalog through the normal settings precedence. The built-in default is default, a Docker environment using buildpack-deps:noble.

Defining environments

Each server-managed environment is a file whose name is its slug:
environments/fabro-dev.toml
provider = "daytona" # local | docker | daytona

[image]
dockerfile = { path = "Dockerfile" }

[resources]
cpu = 8
memory = "16GB"
disk = "20GB"

[network]
mode = "cidr_allow_list"       # allow_all | block | cidr_allow_list
allow = ["10.0.0.0/8"]

[lifecycle]
preserve = false
stop_on_terminal = true
auto_stop = "30m"

[labels]
repo = "fabro-sh/fabro"

[[volumes]]
id = "vol-agent-state"
mount_path = "/home/daytona/agent-state"
subpath = "auth"

[env]
NODE_ENV = "development"
The same fields nest under [environments.<slug>] when defined in workflow or project TOML instead:
workflow.toml
[run.environment]
id = "fabro-dev"

[environments.fabro-dev]
provider = "daytona"

[environments.fabro-dev.image]
dockerfile = { path = "Dockerfile" }
Run-level overrides are sparse and apply only to the selected environment:
workflow.toml
[run.environment]
id = "fabro-dev"

[run.environment.resources]
memory = "32GB"

[run.environment.lifecycle]
preserve = true
env and labels merge by key. volumes replace as a whole list when set at a higher-precedence layer.

Selecting an environment from the CLI

Use --environment with an environment slug:
fabro run workflow.fabro --environment fabro-dev
fabro preflight workflow.fabro --environment ci
fabro server start --environment default
--preserve-sandbox still controls the concrete runtime instance lifecycle for a run. Runtime commands such as fabro sandbox ssh keep the word “sandbox” because they operate on an already-created runtime instance.

Built-in environments

The server seeds four built-in environments on first startup: default, local, docker, and daytona. Missing files are re-seeded, so editing a seeded file customizes it while deleting it restores the built-in definition on the next restart. The seeded default:
environments/default.toml
provider = "docker"

[image]
docker = "buildpack-deps:noble"

[resources]
cpu = 2
memory = "4GB"

[lifecycle]
preserve = false
stop_on_terminal = true

Provider mappings

Environment fieldLocalDockerDaytona
image.dockerIgnoredDocker imageError
image.dockerfileIgnoredWarning; ignoredSnapshot Dockerfile; Fabro computes the snapshot name
resources.cpuWarning; ignoredcpu_quota = cpu * 100000Snapshot CPU
resources.memoryWarning; ignoredContainer memory limitSnapshot memory
resources.diskWarning; ignoredWarning; ignoredSnapshot disk
network.mode = "allow_all"Host networkDocker default bridgeDaytona allow-all
network.mode = "block"ErrorDocker none networkDaytona block
network.mode = "cidr_allow_list"ErrorErrorDaytona CIDR allow-list
labelsWarning; ignoredWarning; ignoredDaytona labels
volumesWarning; ignoredWarning; ignoredDaytona volume mounts
lifecycle.auto_stopWarning; ignoredWarning; ignoredDaytona auto-stop
envProcess environment overlayContainer environmentSandbox environment

Local

local runs tools directly in the resolved working directory. It offers no filesystem or network isolation, so use it only for trusted workflows.
environments/host.toml
provider = "local"
Fabro hard-errors if a local environment asks for blocked or CIDR-restricted networking because the provider cannot enforce it.

Docker

Docker runs tools inside a container created from image.docker. Docker is the built-in default provider.
environments/ci.toml
provider = "docker"

[image]
docker = "buildpack-deps:noble"

[resources]
cpu = 2
memory = "4GB"

[network]
mode = "block"
Docker and Daytona are clone-based providers. When a run has a GitHub origin, Fabro clones it into the provider workspace. Set [run.clone] enabled = false to start with an empty workspace.

Daytona

Daytona runs tools in a cloud sandbox. Without image.dockerfile, Fabro uses Daytona’s built-in daytona-medium snapshot. With image.dockerfile, Fabro computes a deterministic internal snapshot name from the Dockerfile, resource hints, a single-tenant scope, and the Daytona API key.
environments/cloud.toml
provider = "daytona"

[image]
dockerfile = { path = "Dockerfile" }

[resources]
cpu = 4
memory = "8GB"
disk = "20GB"

[lifecycle]
auto_stop = "30m"

[network]
mode = "cidr_allow_list"
allow = ["208.80.154.232/32", "10.0.0.0/8"]
Daytona volumes reference existing provider-managed volumes:
environments/cloud.toml
[[volumes]]
id = "vol-agent-state"
mount_path = "/home/daytona/agent-state"
subpath = "agent-auth"