From 4af255ebc9f1b77c17763b8d64f41dcc107b8428 Mon Sep 17 00:00:00 2001 From: Jarek Krochmalski Date: Sun, 23 Aug 2026 10:21:00 +0200 Subject: [PATCH] clarify stack env-var api docs (git vs internal storage, secret masking) --- src/routes/api/stacks/[name]/env/+server.ts | 23 ++++++++++++--------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/routes/api/stacks/[name]/env/+server.ts b/src/routes/api/stacks/[name]/env/+server.ts index b02782c..e9ada76 100644 --- a/src/routes/api/stacks/[name]/env/+server.ts +++ b/src/routes/api/stacks/[name]/env/+server.ts @@ -36,10 +36,11 @@ function parseEnvFile(content: string): Record { */ /** * @openapi - * summary: Get a stack's env vars (non-secrets from file, secrets masked from DB) plus injected provider keys + * summary: Get a stack's env vars plus injected provider keys + * description: The source of `variables` depends on the stack type. For a GIT stack, ALL variables (secret and non-secret) come from the database via this endpoint - the DB is the canonical store the UI, `POST /env/validate`, and deploys read. For an INTERNAL/adopted stack, non-secrets come from the on-disk `.env` file (written via `PUT /env/raw`) and only secrets come from the DB. Secret values are masked as `***` and never returned in plaintext. Each variable is `{ key, value, isSecret }`. * path: name:string The stack name * query: env:integer Environment id the stack belongs to - * resp-200: {variables:array!, injectedSecretKeys:array, secretProvider:object} + * resp-200: {variables:array<{key:string!, value:string!, isSecret:boolean!}>!, injectedSecretKeys:array, secretProvider:object} * resp-403: Permission denied (needs stacks:view) * resp-500: Failed to read env vars */ @@ -134,21 +135,23 @@ export const GET: RequestHandler = async ({ params, url, cookies }) => { /** * PUT /api/stacks/[name]/env?env=X - * Save secret environment variables for a stack. - * Body: { variables: [{ key, value, isSecret }] } + * Save environment variables for a stack. Body: { variables: [{ key, value, isSecret }] }. * - * Only secrets are stored in the database. Non-secret variables live in the - * .env file (written by PUT /env/raw) and are read directly by Docker Compose. + * Every variable in the body is written to the database. For a git stack the DB is the + * canonical store for both overrides and secrets; for an internal/adopted stack the DB + * holds secrets while non-secrets normally live in the .env file (written by PUT /env/raw). * - * If a secret's value is '***' (masked placeholder), the original value - * from the database is preserved. + * If a secret's value is '***' (masked placeholder), the original value from the database + * is preserved. */ /** * @openapi - * summary: Save a stack's secret env vars to the DB (secrets never hit disk) + * summary: Save a stack's env vars to the DB + * description: Persists EVERY variable in the body to the database (secrets and non-secrets alike). For a GIT stack this is the canonical store - non-secret overrides and secrets both belong here, NOT in the `.env` file. For an INTERNAL/adopted stack the DB is used for secrets while non-secrets are expected in the `.env` file (written via `PUT /env/raw`); posting a non-secret here still saves it but the file remains the source the UI reads for that type. A masked secret value of `***` is left unchanged (the stored value is preserved). Each item is `{ key, value, isSecret? }`; a request whose `variables` are not such objects is rejected with 400. * path: name:string The stack name * query: env:integer Environment id the stack belongs to - * body: {variables:array!} + * body: {variables:array<{key:string!, value:string!, isSecret:boolean}>!} + * body-example: {"variables":[{"key":"DB_HOST","value":"db","isSecret":false},{"key":"DB_PASSWORD","value":"s3cret","isSecret":true}]} * resp-400: Invalid variables payload * resp-403: Permission denied (needs stacks:edit) * resp-500: Failed to save env vars