# Environments and variables

A Bastide project serves its application in one or more environments, and each one receives its own environment variables. This page explains the kinds of environment, the difference between a visible variable and a secret, and how to write them from the console, an agent, the command line or the API.

## Environments

An environment is a place where the application is served: it has its address, its deployments and its variables. The model knows four kinds.

| Kind | Value in the API | How it comes into being |
| --- | --- | --- |
| Production | `production` | With the project, always, and only one per project. |
| Preview | `preview` | When a branch is pushed, or on demand: see [Previews](previews.md). |
| Staging | `staging` | Not yet available: no operation creates this environment today. |
| Custom | `custom` | Not yet available: no operation creates this environment today. |

The price list provides custom environments from the Standard tier upwards (2 on Standard, 10 on Pro and Sovereign). Until their creation is served, a project works with its production and its previews.

Every environment has an `env_…` identifier. Reading variables, deploying and restoring all require it, and the only way to learn it is to list the project's environments.

### Environment states

The **“Environnements”** (Environments) card on the project's **“Vue d'ensemble”** (Overview) shows, for each environment, the commit it serves and its state.

- **Épinglé** (pinned): the environment serves a restored version and no longer follows pushes. See [Restore](restore.md).
- **Isolé · bifurcation** (isolated, fork): a preview opened from a fork of the repository. It receives no sensitive variable.
- **En veille** (asleep): the environment is idle for lack of traffic and wakes on the next request.
- **supprimé le …** (removed on …): the date on which a preview will be removed.

Unpinning puts the last healthy deployment back into service and makes the environment follow pushes again. It is a promotion, reserved for the Maintainer rank: open the row's “⋯” menu, then **Lever l'épinglage** (Unpin), or use the `environment_unpin` tool.

## Variables and secrets

A variable has a name, a value and a sensitivity.

- **Visible**: the value can be read back by anyone with a sufficient rank (see the table below).
- **Secret**: the value is encrypted when written and is never read back, not in the console, not through the API, not by an agent, whatever the rank. A secret is replaced: you write a new value under the same name.

By default, a variable written to production is a secret; elsewhere, it is visible. You can choose the other setting on each write.

The `secret.reveal` operation exists in the registry so that the request is refused by name and logged: no role is allowed it. A read that names a secret receives the `secret_never_read` refusal.

### When a variable takes effect

A change takes effect on the environment's next deployment. The live version keeps the set of variables it was built with until then. After writing a variable, redeploy: see [Deploy](deploy.md).

### Allowed names

A name starts with an uppercase letter, then contains only uppercase letters, digits and underscores, 64 characters at most. It is read as is in code, for example `process.env.NEXT_PUBLIC_SITE_URL`.

Some names are refused on write, because the interpreter or the dynamic linker reads them before the program and they would change the behaviour of the image without leaving a trace in the build plan:

```
BASH_ENV  DYLD_INSERT_LIBRARIES  DYLD_LIBRARY_PATH  ENV  GIT_EXTERNAL_DIFF
GIT_SSH_COMMAND  IFS  LD_AUDIT  LD_LIBRARY_PATH  LD_PRELOAD  NODE_EXTRA_CA_CERTS
NODE_OPTIONS  PATH  PERL5OPT  PYTHONPATH  PYTHONSTARTUP  RUBYOPT  SHELL  SSL_CERT_FILE
```

A variable bearing one of these names can still be deleted.

### Inheritance

A preview inherits the variables of the project's staging environment, when there is one: a variable of the preview's own hides an inherited variable of the same name. Production, staging and custom environments inherit nothing. The inventory says, for each variable, whether it belongs to the environment or is inherited, and from which environment.

### Variables set by the platform

- `PORT`: the port the application must listen on, set in the container.
- `NODE_ENV`: set to `production` in the Node.js images the platform builds.
- `DATABASE_URL` and `DIRECT_URL`: when the project has its PostgreSQL database, the platform sets them on the environment before the build. `DATABASE_URL` goes through the connection pooler in transaction mode; `DIRECT_URL` in session mode, with a single connection, for migration tools such as `prisma migrate`. The inventory marks them “Fournie par Bastide” (provided by Bastide).

Your own value always wins. If you write `DATABASE_URL`, the variable becomes yours and the platform no longer touches it. A repository configured for SQLite keeps its `DATABASE_URL`, and an isolated preview receives neither address.

### Variables required by the repository

If the repository contains a `.env.example` file, every variable it declares must exist in the environment before the build. Otherwise the deployment is refused with `variables_requises_manquantes`, and the report lists them. A variable that the file's comments call optional (“optional”, “facultatif”, “leave empty”…), or that carries an example value without having a secret's name, only produces a warning in the build log.

## From the console

Open **“Variables”** in the project menu. The tabs at the top choose the environment; the inventory shows each variable's name, sensitivity, origin, revision and date, never its value.

1. In the **Ajouter une variable** (Add a variable) panel, enter the name and the value, then choose **Secret** or **Visible**.
2. Tick one or more environments: one write goes to each.
3. Click **Enregistrer** (Save). A banner confirms and offers **Défaire** (Undo) for 5 minutes.

**Modifier** (Edit) reads back a visible variable's value before replacing it. **Remplacer** (Replace) on a secret opens an empty field: the old value is not shown. **Remplacer ici** (Replace here) on an inherited variable creates a variable of the environment's own that hides it. The bin icon deletes a variable of the environment's own; the live version keeps it until the next deployment.

### Importing a .env file

**Importer un .env** (Import a .env) opens a window where you paste the content or choose a file; you can also drop the file onto the page. The file is read in the browser, its content is not displayed, and each variable is then sent as a separate write.

- A blank line, or one starting with `#`, is ignored; a leading `export ` is tolerated.
- Between double quotes, `\n`, `\t`, `\"` and `\\` are interpreted and the value may span several lines, which lets you paste a PEM key. Between single quotes, the value is literal.
- A key defined twice keeps the last value, with a warning.
- `${OTHER}` is not substituted: the value is sent as is, with a warning.
- A file larger than 1 MB is not read.

Before sending, the window says what will happen to each variable (added, replaces the current value, will hide the inherited one) and lets you tick **Secret** row by row. A refused write does not stop the others. **Défaire l'import** (Undo the import) reverts every write for 5 minutes.

## Writing to production, writing elsewhere

Production has its own operations, separate from those of the other environments, with a higher rank.

| Action | Outside production | Production | Minimum rank |
| --- | --- | --- | --- |
| List the names, without values | `env_list` | `env_list` | Developer |
| Read the visible values | `env_pull_nonproduction` | `env_pull_production` | Developer / Maintainer |
| Write | `env_write_nonproduction` | `env_write_production` | Developer / Maintainer |
| Delete | `env_delete_nonproduction` | `env_delete_production` | Developer / Maintainer |

A non-production operation called on production is refused (`production_environment` or `wrong_operation`), and the message names the operation to use. Production operations take no environment identifier: there is only one per project.

A write or a deletion returns an `undoToken`: the `action_undo` tool reverts it within five minutes, once, for the same account. A service account can neither write nor delete a variable.

## Through an agent

The variable tools are marked destructive: your MCP client asks for your consent before each write or deletion. An agent never receives the value of a secret.

1. `environment_list` returns the environments and their identifiers.
2. `env_list` returns an environment's inventory, with the counters to compare against the caps.
3. `env_write_nonproduction` or `env_write_production` writes the variable.

```json
{
  "projectId": "prj_01JQ8ZK4M7NP2RSTVWXYZ34567",
  "environmentId": "env_01JQ8ZK4M7NP2RSTVWXYZ34567",
  "key": "NEXT_PUBLIC_API_URL",
  "value": "https://api.preview.example.com",
  "sensitive": false,
  "idempotencyKey": "preview-api-variable-2026-09-14"
}
```

`env_pull_nonproduction` and `env_pull_production` return the visible values. Each value arrives wrapped and marked as untrusted data, and sensitive variables are named in `clesOmises` rather than silently left out: a caller thus knows they exist and does not overwrite them by mistake.

```json
{
  "variables": {
    "NEXT_PUBLIC_API_URL": {
      "untrusted": true,
      "source": "variable-d-environnement",
      "content": "https://api.preview.example.com"
    }
  },
  "clesOmises": ["PAYMENT_SECRET_KEY"]
}
```

## From the command line

```sh
bastide environment list --project-id prj_01JQ8ZK4M7NP2RSTVWXYZ34567
bastide env list --project-id prj_01JQ8ZK4M7NP2RSTVWXYZ34567 --environment-id env_01JQ8ZK4M7NP2RSTVWXYZ34567
bastide env write production --project-id prj_01JQ8ZK4M7NP2RSTVWXYZ34567 --key PAYMENT_SECRET_KEY --value '<key>'
bastide env pull nonproduction --project-id prj_01JQ8ZK4M7NP2RSTVWXYZ34567 --environment-id env_01JQ8ZK4M7NP2RSTVWXYZ34567
```

`env pull` prints the visible values; it does not write a file. Writes and deletions ask for confirmation unless you pass `--yes`. In a directory linked with `bastide link`, `--project-id` can be left out.

`bastide doctor` compares the names in your machine's `.env` files with those of the deployed environment, without ever reading a value, and suggests the command that brings the two sides together.

> **Tip.** A value passed with `--value` stays in your shell history. For a secret, prefer the console, or remove the line from your history afterwards.

## Through the API

```sh
curl -X PUT https://api.bastide.app/v1/projects/prj_01JQ8ZK4M7NP2RSTVWXYZ34567/env/production \
  -H "Authorization: Bearer <your-token>" \
  -H "Content-Type: application/json" \
  -d '{"key": "PAYMENT_SECRET_KEY", "value": "<key>", "idempotencyKey": "payment-secret-2026-09-14"}'
```

The other routes are `GET /v1/projects/{projectId}/env` (inventory), `GET …/env/pull` and `…/env/pull/production` (visible values), and `PUT` and `DELETE` on `…/env` (outside production) and `…/env/production`. See the [API reference](../reference/en/api.md).

## Limits

They are the same on every tier.

| Limit | Value |
| --- | --- |
| Variables per environment | 1,000 |
| Combined size of an environment's values | 256 KB (262,144 bytes) |
| Size of one value | 64 KB (65,536 bytes) |
| Length of a name | 64 characters |
| Window to undo a write | 5 minutes |

## Common errors

| Code | Cause | Remedy |
| --- | --- | --- |
| `production_environment`, `wrong_operation` | A non-production operation targeted production. | Use `env_write_production`, `env_pull_production` or `env_delete_production`, with the Maintainer rank. |
| `secret_never_read` | The read named a sensitive variable. | Replace the secret with a new value; it cannot be read back. |
| `quota_exceeded` | 1,000 variables or 256 KB reached. | Delete unused variables, or combine values. |
| `invalid_request` | Name outside the grammar, refused name (`LD_PRELOAD`…) or value over 64 KB. | Fix the name or the value; the message names the rule. |
| `secret_store_unavailable` | The secret store cannot be reached. | Try again later; nothing was written. |
| `variables_requises_manquantes` | The repository's `.env.example` declares a missing variable. | Write the variable, or mark it optional in `.env.example`, then redeploy. |

## See also

- [Previews](previews.md)
- [Deploy](deploy.md)
- [Restore](restore.md)
- [Agent safety](agent-safety.md)
- [Service accounts](service-accounts.md)
- [env.write.production](../reference/operations/env.write.production.md), [env.pull.nonproduction](../reference/operations/env.pull.nonproduction.md), [secret.reveal](../reference/operations/secret.reveal.md) (operation pages, in French)
