dibbla secrets
Set, request, list and delete write-only secrets — for the whole org, one app or one service — pull the app's environment to a local .env.local, and promote a plain env var to a secret.
dibbla secrets manages the secret store on the Dibbla platform. Secrets come in three scopes, and the narrowest one that matches wins:
- Global — available to every deployment in the org. Omit
--deployment. - Per-deployment — only injected into a single app. Set
--deployment <alias>. - Per-service — only injected into one service of a multi-service app. Add
--service <name>alongside--deployment.
A secret is write-only. You can list, set, request and delete one, but no Dibbla API, CLI command or MCP tool returns its value: Dibbla hands it to the running app’s environment and to nothing else. A plain env var is the opposite — not sensitive, and readable by anyone who can read the app’s configuration, assistants included. Env vars and secrets is the model; this page is the commands.
A value reaches the secret store in one of four ways, and none of them passes through an AI assistant:
secrets set— you, in your own terminal, pasting the value at a hidden prompt or piping it in.secrets request— a link to a signed-in Dibbla page where the value is typed. This is how an assistant gets a secret set without ever seeing it.env promoteuses the same page to turn a plain env var into a secret.secrets import— a whole.env-style file you wrote, without a redeploy.- The console’s Secrets page.
dibbla secrets list
| Flag | Purpose |
|---|---|
-d, --deployment <alias> | List secrets attached to one deployment. Omit for global secrets. |
-s, --service <name> | Narrow to one service. Requires -d. |
$dibbla secrets list 🌱 Retrieving secrets... Found 2 secret(s) (global): NAME DEPLOYMENT SERVICE UPDATED ---- ----------- ------- ------ DATABASE_URL_MAIN (global) (all) 2026-04-14 10:23:01 STRIPE_KEY (global) (all) 2026-04-28 12:14:55
$dibbla secrets list --deployment my-api 🌱 Retrieving secrets... Found 2 secret(s) (deployment 'my-api'): NAME DEPLOYMENT SERVICE UPDATED ---- ----------- ------- ------ DATABASE_URL_TODO my-api (all) 2026-04-28 13:01:12 WEBHOOK_SIGNING_SECRET my-api web 2026-04-25 09:42:18
dibbla secrets set
Create or update a secret. Leave the value out of the command: at a terminal the CLI asks for it without echoing it — paste it, then press Enter on an empty line. When stdin is not a terminal it reads stdin to its end, so a pipe or a file works too, including a multi-line value such as a PEM key.
| Flag | Purpose |
|---|---|
<name> (positional, required) | Secret name. Conventionally SCREAMING_SNAKE_CASE. |
[value] (positional) | Leave it out. Given here it still works, with a warning: the value is now in your shell history — and, if an AI assistant ran the command, in its transcript. |
-d, --deployment <alias> | Attach the secret to one app. Omit for global. Works for an alias that has not been deployed yet, so an app’s secrets can be in place before its first deploy. |
-s, --service <name> | Scope to one service of a multi-service app. Requires -d. |
$dibbla secrets set STRIPE_KEY -d my-api Value for STRIPE_KEY (input hidden): paste it, then press Enter on an empty line: 🌱 Setting secret 'STRIPE_KEY'... ✅ Secret created successfully Secret: STRIPE_KEY Deployment: my-api
# a multi-line value, read from stdin $dibbla secrets set TLS_KEY -d my-api < tls.key
$dibbla secrets set NPM_TOKEN -d my-api --service web A saved value reaches the running app on its next deploy or restart — see Rotate secrets and env vars.
An assistant never runs secrets set with your value: the value would sit in
its transcript. It runs secrets request and hands
you a link instead. If an assistant asks you to paste a key into the chat, a
file, an environment variable or a command, say no and ask it to request the
secret.
dibbla secrets request
Ask for a secret’s value to be typed into a Dibbla page in the browser instead of
the terminal. The command prints a link; you open it signed in, paste the value
and save, and it goes straight into the secret store. The value never passes
through the terminal, its history or an AI assistant’s transcript — the command
never sees it. It is what an assistant runs when a task needs a secret, and like
set it works before the app’s first deploy.
| Flag | Purpose |
|---|---|
<name> (positional) | Secret name. Required unless --status is given. |
-d, --deployment <alias> | The app. Omit for an org-global secret. |
-s, --service <name> | Scope to one service. Requires -d. |
--title <text> | The page’s heading: what the secret is. Plain text, at most 80 bytes. Without it the page shows the name. |
--why <text> | The page’s paragraph: why the app needs it and where to find it. Plain text, at most 600 bytes. |
--wait | Check every 3 seconds until the value is entered, the request is cancelled on the page, or it expires. |
--status <id> | Read a request made earlier instead of making one: its state and, while it is pending, the link. Combine with --wait. |
--json | The server’s document — request_id, state, entry_url, expires_at, replaces_existing, … — never a value. |
$dibbla secrets request STRIPE_KEY -d my-api --title "Stripe secret key" \ --why "The shop charges cards with it. Stripe dashboard → Developers → API keys." ✅ Secret entry request for STRIPE_KEY (deployment my-api). Open this link and enter the value; it never passes through this terminal: https://console.dibbla.com/connector/secrets/sreq_EXAMPLE Request: sreq_EXAMPLE Expires: 2026-10-07 12:15 UTC (in 15 min) Check: dibbla secrets request --status sreq_EXAMPLE (add --wait to wait for it)
$dibbla secrets request --status sreq_EXAMPLE --wait Request sreq_EXAMPLE — STRIPE_KEY (deployment my-api): pending Link: https://console.dibbla.com/connector/secrets/sreq_EXAMPLE Expires: 2026-10-07 12:15 UTC (in 15 min) ⏳ Waiting for the value to be entered on the page (Ctrl-C stops waiting; the request stays open until it expires)... ✅ The value of STRIPE_KEY (deployment my-api) was entered and saved.
What holds the request together:
- Only you can open the link — the person who made the request, signed in to the console in the same organization, with a role that may write secrets (owner, admin or developer). Anyone else sees “not found”. A viewer cannot make a request.
- It works once, for 15 minutes. The page shows the organization, app, service and secret name it will write, and whether it replaces a current value; nothing on it can point the write elsewhere. An expired or cancelled request is replaced by running the command again.
- Saving restarts nothing. The app picks the value up on its next deploy or restart.
An assistant runs it in two steps, because a shell call shows its output only
when the command ends: first the request without --wait, so you get the link
at once, then dibbla secrets request --status <id> --wait, which returns when
you have saved. The platform connector
does the same through platform_secret_write, onto the same page.
| Exit | Meaning |
|---|---|
0 | The request was made — or, with --wait, the value was entered and saved. |
1 | The request was cancelled on the page. Nothing was saved. |
3 | Not allowed — a viewer, or a role that may not write secrets. |
4 | --status: no such request for you in this organization. A request is answered only to the person who made it. |
5 | An invalid name, --title or --why, or flags that do not go together. Nothing was sent. |
7 | The request expired before a value was entered. Nothing was saved. |
dibbla secrets import
Bulk-load every KEY=value from a .env-style file into the secret store in one shot — without a redeploy. This is the alternative to a while read loop around secrets set, and it keeps values out of your shell history. It is also the answer when a deploy refuses --env-file because the file holds keys: an env file makes plain env vars, and keys belong here (see env vars that look like secrets).
The file is one you wrote, holding values you have. An AI assistant may run the import for you, but it never opens the file.
| Flag | Purpose |
|---|---|
<file> (positional, required) | A .env-style file. See .env file format below. |
-e, --env KEY=value | Override a single key on top of the file. Repeatable. |
-d, --deployment <alias> | Import into one deployment. Omit for global secrets. |
-s, --service <name> | Scope to a single service. Requires -d. |
--dry-run | List the keys that would be set. No values, no network calls. |
The file is the base layer and -e KEY=value flags override individual keys. If a key appears in more than one -e flag, the last one wins. This is the same env precedence dibbla run --env-file uses.
$dibbla secrets import ../secrets/.env.prod --deployment my-api 🌱 Importing 3 secret(s) into deployment my-api... ✅ imported 3 secret(s) into deployment my-api: API_KEY, DATABASE_URL, STRIPE_KEY
# File is the base layer; -e wins for that one key $dibbla secrets import ../secrets/.env.prod \ --deployment my-api \ -e STRIPE_MODE=test
$dibbla secrets import .env --deployment my-api --dry-run 🌱 Dry run — would import 3 secret(s) into deployment my-api: API_KEY DATABASE_URL STRIPE_KEY No secrets were written (--dry-run).
$dibbla secrets import .env --deployment my-api ❌ Invalid secret name(s): 1BAD, my.key Names must match ^[a-zA-Z][a-zA-Z0-9_]{0,127}$ Nothing was imported.
Every key is validated before anything is sent. Names must match ^[a-zA-Z][a-zA-Z0-9_]{0,127}$ — start with a letter, then letters, digits, and underscores. If any key in the file is invalid, the command names the offending keys, exits non-zero, and imports nothing; you never end up with half a file applied.
Values are never printed. Output is key names and a count, on both the success and the failure path.
Re-running is safe. The server upserts each secret, so an import is idempotent. If a write fails partway, the command stops, tells you which key failed and how many succeeded, and you can simply re-run it.
secrets import reads the file client-side and sends the values over the
authenticated API — the file itself is never uploaded. But a .env sitting in
the directory you deploy is a separate problem: it is a pre-deploy guardrail
blocker and is stripped from Dibbla-managed VCS. Note that being stripped
from VCS is not the same as being absent from the Docker build — those are
two different filters, and
.dibblaignore only governs the first. Keep the file somewhere else
(dibbla secrets import ../secrets/.env.prod), and use .dockerignore if it
must stay put.
.env file format
The same parser backs secrets import, dibbla deploy --env-file, dibbla apps update --env-file, and dibbla run --env-file.
| Line | Result |
|---|---|
KEY=value | The basic form. |
export KEY=value | Accepted — export is ignored. |
# comment | Whole-line comments are skipped, as are blank lines. |
KEY=value # note | Trailing comments are stripped. |
KEY= | An empty string, not “unset”. |
KEY=a=b=c | Split on the first =, so the value is a=b=c. |
KEY="has spaces" | Quotes are stripped. Double-quoted values may span lines. |
KEY="pre $OTHER post" | $VAR and ${VAR} are expanded — in double-quoted and unquoted values alike. |
KEY='literal ${OTHER}' | Single quotes are literal — nothing is expanded. |
Expansion applies to uppercase names only, and it does not care about double quotes:
PASSWORD=p$ASSW0RDandPASSWORD="p$ASSW0RD"both import as justp—$ASSW0RDis an undefined variable and expands to nothing.PASSWORD="p$assw0rd"survives intact, because$assw0rdis lowercase.
That asymmetry is the trap: a value works, one character becomes a capital,
and it silently truncates. Single-quote anything containing a $ and stop
thinking about it — PASSWORD='p$ASSW0RD'.
A - in a name (MY-KEY=x) is a parse error and fails the whole file.
dibbla secrets get
Removed: a secret’s value cannot be read back. The command still exists only
to say so — it prints why and exits 1 — and the server answers
410 SECRET_VALUE_NOT_READABLE to every client, older CLIs included, before it
even looks the name up.
$dibbla secrets get STRIPE_KEY ❌ secrets get: a secret's value cannot be read back — secrets are write-only on Dibbla. The app gets its secrets in its environment when it runs. See which exist: dibbla secrets list [-d <alias>] Change one: dibbla secrets set <name> [-d <alias>]
What to do instead:
- See which secrets exist —
dibbla secrets list [-d <alias>]. - Change one —
dibbla secrets set <name> -d <alias>in your own terminal, ordibbla secrets request <name> -d <alias>for a link. - Reach the app’s database from your machine —
dibbla db connectgives you a connection of your own, signed in as you. - Run the app locally —
dibbla env pullgives you the names; you fill in development values. - A script that read a value with
secrets getneeds its own copy, from the provider that issued it.
dibbla secrets delete
Remove a secret. Apps that reference it will lose the env var on their next start.
| Flag | Purpose |
|---|---|
<name> (positional, required) | Secret name. |
-d, --deployment <alias> | Delete a deployment-scoped secret. |
-s, --service <name> | Delete a per-service secret. Requires -d. |
-y, --yes | Skip the confirmation prompt. |
$dibbla secrets delete STRIPE_KEY --yes 🗑️ Attempting to delete secret 'STRIPE_KEY' (global)... ✅ Secret 'STRIPE_KEY' deleted
dibbla env pull
Write the app’s environment to .env.local for running it on your machine:
every plain variable with its value, every secret by name only, as an
empty NAME= line for a development value of your own.
What it resolves is exactly what the running container gets, in the same order:
global secrets, then the app’s, then the service’s (with --service), then the
inline env vars (deploy -e, dibbla.yaml environment:), then the
DIBBLA_* variables the platform injects. A name that is a secret in any of
those layers comes by name only. DATABASE_URL_* and STORAGE_* are secrets the
platform created, so they too arrive empty.
Values live in Dibbla, names live in the code: keep the names in
.env.example (committed) and run this again after adding one. The file lives
only on this machine; .gitignore gets a .env.local line if it lacks one, and
the platform’s filter and the push hook refuse the file on top of that. The
guide is Secrets and local development.
| Flag | Purpose |
|---|---|
-d, --deployment <alias> | The app. Default: the app this folder is linked to (dibbla clone / dibbla link). |
-s, --service <name> | Resolve one service’s view of a multi-service app. |
--replace | Rewrite .env.local from scratch instead of updating it in place. A secret line you filled in survives even this. |
--stdout | Print the variables as KEY=value lines, and each secret as a comment # NAME is a secret: Dibbla never hands out its value, instead of writing a file — eval "$(dibbla env pull --stdout)". |
--json | Print the API document: variables[] with name, value, source (inline, platform) and looks_like_secret, and a separate secrets[] with name and source (global, deployment, service) — never a value. |
$dibbla env pull ✅ .env.local: 4 variable(s) from Dibbla (app my-api) — 1 inline, 3 platform 3 secret(s) by name only — Dibbla never hands out a secret's value; fill in development values (0 already set here) added .env.local to .gitignore so git never sees it This file lives only on this machine.
$dibbla env pull ✅ .env.local: 5 variable(s) from Dibbla (app my-api) — 2 inline, 3 platform 3 secret(s) by name only — Dibbla never hands out a secret's value; fill in development values (0 already set here) MAIL_API_KEY look like secrets but are plain env vars, readable by anyone who can read the app's configuration. Promote each to a secret with a new value: 'dibbla env promote NAME -d my-api'. This file lives only on this machine.
The secrets end up in a block of their own at the bottom of the file:
# Secrets: Dibbla never hands out a secret's value. Set a development value for each one here.
# DATABASE_URL_MAIN: for a connection of your own, use "$(dibbla db connect main -q)" in the start command — it carries your API token, so not in this file
DATABASE_URL_MAIN=
SMTP_PASSWORD=
STRIPE_KEY=
Fill each one in with a development value — a test key, a sandbox account, a
local database. A secret line that already has a value is yours and no pull
overwrites it, not even --replace; the command names such lines. If your
.env.local came from a pull made before secrets became write-only, those lines
hold the app’s real secrets: replace them with development values.
An existing .env.local is otherwise updated in place: variables Dibbla knows
are refreshed where they stand, every other line survives, new ones are
appended. The first line of the file says where it came from and how to refresh
it. Output is names and counts, never values. Reading the environment takes the
deploy roles — owner, admin or developer; a viewer is refused.
dibbla env promote
Turn a plain env var into a secret. A plain variable is readable by anyone who
can read the app’s configuration — AI assistants included — so its current
value counts as exposed. env promote therefore asks for a new value: you
rotate the key where it was issued (a new API key at the provider), open the link
the command prints, and enter the new one there. The page refuses the unchanged
value. When you save, Dibbla stores the secret, removes the env var from every
workload of the app and restarts the app.
A promotion is for the whole app: an env var a deploy set is on every service,
so the secret becomes the app’s and the variable leaves every service. There is
no promotion for one service — Dibbla refuses --service.
dibbla env pull names the plain variables whose name or value looks like a
secret; those are the candidates. In the console, the environment editor (the
app card’s ⋮ → Edit deployment) does the same with Make secret next to
such a variable.
| Flag | Purpose |
|---|---|
<name> (positional, required) | The env var to promote. |
-d, --deployment <alias> | The app. Default: the app this folder is linked to. |
--title <text> | The page’s heading, as on secrets request. |
--why <text> | The page’s paragraph. Without it the page says the old value is exposed and asks for a new one. |
--wait, --json | As on secrets request. A promotion’s state is read later with dibbla secrets request --status <id>. |
$dibbla env promote MAIL_API_KEY -d my-api --title "Mail provider API key" \ --why "Create a new key in the mail provider's dashboard first: the old one was visible as a setting." ✅ Promotion requested: MAIL_API_KEY (deployment my-api) becomes a secret. Its value has been a plain env var, readable by anyone who can read the app's configuration: treat it as exposed. Rotate it where it was issued, then open this link and enter the NEW value; it never passes through this terminal: https://console.dibbla.com/connector/secrets/sreq_EXAMPLE Request: sreq_EXAMPLE Expires: 2026-10-07 12:15 UTC (in 15 min) Then: the env var MAIL_API_KEY is removed and the app restarts with the secret. The page refuses the unchanged value. Check: dibbla secrets request --status sreq_EXAMPLE (add --wait to wait for it)
| Refusal | Exit | Meaning |
|---|---|---|
ENV_VAR_NOT_FOUND | 4 | No plain env var of that name on the app. Names are case-sensitive. If the name is already a secret, change it with secrets set or secrets request; to set a new secret, use secrets request. |
ENV_FROM_MANIFEST | 6 | The variable is in dibbla.yaml environment:, where the next deploy would put it back. Remove the line, run dibbla secrets request <name> -d <alias> for a new value, then deploy. |
SECRET_EXISTS | 6 | A service of the app has a secret of that name of its own, which would win over the app-wide one there. Delete that service’s secret (dibbla secrets delete <name> -d <alias> --service <service>), then promote again. |
VALIDATION_FAILED | 5 | --service was given, or the name is a DIBBLA_* variable. |
On the page, when you save:
- The unchanged value is refused (
VALUE_NOT_ROTATED). Nothing is saved, and the request stays open for the new value. - Not while a deploy runs. A save during a deploy of the app is refused
with
DEPLOY_IN_PROGRESS. Nothing is written and the request stays pending: save again once the deploy has finished, while the link lasts. - If the env var could not be removed, the secret is still stored and the
request completes. The page says what is left to do — remove the env var by
hand, or, when the secret had not reached the app yet, keep it and promote
again later — and the request’s
--jsondocument carriesenv_var_remains: true.
An AI assistant may run env promote: it never sees the value. It hands you the
link, tells you the value has to be a new one, and waits.
Env vars that look like secrets
Every write of a plain env var is checked: deploy -e and --env-file,
apps update -e and --env-file, a dibbla.yaml environment:, the
console’s environment editor and the platform connector. Only the variables the
write names are checked — the ones the running app already has are carried
forward as they are. Two refusals can come back, both 422 (exit 5), and both
name the variable and the reason, never the value:
| Code | When | Who may go on anyway |
|---|---|---|
ENV_SHADOWS_SECRET | The name is one of the app’s secrets — global, the app’s, or one of its services’. An env var of that name would replace the secret’s value at runtime. | Nobody. Drop the env var: the app already gets the secret. To change the secret, secrets set or secrets request. |
ENV_LOOKS_LIKE_SECRET | The name (…_SECRET, …_TOKEN, …_PASSWORD, …_API_KEY, …_KEY, …) or the value (a private key, a well-known provider key, a JWT, a database URL with a password in it) looks like a secret. | A person who knows it is not one: --allow-secret-env on deploy or apps update, or Save anyway in the console. Not an AI assistant: the platform connector and the maintenance agent are refused whatever they send, and the Dibbla skill tells a coding assistant never to pass the flag — it asks you for the value with secrets request instead. |
The name check errs on the side of flagging — PUBLIC_KEY is caught too — which
is why a person can wave it through. --allow-secret-env never covers a name
that is one of the app’s secrets.
- A file of keys is not an env file.
--env-filemakes plain env vars, so a file of keys is refused as above. Load it into the secret store withsecrets importand deploy without it. - In
dibbla.yaml. The file is in the app’s source and its history, so for a person a secret-lookingenvironment:entry is a warning, not a refusal —dibbla previewlists it before you deploy, and the deploy names it when it finishes, a deploy from a linked folder (git push) included. An entry named like one of the app’s secrets is refused for everyone. An AI assistant’s deploy is refused for either: one it starts over the platform connector, and a proposal it wrote, whoever approves it. To move an entry into a secret, delete the line, request a new value withdibbla secrets request <name> -d <alias>— the old one has been readable — and deploy again;env promotedoes not take adibbla.yamlentry.
See also
- Env vars and secrets — what is which, and why a secret is write-only.
- Secrets and local development — the model behind
env pull,.env.example, and what happens to a committed.env.local. - Giving the assistant a secret without pasting it — the same entry page, from the platform connector.
dibbla db—db createautomatically creates aDATABASE_URLsecret.dibbla deploy— deployments inherit global and matching scoped secrets at runtime, anddeploy --env-fileseeds plain env vars from the same kind of file.dibbla run— the same file-then-flags precedence, for pipeline steps.