This chapter was produced by auditing the source (`grep -rn "env::var" testing-framework/ cfgsync/ --include="*.rs"`), not by convention. If a variable is not listed here, the framework does not read it. Re-run the grep after upgrading.
The three `LOGOS_BLOCKCHAIN_*` names are historical; they are only consulted when telemetry inputs come from the environment rather than from an `ObservabilityCapability`; see [Telemetry and External Observability](telemetry.md).
| `TF_KEEP_LOGS` | Preserve per-node working directories (`1`/`true`/`yes`) | `deployers/local/src/lib.rs`, honored by the orchestrator and `ManualCluster` | directories deleted at teardown (unless the deployment policy preserves them) |
-`EnvBinaryProvider::new("MY_NODE_BIN")` reads that variable as an explicit executable path. Unset or not-a-file counts as unresolved, letting a `FallbackBinaryProvider` continue to the next provider.
-`DownloadUrl::Env(var)` / `DownloadChecksum::Env(var)` on `DownloadBinaryProvider` read the download URL and expected SHA-256 from the named variables. A missing URL variable is a hard error (`MissingDownloadUrl`); a missing checksum variable disables verification.
| `COMPOSE_RUNNER_PRESERVE` | Skip `docker compose down`, keep the workspace | `lifecycle/cleanup.rs` | full teardown |
| `TESTNET_RUNNER_PRESERVE` | Alias for the above | `lifecycle/cleanup.rs` | full teardown |
| `COMPOSE_RUNNER_HOST` | Host used to reach published container ports | `infrastructure/ports.rs` | `127.0.0.1` |
| `COMPOSE_RUNNER_HOST_GATEWAY` | Explicit `extra_hosts` gateway entry; `disable` or empty removes it | `docker/platform.rs` | falls through to `DOCKER_HOST_GATEWAY` |
| `DOCKER_HOST_GATEWAY` | Gateway IP mapped as `host.docker.internal:<ip>` | `docker/platform.rs` | `host.docker.internal:host-gateway` |
| `TESTNET_PRINT_ENDPOINTS` | If set (any value), print discovered endpoints after deploy | `deployer/orchestrator.rs` | silent |
| `REPO_ROOT_OVERRIDE_DIR` | Override repository-root detection for stack assets | `docker/workspace.rs` | falls through to `CARGO_WORKSPACE_DIR`, then manifest-relative detection |
| `CARGO_WORKSPACE_DIR` | Workspace root override (also used by template rendering) | `docker/workspace.rs`, `infrastructure/template.rs` | manifest-relative detection |
| `REL_ASSETS_STACK_DIR` | Alternative stack-assets directory (absolute, or relative to repo root) | `docker/workspace.rs` | bundled default assets |
Per-application image selection is again a mechanism with caller-derived names: `BinaryConfigNodeSpec::conventional("/usr/local/bin/kvstore-node", ...)` derives the prefix `KVSTORE` and reads `KVSTORE_IMAGE` (default `kvstore-node:local`) and `KVSTORE_PLATFORM` (`descriptor/node.rs`).
Image selection mirrors compose with a k8s-specific override first: `BinaryConfigK8sSpec::conventional` reads `<PREFIX>_K8S_IMAGE`, then `<PREFIX>_IMAGE`, then the `<binary-name>:local` default (`env.rs`). `workspace.rs` additionally exposes `resolve_workspace_root` / `resolve_optional_relative_dir` helpers that read a variable **named by the caller**.
These are read by the cfgsync **client inside node containers** at startup, not by your test process; the deployers set them when rendering the stack. See [Static Artifacts and cfgsync](cfgsync.md).
The example applications define their own variables through the mechanisms above. **These belong to the examples**: `KVSTORE_NODE_BIN` is defined by the kvstore example's environment implementation, not by the framework; your application will define its own equivalents. Found by auditing `examples/`: