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. |
| 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.
- 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.
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 toproductionin the Node.js images the platform builds.DATABASE_URLandDIRECT_URL: when the project has its PostgreSQL database, the platform sets them on the environment before the build.DATABASE_URLgoes through the connection pooler in transaction mode;DIRECT_URLin session mode, with a single connection, for migration tools such asprisma 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.
- In the Ajouter une variable (Add a variable) panel, enter the name and the value, then choose Secret or Visible.
- Tick one or more environments: one write goes to each.
- 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 leadingexportis 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.
environment_listreturns the environments and their identifiers.env_listreturns an environment's inventory, with the counters to compare against the caps.env_write_nonproductionorenv_write_productionwrites the variable.
{
"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.
{
"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
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
--valuestays in your shell history. For a secret, prefer the console, or remove the line from your history afterwards.
Through the API
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.
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
- Deploy
- Restore
- Agent safety
- Service accounts
- env.write.production, env.pull.nonproduction, secret.reveal (operation pages, in French)