# Environments

> What a new box inherits, kept as versions on your account. Repos to clone, a setup script, env vars, secret files, and which of your credentials a box may receive.

URL: https://prized.dev/docs/environments

## What a box receives [#what-a-box-receives]

Pick an environment at create time (the dashboard form, or `environment` on `POST /api/v1/boxes`), or let the default apply. The box receives, in order:

| Item         | When              | Where it lands                                                                               |
| ------------ | ----------------- | -------------------------------------------------------------------------------------------- |
| Env vars     | first boot + live | `/etc/doppel/box-env.sh`, sourced by every login shell                                       |
| Repos        | first boot        | `~/<dir>` (default: the repo name), cloned as your box user, blocking repos first            |
| Setup script | first boot        | Runs once as your box user in your home, after the repos; log in `/var/log/doppel-setup.log` |
| Secret files | first boot + live | `~/<path>`, owned by your box user, mode 0600 unless you set another                         |

Env vars and secret files are kept live: changes reach the box within seconds, and new login shells see them. Repos and the setup script run at first boot only. Secret files travel over the box's authenticated channel, never in the launch configuration, and the dashboard and CLI show only their path and size.

## The default environment [#the-default-environment]

Nothing is a default until you mark one: "Make it the default for new boxes" when creating, "Make default" on its card, or `prized environments default <name>`. A box created without naming an environment then takes it; `none` (dashboard) or `environment: null` (API) opts out. The box your plan launches, a restore, and a fork never inherit one.

## Versions and upgrade [#versions-and-upgrade]

Every save that changes what a box inherits mints a new immutable version; a rename or a new description does not. A running box keeps the version it was created from until you upgrade it. A name is unique within the workspace regardless of case (`prod` and `Prod` are one name), so `--environment` on the CLI always means the same environment for everyone.

```text title="Versions"
environment ml        v1 ──── v2 ──── v3 (latest)
                       │        │
box kiwi (created v1)  ●────────┼──── upgrade ──▶ v3
box mango (created v2)          ●──── stays on v2 until upgraded
```

`prized environments upgrade <name>` (or **Upgrade** on the environment or a box's page) pins boxes to the latest version:

* **Env vars and secret files re-apply** within seconds; a dropped file or var is removed from the box.
* **Repos and the setup script do not re-run.**
* **A safe-for-third-parties version makes the box restricted, permanently**: mounted secrets stop resolving, its phone password is revoked, device keys leave.
* **A box whose own vars would not fit the launch budget** is skipped (named in `skipped`) and keeps its version.

Each environment keeps up to 50 versions; older ones no live box pins are pruned. Deleting an environment leaves its boxes on the version they carry.

## Safe for third parties [#safe-for-third-parties]

A box built for someone else (a contractor, a demo, an agent you do not fully trust) should hold none of your credentials. Switch on **Safe for third parties** on the environment, or tick **Restricted** when creating one box. Four channels close:

| Channel                | What is refused                                                                                                                                                                                  |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Secret mounts          | `POST /api/v1/boxes/:id/secrets` answers `409 box_restricted`; no [connector](https://prized.dev/docs/connectors) or secret can be mounted, and the box receives no [AWS profile](https://prized.dev/docs/aws)                       |
| Device keys            | No member device SSH keys reach the box; desktop apps that rely on them cannot log in                                                                                                            |
| Phone access           | `POST /api/v1/boxes/:id/mobile-ssh` answers `409 box_restricted`                                                                                                                                 |
| CLI credential handoff | `prized agents handoff` and the agent, secrets and data steps of `prized setup` refuse with `CHECK_FAILED` (exit 5); `prized setup` also keeps `.env` files out of every sync session to the box |

Env vars still apply, but a safe environment cannot carry secret files (`safe_environment_has_secret_files`), and a restricted box never receives any: an environment's secret files are left out of a restricted box's setup at create and on every upgrade, whichever environment it runs. Restricted is one-way for a box: set at create (`restricted: true`, or a safe environment), by an upgrade onto a safe version, or inherited by a restore, and never unset. The three channels can also be closed one at a time on an environment without restricting the box.

## Repos [#repos]

A repo is `owner/name` (GitHub) or an `https://` clone URL, with an optional branch, a directory under your home (default: the repo name), an optional setup script run inside the clone, and a **blocking** flag: blocking repos clone before the others and before the environment's setup script.

Clones are plain https, so no credential rides a URL into `.git/config`. For a private repo, clone on the box once `gh` is signed in there ([Credentials](https://prized.dev/docs/credentials)), or ship a deploy key as a secret file and clone in the setup script. An existing directory is left alone.

## Vars and secret files [#vars-and-secret-files]

Env vars are non-secret by definition: shell-identifier names, single-line values, shown in the dashboard and CLI. Put anything sensitive in a [secret](https://prized.dev/docs/connectors) or a secret file: a body of at most 64 KiB at a path relative to your home (`.aws/credentials`, `.npmrc`), encrypted at rest, decrypted only for its box, written 0600 or the mode you set. Up to 16 per environment.

## Per-box vars [#per-box-vars]

A box can carry its own vars: `env` at create (`POST /api/v1/boxes` with `{"env": {"NAME": "value"}}`), or the Vars editor on the box page (`PATCH /api/v1/boxes/:id` with `{"env": {...}}`), which replaces the set. A per-box var wins over an environment var of the same name. Environment plus per-box vars must fit the launch budget of about 16 KiB compressed, checked as rendered for a snapshot restore; a create or PATCH that would not fit is refused with `environment_too_large`.

## Limits [#limits]

Every cap, the launch budget included, is on [Limits](https://prized.dev/docs/limits#environments).

## CLI [#cli]

`prized environments` (alias `prized envs`); every command takes `--json`.

| Command                                                                                                                                                                                      | What it does                                                                                                                             |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `prized environments ls`                                                                                                                                                                     | One row per environment. JSON: `{"environments": [...]}`                                                                                 |
| `prized environments info <name>`                                                                                                                                                            | Channels, repos, vars, secret files (path, mode, size), versions with box counts. JSON: `{"environment": {...}}`                         |
| `prized environments new <name> [--description] [--setup-script FILE] [--default]`                                                                                                           | Create; `--default` marks it the default for new boxes                                                                                   |
| `prized environments default <name>`                                                                                                                                                         | Make it the default for new boxes                                                                                                        |
| `prized environments rm <name>`                                                                                                                                                              | Delete (asks; `-y` skips); boxes keep their version                                                                                      |
| `prized environments set <name> --safe-for-third-parties true\|false --secret-mounts true\|false --device-keys true\|false --phone-access true\|false [--description] [--setup-script FILE]` | Flip the switches or replace the description or script                                                                                   |
| `prized environments set-var <name> K=V` / `rm-var <name> K`                                                                                                                                 | Set or remove one env var                                                                                                                |
| `prized environments set-file <name> <path> --from FILE\|- [--mode 0600]` / `rm-file <name> <path>`                                                                                          | Set a secret file from a local file or stdin, or remove it                                                                               |
| `prized environments add-repo <name> owner/repo [--branch] [--dir] [--setup-script FILE] [--blocking]` / `rm-repo <name> owner/repo`                                                         | Add, replace, or remove a repo                                                                                                           |
| `prized environments upgrade <name> [--box B]...`                                                                                                                                            | Pin every live box (or just `--box` ones) to the latest version. JSON: `{"environment", "version", "upgraded": [ids], "skipped": [ids]}` |

The CLI's environment object carries the API row's fields in snake\_case (`safe_for_third_parties`, `secret_files`, `latest_version`, `versions` with `boxes` counts).

Exit codes follow the [CLI table](https://prized.dev/docs/cli#for-agents-and-scripts): an unknown environment or a restricted-box refusal is `CHECK_FAILED` (5), a bad value `USAGE` (2), a taken name `CONFLICT` (6).

## API [#api]

All routes take your session or a `dcp_` CLI bearer and are scoped to your workspace. Bodies are JSON.

| Route                                   | Body / answer                                                                                                                                                                                                                    |
| --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /api/v1/environments`              | `{"environments": [row]}`; a row carries `isDefault`, the four switches, `latestVersionNo`, `repos`, `secretFiles` as `{path, mode, size}`, `versions` as `{versionNo, createdAt, boxCount}`                                     |
| `POST /api/v1/environments`             | `{name, description?, baseTier?, setupScript?, envVars?, repos?, secretFiles?: [{path, content (base64), mode?}], safeForThirdParties?, passSecretMounts?, passDeviceKeys?, passPhoneAccess?, isDefault?}` → 201 `{environment}` |
| `PATCH /api/v1/environments/:id`        | Any of the above; a config field mints a version → 200 `{environment}`                                                                                                                                                           |
| `DELETE /api/v1/environments/:id`       | 204; boxes keep their version                                                                                                                                                                                                    |
| `POST /api/v1/environments/:id/default` | Make it the default → `{environment}`                                                                                                                                                                                            |
| `POST /api/v1/environments/:id/toggles` | `{safeForThirdParties?, passSecretMounts?, passDeviceKeys?, passPhoneAccess?}` → `{environment}`                                                                                                                                 |
| `POST /api/v1/environments/:id/vars`    | `{name, value}` → `{environment}`; `DELETE /:id/vars/:name` removes                                                                                                                                                              |
| `PUT /api/v1/environments/:id/files`    | `{path, content (base64), mode?}` → `{environment}`; `DELETE /:id/files?path=` removes                                                                                                                                           |
| `POST /api/v1/environments/:id/repos`   | `{repo, branch?, dir?, setupScript?, blocking?}` → `{environment}`; `DELETE /:id/repos?repo=` removes                                                                                                                            |
| `POST /api/v1/environments/:id/upgrade` | `{boxIds?}` → `{upgraded: [ids], skipped: [ids], latestVersionNo}`                                                                                                                                                               |

On the box routes, `POST /api/v1/boxes` takes `environment` (id or name; `null` = none; absent = your default), `env: {K: V}` and `restricted`; `PATCH /api/v1/boxes/:id` with `{env}` replaces the per-box vars; `GET /api/v1/boxes/:id` carries `environment: {id, name, versionNo, latestVersionNo}`, `envVars` and `restricted`.

Errors: `400 invalid_repo`, `invalid_file`, `invalid_env_var`, `invalid_env`, `too_many_repos`, `too_many_files`, `environment_too_large` (also from the box create and PATCH), `safe_environment_has_secret_files`; `404 not_found`; `409 name_taken`; `409 box_restricted` from the mount and phone-access routes.
