Env Vars
Give a value to the processes of a workspace, a sandbox, a service or a pipeline job, as a plain variable or from a secret.
Last updated
On this page
Description and operation
General
A process reads its settings from environment variables. You give the variables of a workspace, a sandbox, a service or a pipeline job in its env field. A variable holds its value in plain text, or refers to a secret.
A secret is a named value that the platform keeps for an owner, such as a password, a token or a connection string. You set it once and refer to it by name. The platform puts the value in the variable when the process starts. The value never shows in a definition, a document or a log.
| Aspect | Variable | Secret |
|---|---|---|
| What it is | A NAME=value pair in the env of one resource. See Variables. | A named value of an owner, used by many resources. See Secrets. |
| Where the value is kept | In the definition of the resource, in plain text. | In the secret store of the owner. The resource keeps only the reference. |
| Who can read the value | Everybody who can read the resource, and its processes. | Only the processes of the resources that refer to it. |
| Shown by the API | For a service, in the environment document. | Never. A list shows only the names, who changed each one and when. |
| Set with | The env field of the resource. | kloudlite secrets set, the SDK or PUT /v1/secrets/{owner}/{name}. |
Personal secrets in a team
A personal secret in a team is a secret that you keep for yourself inside one team, for example your own access token. Only you can set it, show its name or delete it. See Set a personal secret in a team.
Only your own resources in that team read it: your team workspaces, benches and sandboxes. Resources that the team shares never read it, so your value never goes to another member.
| Resource | Reads your personal secrets in the team |
|---|---|
| Your workspace, bench or sandbox in the team | Yes |
| Workspace, bench or sandbox of another member | No |
| Service of an environment | No. It reads only the secrets of the team. |
| Pipeline job | No. It reads only the secrets of the owner of the pipeline. |
| Your personal workspace or sandbox | No. It reads only your own secrets. |
A reference in your resource of the team finds the value in this order.
| Order | Store | Result |
|---|---|---|
| 1 | Your secrets in the team | Your value is used, also when the team has the same name. |
| 2 | The secrets of the team | The value of the team is used. |
| 3 | Neither store has the name | The resource does not start. It stays in the state creating. |
Use a variable for a setting such as LOG_LEVEL. Use a secret for a password, a key or a token.
To do these tasks with an AI agent, see Agentic workflows – General.
Reference syntax
A reference is a variable value that is exactly this text, where NAME is the name of a secret.
${{ secrets.NAME }}| Rule | Description |
|---|---|
| Whole value | The reference must be the whole value. With text around it, for example postgres://${{ secrets.DB_PASS }}@db, it is plain text. A pipeline refuses it. |
| Blanks | The blank characters inside the braces are optional. ${{secrets.DB_URL}} is also a reference. |
| Secret name | A to Z, 0 to 9 and _. It does not start with a digit. |
| Variable name | The variable can have a different name from the secret, for example DATABASE_URL: ${{ secrets.DB_URL }}. |
| Check | The platform checks each reference when you save the resource. If no store that the resource reads has the name, the call fails with 422 and saves nothing. |
Owner and scope
A resource reads the secrets of its own owner. A personal resource reads only your secrets. A team resource reads the secrets of the team. Your own team workspace, bench or sandbox reads your personal secrets in the team first. A name is unique in one store, so you, your team and your personal secrets in the team can each have a secret DB_URL.
| Resource | Store of the secrets that it reads |
|---|---|
| Personal workspace or sandbox | Your secrets |
| Your team workspace, bench or sandbox | Your personal secrets in the team, then the secrets of the team |
| Service of an environment | The owner of the environment |
| Pipeline job | The owner of the pipeline |
Delivery
A reference is valid in these fields. The value reaches the process as an environment variable.
| Resource | Field | Task |
|---|---|---|
| Workspace | env of the workspace, at creation or later | Use a secret in a workspace |
| Service of an environment | env of the service | Use a secret in a service |
| Pipeline job | env of the job or of a step, and the secrets list of the job | Use a secret in a pipeline |
| Sandbox | env, set at creation | Use a secret in a sandbox |
The SSH sessions of a workspace start from the tool server, so they have the same variables as its processes.
When a new value takes effect
A resource reads a secret when its processes start. A replaced value does not change a process that runs, and the platform does not restart it. To apply the value, restart the resource, or set restartOnEnvChange to true to restart it each time.
| Resource | When a new value arrives |
|---|---|
| Service of an environment | At the next start. A running service shows restart_required until then. |
| Workspace | At the next start. A running workspace shows restart_required until then. A change of env follows the same rule. |
| Sandbox | Never. A sandbox keeps the value that it started with. |
| Pipeline job | When the job starts. |
Pipelines
| Rule | Description |
|---|---|
| Masking | The log shows *** for each secret value that the job holds. A value shorter than 4 bytes, or an encoded form of a value, is not masked. |
| Save | A save fails with 422 when a secret that the definition names does not exist for the owner. |
| Delete | A delete of a secret fails with 409 while the newest version of a pipeline, or a queued or running run, refers to it. |
| Where | A reference is valid only in the env of a job or a step, and in the secrets list of a job. In a run line it is an error. |
Quota and limits
Secrets do not count against the quota of the owner. See also Limits.
| Item | Limit |
|---|---|
| Secrets | 100 for each store: you, a team, and you in a team. |
| Secret value | 64 KiB. |
| Variables of a workspace or a sandbox | 64, each value up to 32 KiB. |
Tasks
Use a secret in a workspace
Reason for the task: Give a workspace the value of a secret as a variable.
Job set-up
- Access: you.
- Conditions: the secret exists for the owner of the workspace. See Set or replace a secret.
- References:
PUT /v1/workspaces/{id}/env,kloudlite workspaces set-env.
The call replaces all variables of the workspace. A variable that is not in the new list is removed.
Procedure
Send the complete list of variables, with the reference.
from kloudlite import Kloudlitekl = Kloudlite()kl.workspaces.set_env(workspace="acme/api", env={"DB_URL": "${{ secrets.DB_URL }}"})import { Kloudlite } from "./index.ts";const kl = new Kloudlite();await kl.workspaces.setEnv({ workspace: "acme/api", env: { DB_URL: "${{ secrets.DB_URL }}" } });kl := kloudlite.New(kloudlite.Options{})if _, err := kl.Workspaces.SetEnv("acme/api", map[string]string{"DB_URL": "${{ secrets.DB_URL }}"}); err != nil { log.Fatal(err)}require_relative "kloudlite"kl = Kloudlite::Client.newkl.workspaces.set_env(workspace: "acme/api", env: { "DB_URL" => "${{ secrets.DB_URL }}" })var kl = new Kloudlite();kl.workspaces().setEnv("acme/api", Map.of("DB_URL", "${{ secrets.DB_URL }}"));kloudlite workspaces set-env acme/api --env 'DB_URL=${{ secrets.DB_URL }}'Stop the workspace, then start it. See Stop a workspace and Start a workspace.
Close-out
- Run
test -n "$DB_URL" && echo setin the workspace. Make sure that the output isset.
Use a secret in a service
Reason for the task: Give a service of an environment the value of a secret as a variable.
Job set-up
- Access: you. A team environment needs team membership.
- Conditions: the secret and the service exist. See Set or replace a secret and Add a service.
- References:
PATCH /v1/environments/{id},kloudlite environments set-services.
A running service keeps its old variables until you restart it.
Procedure
Send the service list with the reference in the
envof the service, hereapi.env = kl.environments.get(environment="acme-dev")services = [ {**sv, "env": {**sv["env"], "DB_URL": "${{ secrets.DB_URL }}"}} if sv["name"] == "api" else sv for sv in env["services"]]kl.environments.set_services(environment="acme-dev", services=services)import { type Service } from "./index.ts";const env = (await kl.environments.get({ environment: "acme-dev" })) as { services: Service[] };const services = env.services.map((sv) => sv.name === "api" ? { ...sv, env: { ...sv.env, DB_URL: "${{ secrets.DB_URL }}" } } : sv,);await kl.environments.setServices({ environment: "acme-dev", services });env, err := kl.Environments.Get("acme-dev")if err != nil { log.Fatal(err)}var services []kloudlite.Serviceraw, _ := json.Marshal(env["services"])if err := json.Unmarshal(raw, &services); err != nil { log.Fatal(err)}for i := range services { if services[i].Name == "api" { if services[i].Env == nil { services[i].Env = map[string]string{} } services[i].Env["DB_URL"] = "${{ secrets.DB_URL }}" }}if err := kl.Environments.SetServices(kloudlite.SetServices{Environment: "acme-dev", Services: services}); err != nil { log.Fatal(err)}env = kl.environments.get(environment: "acme-dev")services = env["services"].map do |sv| sv["name"] == "api" ? sv.merge("env" => sv["env"].merge("DB_URL" => "${{ secrets.DB_URL }}")) : svendkl.environments.set_services(environment: "acme-dev", services: services)// Replaces the list: describe every service, with the reference in api.var api = new Kloudlite.Service().name("api").image("ghcr.io/acme/api:1.0").port(8080) .env("DB_URL", "${{ secrets.DB_URL }}");kl.environments().setServices(new Kloudlite.SetServices().environment("acme-dev").services(api));# Replaces the list: send every service, with the reference in api.kloudlite environments set-services acme-dev --services '[ {"name": "api", "image": "ghcr.io/acme/api:1.0", "ports": [8080], "env": {"DB_URL": "${{ secrets.DB_URL }}"}}]'
Close-out
- Get the environment again. Make sure that
service_statusshowsapias ready.
Use a secret in a pipeline
Reason for the task: Give the steps of a pipeline job the value of a secret as a variable.
Job set-up
- Access: you. For a team, an admin.
- Conditions: the secret exists for the owner of the pipeline. See Set or replace a secret.
- References: Pipeline definition.
Procedure
Add the name of the secret to the
secretslist of the job. The variable has the same name.{ "jobs": { "deploy": { "secrets": ["KUBECONFIG_PROD"] } } }Give a secret to one step under another variable name. Put the reference alone in the value.
{ "steps": [{ "run": "./deploy.sh", "env": { "TOKEN": "${{ secrets.DEPLOY_TOKEN }}" } }] }Send the definition as the next version of the pipeline.
Close-out
- Start a run and read the log. Make sure that it shows
***for each secret value.
Use a secret in a sandbox
Reason for the task: Give the main process of a sandbox the value of a secret as a variable.
Job set-up
- Access: you. A team sandbox needs team membership.
- Conditions: the secret exists for the owner of the sandbox. See Set or replace a secret.
- References:
POST /v1/sandboxes,kloudlite sandboxes create.
This task creates a standalone sandbox. A sandbox in a session takes the same env. See Run a sandbox in a session.
Procedure
Create the sandbox with the reference in
env. Addteamfor a team sandbox.sb = kl.sandboxes.create(cmd=["sh", "-c", 'test -n "$DB_URL" && echo set'], env={"DB_URL": "${{ secrets.DB_URL }}"})const sb = await kl.sandboxes.create({ cmd: ["sh", "-c", 'test -n "$DB_URL" && echo set'], env: { DB_URL: "${{ secrets.DB_URL }}" } });sb, err := kl.Sandboxes.Create(kloudlite.CreateSandbox{Cmd: []string{"sh", "-c", `test -n "$DB_URL" && echo set`}, Env: map[string]string{"DB_URL": "${{ secrets.DB_URL }}"}})if err != nil { log.Fatal(err)}sb = kl.sandboxes.create(cmd: ["sh", "-c", 'test -n "$DB_URL" && echo set'], env: { "DB_URL" => "${{ secrets.DB_URL }}" })var sb = kl.sandboxes().create(new Kloudlite.CreateSandbox().cmd(List.of("sh", "-c", "test -n \"$DB_URL\" && echo set")).env(Map.of("DB_URL", "${{ secrets.DB_URL }}")));kloudlite sandboxes create --env 'DB_URL=${{ secrets.DB_URL }}' -- sh -c 'test -n "$DB_URL" && echo set'
Close-out
- Read the output of the sandbox as in Read the output and the exit code. Make sure that the output is
set.
Troubleshooting
| Symptom | Possible cause | Corrective action |
|---|---|---|
422 with `DB_URL` is not a secret of acme | The owner of the resource has no secret of that name. | Set the secret for the owner that the message names. |
422 with `DB_URL` is not a secret of acme or yours | Neither you in the team nor the team has a secret of that name. | Set your secret in the team, or set the secret of the team. |
| A service does not get your personal secret in the team | A service reads only the secrets of the team. | Set the secret of the team with --scope team. |
A workspace stays in the state creating after a start | A secret that its env refers to was deleted after the save. For your team workspace, the name is in neither your store in the team nor the store of the team. | Show the secrets and set the missing one. |
| A running workspace shows the old value | A workspace reads a secret only at start. | Stop the workspace and start it again. |
The variable holds the text ${{ secrets.NAME }} | The value has text around the reference, so it is plain text. | Put the reference alone in the value. |
A pipeline shows unknown identifier `secrets.NAME` | The reference is in a run line, or has text around it. | Put the reference alone in the value of an env entry. |
422 with secret NAME does not exist when you save a pipeline | The owner of the pipeline has no secret of that name. | Set the secret for that owner, then save again. |