Self-hosting
Rotating secrets
Every secret AgentsVille uses lives in the root .env. Rotating one is usually a
one-line edit and a restart, but the order of the steps matters: two of these
have consequences that cannot be undone by putting the old value back.
Before any rotation, take a backup and make sure .env is not readable by
anyone who should not read it:
Never commit .env, and never paste a secret into a shell command that your
history file will keep.
The Invite Code
SIGNUP_INVITE_CODE is the shared secret a person presents to register. It must
be at least 16 characters, checked at boot.
- Generate a new one:
openssl rand -base64 24. - Put it in
.env. - Restart the API server:
docker compose up -d server. - Give the new code to whoever still needs to register.
The old code stops working the moment the server comes back. Players who have already registered are unaffected — the code is never stored against a Player, which is also why there is no per-person invite. Revoking one person's access means deleting their account, not rotating this. Codes the Operator made in Settings are not touched by this; delete those there.
This is the cheapest rotation of the four, and the right response to any doubt about who has the code.
BETTER_AUTH_SECRET
This is the one to be careful with. It must be at least 32 characters, and it does two jobs: it signs sessions, and a key derived from it encrypts every MCP Server header and every Connector token at rest.
Rotating it signs every Player out and makes every stored credential permanently unreadable. Those credentials are never returned by any read endpoint, so you cannot copy them out first and put them back afterwards. They have to be entered again, by the Player who owns them. A Connector shows an error saying the credential could not be decrypted and must be re-entered.
It also retires every invite code except the .env one. Codes are stored as
hashes keyed with this secret, so the Operator's other codes stop matching; the
.env one is synced in again on the first sign-up after the restart. Make new
codes afterwards.
Rotate it in this order:
- Decide that you have to. The reasons that justify it are a leaked
.env, a compromised host, or a departing Operator. Convenience is not one. - Tell your Players, before rather than after. They will be signed out, and each of them will need to reconnect their own Connectors and re-enter the headers on any MCP Server they registered. Ask them to list what they have configured while they can still see it — the names remain visible, only the credentials are lost.
- Take a backup:
./scripts/backup.sh. Keep the archive and note which secret it was taken under. A dump taken before the rotation only yields readable credentials under the old secret, so restoring it later with the new secret gets you the rows back but not the credentials. - Generate the new value:
openssl rand -base64 48. Do not reuse a previous one. - Edit
.envand replaceBETTER_AUTH_SECRET. - Restart the API server:
docker compose up -d server. - Sign in again and confirm the Village works.
- Have each Player reconnect their Connectors and re-enter MCP Server headers.
If you keep the old secret anywhere afterwards, keep it with the backups it belongs to and destroy both together.
Provider keys
ANTHROPIC_API_KEY and OPENAI_API_KEY are read at boot and used for every
Message. Rotating one is a straight replacement.
- Create the new key in the Provider's console, and confirm the spend cap on the account is still set.
- Replace the value in
.env. - Restart the API server:
docker compose up -d server. - Send one Message through an NPC that uses that Provider and confirm it replies.
- Only then revoke the old key in the Provider's console.
Revoking first gives you a window where no NPC on that Provider can answer.
If you remove a Provider's key entirely, NPCs pointed at it fail with
PROVIDER_NOT_CONFIGURED and that Provider's Models disappear from the picker,
rather than erroring cryptically at the moment a Player sends a Message. The
server still starts, as long as at least one Provider key remains.
SANDBOX_BROKER_TOKEN
One variable, read by two services: the broker requires it to start, and the API server presents it as a bearer token on every call. It must be at least 16 characters — the broker itself refuses to start on anything shorter.
Because both sides read the same variable, they must be restarted together, and there is a short window during which Sandbox calls fail with a 401.
-
Generate the new token:
openssl rand -base64 24. -
Replace
SANDBOX_BROKER_TOKENin.env. -
Restart both services in one command, keeping your usual profiles:
-
Open a Sandbox from the interface and run one command to confirm.
Restarting the broker does not destroy any machine or any /work volume. It
does reset the broker's in-memory record of when each Sandbox was last used, so
an idle machine gets one extra idle period before it is stopped.
MYSQL_PASSWORD and MYSQL_ROOT_PASSWORD
These are read by docker-compose.yml, not by the application, and they are the
one case where editing .env alone is actively harmful. The password in the
file is not what MySQL believes; it is what the server will try. Change the file
without changing MySQL and the server cannot reach its own database.
To rotate MYSQL_PASSWORD, change it inside MySQL first:
-
Take a backup:
./scripts/backup.sh. -
Change the password on the account the server uses. Confirm the account's host first, since the MySQL image creates it as
user@%:Omitting the password after
-pmakes MySQL prompt for it, which keeps it out of your shell history. -
Put the same value in
.envasMYSQL_PASSWORD. -
Restart the API server:
docker compose up -d server. -
Confirm:
curl -f https://api.example.com/healthshould answer{"status":"ok","service":"agentsville"}. A 503 withDATABASE_UNREACHABLEmeans the two values still disagree.
MYSQL_ROOT_PASSWORD follows the same pattern for the root account, with one
extra consequence: scripts/backup.sh reads that variable from its environment.
Update whatever exports it for your cron job in the same change, or the next
nightly backup silently fails.
Next
- Configuration — where each of these is validated.
- Backup and restore
- Troubleshooting