Changing Passwords and API Keys
The DB account and AI keys used by Studio ship with the same initial values on every installation. Change them once during commissioning and be done. After that, there is nothing more to do.
There is exactly one tool for this: bin/passwd.sh. All three products — platform, ai, and studio — provide it under the same name with the same usage.
/etc/kopens/plantpulse-studio.envThis product's secrets live in /etc/kopens/plantpulse-studio.env. That is outside the repo tree, not inside the installation directory (/opt/kopens/plantpulse-studio-docker), and its permissions are 0600.
The .env at the root of the installation directory is the old path. Nothing reads it anymore — editing it will not change the stack.
Before 2026-08-16, passwd.sh wrote to /etc/kopens/plantpulse-studio.env while compose and the remaining scripts read the repo-root .env. As a result, rotation was silently ineffective: passwd.sh reported success while the stack kept running with the old values (ANTHROPIC_API_KEY and AI_API_KEY were in this state). Do not go back to the old path.
App login accounts are not covered here
Do not conflate the two.
| What | Where to change it | |
|---|---|---|
| App login account | The account a person uses to log in to Studio | Settings → User Management inside the app |
| Infrastructure and API keys | Bundled PostgreSQL account, AI keys | This document — bin/passwd.sh |
.env — it becomes a dead valueAt login, the Studio server checks ① the DB, then ② environment variables, in that order. If the account is found in the DB, the lookup stops there, so editing STUDIO_LOCAL_USERS in the env afterwards has no effect.
App accounts are created, assigned roles, and have their passwords reset in Settings → User Management. See the User Management section of Settings (Administrator) for details. Accounts whose source is 환경변수 cannot be edited or deleted from the UI.
The bootstrap account (STUDIO_LOCAL_USERS) is a temporary means of first access. Once operational accounts are ready, clear it and restart.
What can be changed
Start with the list. Running it with no arguments does the same thing.
cd /opt/kopens/plantpulse-studio-docker
bin/passwd.sh --list
The output shows the key, the ID, the current value (first 3 characters only), the target, and where that value lives. To see the full value, use --list --show.
| Key | What | How it changes |
|---|---|---|
PG_PASSWORD | Bundled PostgreSQL | The server account is the source of truth — ALTER ROLE (psql) |
ANTHROPIC_API_KEY | Anthropic API key | File — loaded when agent-server starts |
AI_API_KEY | OpenAI-compatible API key | File — loaded when agent-server starts |
Key names are exactly the environment variable names. There are no aliases such as postgres — an alias would require maintaining a separate mapping table between names and variables, and such a table inevitably drifts out of sync with reality.
PG_PASSWORD is not in the listPG_PASSWORD is owned by this product only on installations that bring up the bundled PostgreSQL (when COMPOSE_PROFILES contains bundled-pg). On installations that use a platform or shared PG, that password belongs to someone else, so it is omitted from the list and you are told as much.
The platform's PP_* is not here either. Passing it is rejected just like a typo.
Making the change
Omitting the value and entering it at the prompt is the recommended path. The password is then left out of ps output and shell history. It is entered twice and checked for a match.
bin/passwd.sh PG_PASSWORD
You can also pass the value directly as an argument.
bin/passwd.sh ANTHROPIC_API_KEY=<new-value>
Passing several at once bundles the restart into a single operation. Use this form during commissioning.
bin/passwd.sh PG_PASSWORD=<new-password> ANTHROPIC_API_KEY=<new-value>
To see only what would change without touching anything, use --dry-run.
bin/passwd.sh --dry-run PG_PASSWORD=<new-password>
When run, it first shows what will change and its impact, then asks for confirmation with [y/N]. To skip confirmation in automation, use --yes.
In environments without a terminal, such as scripts or cron, omitting the value means no prompt can be shown and the command fails. Pass it in KEY=<value> form instead.
What happens when it runs
The order is fixed.
| Step | What it does |
|---|---|
| 1 | Whatever is server-authoritative first — for PG_PASSWORD, the account is changed inside PostgreSQL first |
| 2 | /etc/kopens/plantpulse-studio.env is updated with the new value (permissions 0600 preserved) |
| 3 | docker compose up -d --wait — containers are recreated so the new values take effect |
The server side is changed first, then the file is brought into line. Doing it the other way round leaves the server on the old value while the file holds the new one, so connections stay broken until the restart.
Studio access is interrupted during this time. Perform it within an inspection window.
If it fails partway through
| Where | State | What to do |
|---|---|---|
| Step 1 (PostgreSQL account change) | Aborts without touching the file | Check with bin/status.sh that the bundled PG is up, then run again |
| Step 3 (restart) | The file already holds the new value | Check status with bin/status.sh and resolve the startup failure |
PG_PASSWORD once data has accumulatedIn bundled mode, changing only PG_PASSWORD in the file puts it out of sync with the account in the existing data directory, and the stack will not come up. This is exactly why passwd.sh changes the server account first.
Verification
bin/passwd.sh --list
If the first 3 characters of the current value have changed, it has taken effect. The first 3 characters are shown so you can tell at a glance whether a default is still in place.
Related documents
- Secret Management — what each of the 6 keys is used for
- Settings (Administrator) — app account and role management
- Installation — initial installation and first login