Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 63 additions & 14 deletions apps/docs/content/features/env-variables.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -140,50 +140,73 @@ A security feature that controls the **visibility** of environment variables acr

By default, Zerops isolates environment variables between services to enhance security and prevent unintended access to sensitive information. This isolation can be configured at both project and service levels.

### Isolation Modes
### Isolation Rules

Zerops supports two isolation modes:
The `envIsolation` value is a space-separated list of rules. **A service's own rules decide which other services can see its variables.** The rules of the service doing the reading are never consulted, so a service can widen or narrow what it exposes, but never what it receives.

<table className="w-full my-1.5">
<thead>
<tr>
<th className="w-fit">Mode</th>
<th className="w-fit">Rule</th>
<th className="w-fit">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td className="w-fit whitespace-nowrap"><code>service</code></td>
<td className="w-full"><strong>Default mode.</strong> Variables are isolated to their respective services. Services can only access their own variables and must explicitly reference variables from other services.</td>
<td className="w-full"><strong>Default.</strong> Only the service itself sees its variables. Other services must reference them explicitly.</td>
</tr>
<tr>
<td className="w-fit whitespace-nowrap"><code>none</code></td>
<td className="w-full"><i>Legacy mode.</i> All variables from all services are automatically shared and accessible via prefixing.</td>
<td className="w-full">Every service in the project sees the variables, prefixed with the service hostname.</td>
</tr>
<tr>
<td className="w-fit whitespace-nowrap"><code>service@name</code></td>
<td className="w-full">The service with hostname <code>name</code> additionally sees the variables.</td>
</tr>
<tr>
<td className="w-fit whitespace-nowrap"><code>-service@name</code></td>
<td className="w-full">The service with hostname <code>name</code> never sees the variables, even when <code>none</code> is present.</td>
</tr>
</tbody>
</table>

#### Rule Evaluation Order

When service `reader` asks for the variables of service `owner`, the rules of `owner` are checked in this order:

1. `-service@reader` is present → **hidden**
2. `service@reader` is present → **visible**
3. `none` is present → **visible**
4. Otherwise → **hidden**

Block rules always win over allow rules for the same service. A service always sees its own variables regardless of its rules, and explicit `${hostname_variable}` references keep working in every mode.

### Configuring Isolation

#### Project-Level Isolation

Zerops automatically creates the `envIsolation` project variable with the default value `service`. You only need to modify this if you want to disable isolation:
Zerops automatically creates the `envIsolation` project variable with the default value `service`. Every service inherits the project value unless it sets its own:

```yaml title="import.yaml"
project:
envIsolation: none # Disables isolation, sharing all variables
envIsolation: service@zcp # The zcp service sees the variables of every other service
```

This can also be set through the Project Environment Variables section in the GUI.

#### Service-Level Override

Individual services can override the project-level isolation setting:
Individual services can replace the project-level value with their own. The values are not merged, the service value applies on its own:

```yaml title="import.yaml"
project:
envIsolation: service@zcp
services:
- hostname: db
envIsolation: none # This service's variables will be visible to all services
envIsolation: none # Everyone sees the db variables
- hostname: api
envIsolation: service # Nobody sees the api variables, not even zcp
```

:::tip
Expand All @@ -194,6 +217,31 @@ You might set a database service to `envIsolation: none` to expose its connectio
In import YAML, `envIsolation` can also be nested under `envVariables`/`envSecrets`. (If both are present, the nested version takes precedence).
:::

#### Common Combinations

**Single privileged reader** *(tooling, migrations, admin service)*:
```yaml
envIsolation: service@zcp
```
- ✅ The `zcp` service sees the variables of every service that inherits this value
- ❌ Every other service stays isolated

**Open project with one exception**:
```yaml
envIsolation: none -service@untrusted
```
- ✅ All services see each other's variables
- ❌ The `untrusted` service sees only its own variables and project variables

**Selective exposure on one service**:
```yaml
services:
- hostname: db
envIsolation: service@api service@worker
```
- ✅ The `api` and `worker` services see the `db` variables
- ❌ Every other service must reference `db` variables explicitly

### Accessing Variables Across Services

#### With Isolation Enabled (`service` mode)
Expand All @@ -210,12 +258,12 @@ run:

This approach gives you complete control over which variables are shared between services.

#### With Isolation Disabled (`none` mode)
#### When Variables Are Shared (`none` or `service@name`)

When isolation is disabled, variables are automatically available across all services with the service name prefix:
When the owning service shares its variables, they are automatically available with the service name prefix:

```yaml
# In any service, you can directly access:
# In any service the 'db' service shares variables with, you can directly access them:
${db_password} # Accesses the 'password' variable from the 'db' service
```

Expand All @@ -224,7 +272,8 @@ ${db_password} # Accesses the 'password' variable from the 'db' service
1. **Use Default Isolation**: Keep the default `service` isolation for enhanced security.
2. **Explicit References**: Create explicit references only for variables that need to be shared.
3. **Naming Conventions**: Use clear naming patterns for reference variables (e.g. `DB_PASSWORD` for a reference to `db_password`).
4. **Service-Level Exceptions**: Use service-level isolation overrides sparingly and only for services that need to expose their variables widely.
4. **Grant, Don't Open**: Prefer `service@name` for the one service that needs broad access over `none`, which exposes variables to every service.
5. **Service-Level Exceptions**: Use service-level isolation overrides sparingly and only for services that need to expose their variables widely.

## Referencing Variables

Expand Down Expand Up @@ -350,6 +399,6 @@ With this setup:
- The `db` service cannot see any variables from `api` or `cache`
- The `cache` service cannot see any variables from `api` or `db`

If we changed the project's `envIsolation` to `none`, all services would be able to see all variables from all other services (prefixed with the service name).
If we changed the project's `envIsolation` to `none`, all services would be able to see all variables from all other services (prefixed with the service name). With `service@api` instead, only the `api` service would see the `db` and `cache` variables, and `db` and `cache` would stay isolated from each other.

*Need help? Join our [Discord community](https://discord.gg/zeropsio).*
24 changes: 16 additions & 8 deletions apps/docs/content/guides/environment-variables.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: "Environment Variables"
description: "Zerops manages environment variables at two scopes (project and service) with strict build/runtime isolation. Variables are set via zerops.yml, import.yml, or GUI. Cross-service references use `${hostname_varname}` syntax. Project vars auto-inherit into all services. Secret vars are write-only after creation. Changes require service restart."
---

Zerops manages environment variables at two scopes (project and service) with strict build/runtime isolation. Variables are set via zerops.yml, import.yml, or GUI. **Project vars auto-inherit into every service** — read them directly, no declaration. **Cross-service (sibling) vars do NOT auto-inject under the default `envIsolation=service`** — reference a sibling's value explicitly as `${hostname_varname}` in `run.envVariables` (only legacy `none` mode injects siblings as bare vars). Secret reads are privilege-gated (an admin token returns the value; a read-only token gets `REDACTED`). A running process keeps its boot-time env — restart it (not reload) to pick up a changed value.
Zerops manages environment variables at two scopes (project and service) with strict build/runtime isolation. Variables are set via zerops.yml, import.yml, or GUI. **Project vars auto-inherit into every service** — read them directly, no declaration. **Cross-service (sibling) vars do NOT auto-inject under the default `envIsolation=service`** — reference a sibling's value explicitly as `${hostname_varname}` in `run.envVariables` (siblings are injected as bare `<host>_KEY` vars only when the owning service shares them via `none` or `service@<reader-hostname>`). Secret reads are privilege-gated (an admin token returns the value; a read-only token gets `REDACTED`). A running process keeps its boot-time env — restart it (not reload) to pick up a changed value.

---

Expand Down Expand Up @@ -63,7 +63,7 @@ run:
- An **unresolved ref stays literal** (`${db_hostname}` reaches the process verbatim) — no error, no blank. A wrong hostname/var on the right-hand side becomes a literal string and the app fails at connect time.
- **Hostname charset**: service hostnames are lowercase alphanumeric only (`[a-z0-9]`) — the platform rejects dashes, underscores, and uppercase with `serviceStackNameInvalid`. So a ref is simply `${hostname_varname}` with the literal hostname (service `cache` → `${cache_port}`); there is no dash-to-underscore rewrite to reason about, because a dashed hostname cannot exist.

Only legacy `envIsolation=none` auto-injects every sibling's vars as bare `<host>_KEY` OS env vars without a ref — see Isolation Modes. New projects are `service`; rely on explicit refs.
Sibling vars are auto-injected as bare `<host>_KEY` OS env vars only when the **owning** service shares them, via `none` or a `service@<reader-hostname>` grant — see Isolation Modes. New projects are `service`; rely on explicit refs.

### Cross-Service References in API vs Runtime

Expand All @@ -75,23 +75,31 @@ Cross-service references (`${hostname_varname}`) are **resolved at container sta

### Isolation Modes (envIsolation)

`envIsolation` is a project-scope setting that controls whether sibling-service vars are auto-injected.
`envIsolation` is a space-separated rule list set at project scope and optionally overridden per service (the service value replaces the project value, no merge). **It is owner-side and directional**: a service's own rules decide who sees ITS vars. The reader's rules never widen what the reader receives, so a compromised container cannot grant itself access to sibling secrets.

| Mode | Behavior |
| Rule | Behavior |
|------|----------|
| `service` (default) | **Siblings are isolated.** A service sees only its own vars + project vars + the explicit `${hostname_varname}` refs it declares in `run.envVariables`. Managed-service connection vars also require an explicit ref. |
| `none` (legacy) | Every service's vars are auto-injected into every other container as bare `<host>_KEY` OS env vars (source-side, directional). Ambiguous and broad — avoid for new projects. |
| `none` | Every service sees this service's vars as bare `<host>_KEY` OS env vars. Broad — prefer `service@name` for new projects. |
| `service@name` | The service with hostname `name` additionally sees this service's vars as bare `<host>_KEY` vars. |
| `-service@name` | The service with hostname `name` never sees this service's vars, even when `none` is present. |

Evaluation for reader R against owner O's rules: `-service@R` → hidden, else `service@R` → visible, else `none` → visible, else hidden. A service always sees its own vars.

Set in import.yml at project or service level:
```yaml
project:
envIsolation: none # legacy — avoid; default is service
envIsolation: service@zcp # zcp sees every service that inherits this value
services:
- hostname: db
envIsolation: none # per-service: expose THIS service's vars to siblings
envIsolation: none # per-service: expose THIS service's vars to all siblings
- hostname: api
envIsolation: service # per-service: opt out of the project grant, nobody sees api vars
- hostname: cache
envIsolation: none -service@api # everyone except api sees cache vars
```

**Default (`service`) is the right choice.** Wire cross-service explicitly with `${hostname_varname}` — it works in both modes, so code stays correct if isolation ever changes.
**Default (`service`) is the right choice.** Wire cross-service explicitly with `${hostname_varname}` — it works in every mode, so code stays correct if isolation ever changes. When one tooling or admin service needs broad read access, grant it with `service@<hostname>` at project level instead of switching to `none`.

## Project Variables -- Auto-Inherited

Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/guides/networking.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ http://postgres:5432
- Service discovery is automatic — no manual network config
- VPN uses same hostnames: `http://api:3000` from local machine (both `api` and `api.zerops` resolve — VPN sets up DNS search domain)

**Cross-service env vars**: under the default `envIsolation=service`, reference a sibling's var explicitly as `${hostname_varname}` in `run.envVariables` (e.g. `${app_API_TOKEN}`) — siblings are NOT auto-injected. The bare `<host>_KEY` injected form only appears under legacy `envIsolation=none`. Zerops auto-generates connection vars for managed services — reference them the same way (`${db_*}`).
**Cross-service env vars**: under the default `envIsolation=service`, reference a sibling's var explicitly as `${hostname_varname}` in `run.envVariables` (e.g. `${app_API_TOKEN}`) — siblings are NOT auto-injected. The bare `<host>_KEY` injected form only appears when the owning service shares its vars, via `envIsolation=none` or a `service@<reader-hostname>` grant. Zerops auto-generates connection vars for managed services — reference them the same way (`${db_*}`).

**DO NOT** use `https://` for service-to-service calls — SSL terminates at the L7 balancer, internal network is already isolated.

Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/mariadb/how-to/backup.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Use the `zerops-import.yaml` file from the repository to import the service. See
Connect to the `mariadbrestore` service using the GUI terminal or via [VPN](/references/networking/vpn) and [SSH](/references/networking/ssh).

:::note
To use environment variables from your MariaDB service in the backup and restore commands, make sure the `envIsolation` project variable is set to `none`. See [Environment Variable Isolation](/features/env-variables#environment-variable-isolation) and [Referencing Variables](/features/env-variables#referencing-variables) for details.
To use environment variables from your MariaDB service in the backup and restore commands, make sure the MariaDB service shares its variables with the `mariadbrestore` service, for example with `envIsolation: service@mariadbrestore` on the MariaDB service or on the project, or with `envIsolation: none`. See [Environment Variable Isolation](/features/env-variables#environment-variable-isolation) and [Referencing Variables](/features/env-variables#referencing-variables) for details.
:::

Run the backup script:
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/references/zsc.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ When using an object storage service, the command requires the following environ
* `objectstorage_secretAccessKey` - Secret access key for authentication
* `objectstorage_bucketName` - Name of the bucket to use

These environment variables will be automatically available if the object storage service has `envIsolation: none` configured, or if the entire project has `envIsolation: none` set. Otherwise, you need to explicitly reference these environment variables in your `zerops.yaml` file.
These environment variables will be automatically available if the object storage service shares them with your service, either with `envIsolation: none` or with `envIsolation: service@<your-hostname>` (set on the object storage service or inherited from the project). Otherwise, you need to explicitly reference these environment variables in your `zerops.yaml` file.

#### Sub-commands

Expand Down
Loading
Loading