.aether/environment.json file defines how your workspace is set up. It tells Aether what to run after cloning your repository — installing dependencies, running migrations, pulling Docker images, and starting background services like dev servers.
This file lives in your repository and should be committed so every teammate and the agent get the same environment.
Quick Start
Create a.aether/environment.json file at the root of your repository:
Schema Reference
The file is parsed against the schema below. Only the five fields below are read; all are optional. Invalid JSON or a field of the wrong type causes boot to fail loudly with an error — it is never silently ignored.setup
A shell command that prepares your project — installing dependencies, building assets. It runs after the repository is cloned, before the agent starts.
setup runs through bash with a 10-minute timeout. It must be idempotent (safe to re-run) and must not depend on secrets, because seed builds run it without secrets present (see Seed volumes). Put secret-dependent steps in setup_on_boot.
&& so later steps only run if earlier ones succeed:
setup_on_boot
A shell command that runs on every task boot, after the seed volume is attached. Use it for steps that depend on secrets or must run fresh each time — database migrations, private-registry authentication, environment-specific configuration. Same bash runner and 10-minute timeout as setup.
setup and setup_on_boot run. On a boot with a valid seed volume, setup is skipped and only setup_on_boot (and terminals) run.
docker_prewarm
A shell command that runs only during seed builds, after setup, to pull Docker images onto the seed volume so they don’t download at boot time. It is never run on a normal task boot. Docker pulls can be slow, so it gets a 15-minute timeout and is retried up to five times.
terminals
An array of background processes started automatically when the workspace boots and kept running while the agent works — dev servers, file watchers, queue workers.
Terminals are started sequentially and restarted when the workspace reboots. A terminal that fails to start is logged but does not block the others.
Named terminal ports in the repository’s default-branch configuration define standard preview pins and the sticky “Preview” PR comment; catalog changes refresh both. Review links target the publishing task’s workspace. Branch-only listening ports remain available in the detected-port UI. Startup and preview-auth commands still execute from the workspace’s local checkout. Keep each declared port aligned with the port its command actually serves on.
Aether starts
terminals before the agent starts; the agent does not issue those startup calls.
The agent can start additional, non-persistent services with start_service. list_services
identifies each service as declared or adhoc and includes its declared port. A second service
cannot use a command that is already running; Aether reports the existing service so the agent can
use it or stop it explicitly. Changes to a running terminal take effect after it is stopped or the
workspace restarts.previews
An object keyed by preview port. Each entry declares the repository-owned command that knows how to mint a development session for that application:
1 to 65535. Each entry accepts these fields:
The command runs from the repository root through
bash -c and has a 30-second timeout. A non-zero exit, timeout, oversized output, or invalid JSON fails the authenticated-preview request loudly.
The command receives exactly one JSON object on stdin. preview_origin is the target port, while sibling_origins contains the workspace’s other discovered and declared ports:
url for an application-owned bootstrap route or URL fragment that establishes the browser session. The URL must use http or https, contain no URL credentials, and belong to preview_origin or one of sibling_origins.
local_storage for stacks whose client accepts a serialized browser session. reload is optional. This result is returned to the caller as data and is clearly marked as requiring client-side application; the platform does not silently discard it or construct a redirect wrapper.
The two shapes are exclusive. Both keys, neither key, unknown fields, a URL outside the supplied preview origins, or stdout larger than 64 KiB are rejected. See Authenticated previews for CLI and web usage.
With auto: true, the injected preview runtime starts the hook request while the page loads. The request is asynchronous, so application initialization can continue before the result is applied. A url result navigates to the returned URL. A local_storage result is written when the response arrives; clients that read authentication only at startup must return reload: true so they initialize again with the stored session.
The runtime records the attempt in sessionStorage before making the request. Success or failure is therefore attempted only once per tab session at that preview origin; later navigations and reloads in the same tab do not mint another session.
Seed Volumes and Caching
To avoid re-runningsetup on every task, Aether can pre-build a seed volume: a builder machine runs setup and docker_prewarm once, then writes a .seed-prepared marker that records a hash of the checkout’s dependency files and .aether configuration.
On a later boot, if the marker matches the checkout, setup is skipped and only setup_on_boot and terminals run. If the marker is missing or the hash does not match, setup runs first. The marker records completed installation before boot commands run, so retrying a failed boot step does not repeat an already recorded installation. The environment is ready only after boot commands and terminal restoration succeed.
This is why the two commands have different rules:
setupis the cacheable, secret-free prep that the seed captures once.setup_on_bootis the per-boot work that can use secrets and is never cached.
Examples
Node.js / Next.js
Python / Django
Monorepo (Bun Workspaces)
bun install at the monorepo root resolves all workspace dependencies. Use terminals to start each app independently.
Project with Docker Services
Docker on Task VMs
Task VMs run Linux dockerd, which differs from Docker Desktop in a few important ways.host.docker.internal does not resolve
Symptom: A container cannot reach a service on the task VM through host.docker.internal.
Docker Desktop creates this hostname automatically. Linux dockerd only resolves it when the container has an explicit host-gateway mapping.
Add the mapping to each Compose service that needs host access:
docker run, pass --add-host host.docker.internal:host-gateway instead.
Go builds fail with error obtaining VCS status
Symptom: A Go dev container building from a bind-mounted checkout exits with error obtaining VCS status.
The container user, often root, differs from the task VM user that owns the checkout. Git treats the mount as having dubious ownership, and Go’s build-VCS stamping fails with it.
Trust only the mounted checkout path for every container user:
Shared named volumes fail on the first cold boot
Symptom: Several Compose services mounting the same new named volume can fail on the first cold boot with an error such asmkdir ...: file exists.
When an image contains files under the volume’s mount path, dockerd copies them into a new volume on first use. Multiple services starting together can race while creating and populating the same paths.
Prefer a dedicated mount path with no content baked into the image:
Execution Order
On a normal task boot, Aether runs the configuration in this order:- Clone the repository
- Write secret files to disk and inject environment variables into the agent’s process environment
- Run
setup— skipped if a valid seed volume is attached - Run
setup_on_boot - Start
terminalsin the background - Start the agent
setup exits non-zero, boot fails and the error is reported — check the workspace logs (and /tmp/setup.log inside the VM) for the full command output.
Best Practices
- Keep
setupidempotent and secret-free. It is cached and run without secrets. Put migrations, registry auth, and other secret-dependent steps insetup_on_boot. - Use
setupfor one-time prep,terminalsfor long-running processes. Installing dependencies issetup. Running a dev server is a terminal. - Don’t put secrets in this file. It is committed to your repository. Use environment variables and secret files for sensitive values.
- Chain setup commands with
&&so a failed step stops the rest.