Environment variables reference (deploymill)
Reference for managing per-app environment variables via set_env_vars, list_env_vars, and delete_env_vars. The platform stores env as a single per-application dotenv-style blob; these tools are the only supported way to mutate it.
The three tools
set_env_vars— add or update one or more keys. Merges into the existing env by default (preserving keys, comments, and${{project.X}}template references not named in the call). Passreplace: trueto overwrite the entire env blob.list_env_vars— returns the env var names only (and a count). Values are write-only and can never be read back through any tool — there is no reveal option. Use it to see what's set, not to retrieve values.delete_env_vars— remove one or more keys by name. Reports which keys were removed vs not found.
Merge semantics (the important bit)
set_env_vars is merge-by-default, not replace. That means:
- Keys you don't name in the call are left untouched, including:
- Comment lines (
# this is preserved). - Blank lines.
- Template references like
DATABASE_URL=${{project.DATABASE_URL}}(project-level shared env). - Anything
reconcile_projectset for you (notablyDATABASE_URLfrom a provisioned database). PREVIEW_URL(on preview apps — set bycreate_previewto the preview's full URL).
- Comment lines (
- Keys you DO name in the call overwrite the existing value (or append if new).
replace: trueflips this — the entire blob is overwritten with exactly what you passed. Almost always wrong unless you're intentionally rewriting the whole env (note you can't read existing values back to reconstruct them —list_env_varsis names-only).
If you need to remove a key, use delete_env_vars — don't try to do it by passing an empty string to set_env_vars (you'll just set the key to the empty string).
Changes require a redeploy
Env changes only take effect on the next deploy. The tools mutate the platform's stored env blob but the running container won't see the new values until it restarts. Call deploy after mutating env.
reconcile_project returns a note flagging when a redeploy is required for its changes (e.g. DATABASE_URL set during database provisioning). The same applies here — there's no auto-restart.
Template references
The platform supports ${{project.<KEY>}} as a reference to a project-level shared env var, e.g. DATABASE_URL=${{project.SHARED_DB_URL}}. deploymill doesn't currently expose project-level env via MCP tools, but if a project-level env has been set out-of-band, those references survive merges as long as you don't name the same key in set_env_vars.
Previews get their own env blob
Each preview app is a regular deploymill app with its own env. create_preview clones the parent's env at creation time and then applies the caller's envOverrides (with ${PREVIEW_URL} substitution for host-pinned vars). After creation, set_env_vars against the preview's applicationId modifies only that preview.
Note that DATABASE_URL is handled specially when the parent declares a managed Neon database: create_preview forks a Neon branch off the parent's default branch and rewrites the preview's DATABASE_URL to point at the fork, so destructive migrations on the preview stay off prod data. The preview only shares the parent's DATABASE_URL when branching is opted out (previews.shareDatabase: true in .deploymill/project.json) or unavailable (no NEON_API_KEY, or the parent's database isn't Neon) — create_preview's response reports which path it took via database.action and a matching warning. See deploymill://guides/previews for the full matrix.
Common operations
- Add a third-party API key / any secret: do NOT use
set_env_vars— secret values must not pass through the agent. Use the org secrets vault:request_secret({ name })to get a browser link the human fills in, thenbind_secretor thesecretsarray in.deploymill/project.json. Seedeploymill://guides/secrets.set_env_varsis for non-secret config only (ports, flags, public URLs). - Update an existing key: same call — merge semantics overwrite that single key in place.
- Remove a key:
delete_env_vars({ applicationId, keys: ["LEGACY_FLAG"] }), thendeploy. - Inspect what's set:
list_env_varsreturns the key names (not values — values are write-only). - Bulk replace (rare):
set_env_vars({ applicationId, env: {...}, replace: true }). Wipes everything not in the call, includingDATABASE_URL, comments, and template references. Reach for this only when intentionally rewriting the whole env.
What NOT to do
- Don't edit env out-of-band for keys deploymill manages.
DATABASE_URLprovisioned byreconcile_projectshould be left alone; if you need to rotate it, drop thedatabasefield and re-reconcile withprune: true, then re-add. - Never pass secrets through
set_env_vars. Its values flow through the agent and may land in a transcript. Anything sensitive goes through the secrets vault (request_secret→ browser entry →bind_secret), where the value never touches the agent.set_env_varsis for non-secret config only. - Don't use
replace: trueto "clean up" an env you haven't read first. You will silently nukeDATABASE_URLand any project-level template references. - Don't expect env changes to apply without a redeploy. They don't.
Troubleshooting
- New key not visible inside the app → you didn't
deployafterset_env_vars. The container is still running with the old env. DATABASE_URLdisappeared → someone calledset_env_varswithreplace: true, orreconcile_projectran withprune: trueafter thedatabasefield was removed from.deploymill/project.json. Re-adddatabase: { provider: "neon" }and re-reconcile to restore.- Comments /
${{project.X}}references gone after a merge → only happens withreplace: true. Merge mode preserves them.