--- title: "Environments" description: "Reusable named execution environments for workflow runs" --- 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 `[environments.default]`, and then continues startup. This compatibility rewrite only handles direct field mappings. Unsupported legacy fields fail with a migration message that lists the keys to edit manually. The rewrite path will be removed before v1.0. Runs select environments by slug: ```toml title="workflow.toml" [run.environment] id = "fabro-dev" ``` Environment catalogs can live in `settings.toml`, `.fabro/project.toml`, or `workflow.toml`, and merge through the normal settings precedence. The built-in default is `default`, a Docker environment using `buildpack-deps:noble`. ## Defining environments ```toml title="workflow.toml" [run.environment] id = "fabro-dev" [environments.fabro-dev] provider = "daytona" # local | docker | daytona [environments.fabro-dev.image] dockerfile = { path = "Dockerfile" } [environments.fabro-dev.resources] cpu = 8 memory = "16GB" disk = "20GB" [environments.fabro-dev.network] mode = "cidr_allow_list" # allow_all | block | cidr_allow_list allow = ["10.0.0.0/8"] [environments.fabro-dev.lifecycle] preserve = false stop_on_terminal = true auto_stop = "30m" [environments.fabro-dev.labels] repo = "fabro-sh/fabro" [[environments.fabro-dev.volumes]] id = "vol-agent-state" mount_path = "/home/daytona/agent-state" subpath = "auth" [environments.fabro-dev.env] NODE_ENV = "development" ``` Run-level overrides are sparse and apply only to the selected environment: ```toml title="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: ```bash 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 default ```toml [run.environment] id = "default" [environments.default] provider = "docker" [environments.default.image] docker = "buildpack-deps:noble" [environments.default.resources] cpu = 2 memory = "4GB" [environments.default.lifecycle] preserve = false stop_on_terminal = true ``` ## Provider mappings | Environment field | Local | Docker | Daytona | |---|---|---|---| | `image.docker` | Ignored | Docker image | Error | | `image.dockerfile` | Ignored | Warning; ignored | Snapshot Dockerfile; Fabro computes the snapshot name | | `resources.cpu` | Warning; ignored | `cpu_quota = cpu * 100000` | Snapshot CPU | | `resources.memory` | Warning; ignored | Container memory limit | Snapshot memory | | `resources.disk` | Warning; ignored | Warning; ignored | Snapshot disk | | `network.mode = "allow_all"` | Host network | Docker default bridge | Daytona allow-all | | `network.mode = "block"` | Error | Docker `none` network | Daytona block | | `network.mode = "cidr_allow_list"` | Error | Error | Daytona CIDR allow-list | | `labels` | Warning; ignored | Warning; ignored | Daytona labels | | `volumes` | Warning; ignored | Warning; ignored | Daytona volume mounts | | `lifecycle.auto_stop` | Warning; ignored | Warning; ignored | Daytona auto-stop | | `env` | Process environment overlay | Container environment | Sandbox 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. ```toml [run.environment] id = "host" [environments.host] 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. ```toml [run.environment] id = "ci" [environments.ci] provider = "docker" [environments.ci.image] docker = "buildpack-deps:noble" [environments.ci.resources] cpu = 2 memory = "4GB" [environments.ci.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. ```toml [run.environment] id = "cloud" [environments.cloud] provider = "daytona" [environments.cloud.image] dockerfile = { path = "Dockerfile" } [environments.cloud.resources] cpu = 4 memory = "8GB" disk = "20GB" [environments.cloud.lifecycle] auto_stop = "30m" [environments.cloud.network] mode = "cidr_allow_list" allow = ["208.80.154.232/32", "10.0.0.0/8"] ``` Daytona volumes reference existing provider-managed volumes: ```toml [[environments.cloud.volumes]] id = "vol-agent-state" mount_path = "/home/daytona/agent-state" subpath = "agent-auth" ```