# Self-host secrets checklist

Required and optional credentials for published-images and Compose deploys.

**Never commit `.env`, never paste secret values into issues/PRs, and never
overwrite an existing `.env` without an explicit backup and operator consent.**

## Published images: what the installer fills

`bash install-images.sh` (without a pre-existing `.env`) copies
`.env.images.example` and fills empty required keys with `openssl`:

| Key | Installer fill | Role |
| --- | --- | --- |
| `POSTGRES_PASSWORD` | `openssl rand -hex 16` | Postgres owner password inside Compose (migrations) |
| `POSTGRES_APP_PASSWORD` | `openssl rand -hex 16` | Password of the app's `rakazo_app` login, which row-level security applies to |
| `BETTER_AUTH_SECRET` | `openssl rand -hex 32` | Auth/session signing |
| `ENCRYPTION_KEY` | `openssl rand -hex 32` | Encrypts stored credentials at rest |
| `SCREEN_PROXY_SECRET` | `openssl rand -hex 32` | Signs short-lived `/novnc/*` screen capabilities |
| `SANDBOX_SUPERVISOR_TOKEN` | `openssl rand -hex 32` | API ↔ sandbox supervisor auth |

Re-runs **preserve** an existing `.env` (they do not rotate secrets); they only append
`POSTGRES_APP_PASSWORD` when an older `.env` lacks it. Empty
required keys still fail closed at Compose validate time
(`${VAR:?Set … in .env}`).

Manual generation (same shapes the installer uses):

```bash
openssl rand -hex 16   # POSTGRES_PASSWORD, POSTGRES_APP_PASSWORD
openssl rand -hex 32   # BETTER_AUTH_SECRET, ENCRYPTION_KEY, SCREEN_PROXY_SECRET, SANDBOX_SUPERVISOR_TOKEN
```

Source-checkout `.env.example` also requires `POSTGRES_PASSWORD` and
`POSTGRES_APP_PASSWORD` (same `openssl rand -hex 16` shape) for
`infra/compose/docker-compose.yml`. Put them in host-side `DATABASE_MIGRATION_URL`
and `DATABASE_URL` when using the optional `docker-compose.postgres-host.yml` overlay. Keep the password URI-safe: Compose
interpolates it into `DATABASE_URL` the same way the images stack does. Hex from
`openssl rand` is safe; characters such as `@ : / ? # %` are not.

Existing source-checkout `pgdata` volumes keep the user, password, and database
from first init (often the former hardcoded `rakazo` / `rakazo`). Keep those
values in `.env`, or change them in place with `ALTER ROLE` / rename. Recreate
the volume only after a backup (or when the data is disposable);
`docker compose down -v` deletes all Postgres state.

Source-checkout `.env.example` asks for a **64-hex** `ENCRYPTION_KEY` in
comments; published-images installer uses 32 bytes of hex (64 hex chars) via
`openssl rand -hex 32`. Prefer long independent random values either way.
Do not reuse one string across keys.

## Distinctness rules

These must be **independent** random values (do not copy-paste the same
secret into multiple keys):

- `BETTER_AUTH_SECRET`
- `ENCRYPTION_KEY`
- `SCREEN_PROXY_SECRET`
- `SANDBOX_SUPERVISOR_TOKEN`
- `RAKAZO_UPDATER_TOKEN` (only if the updater profile is enabled; ≥32 chars,
  also distinct from the four above)

Rotating `ENCRYPTION_KEY` after credentials were stored makes old ciphertext
unreadable. Keep the original key for an existing deployment unless you
intentionally wipe encrypted material.

`ENCRYPTION_KEY` also derives the default master key for chat, memory and file
encryption, so changing it also makes that content unreadable. To move content
off it, set `ENCRYPTION_MASTER_KEYS` and run the rotation in
[content-encryption.md](https://github.com/elie222/rakazo/blob/main/docs/content-encryption.md), which re-wraps the space keys
without stranding content. Keep `ENCRYPTION_KEY` unchanged afterwards: stored
credentials still depend on it.

## Optional keys (leave blank if unused)

From `.env.images.example` (images installer). Leave blank if unused:

| Key(s) | When needed |
| --- | --- |
| `OPENROUTER_API_KEY` | Deployment-wide OpenRouter models |
| `TYPESAFE_API_KEY` | Optional TypeSafe Jev Auto Review verifier (`RAKAZO_AUTO_REVIEW_PROVIDER=jev`) |
| `COMPOSIO_API_KEY` | Composio managed catalog |
| `E2B_API_KEY` / `DAYTONA_API_KEY` / `BOAT_API_KEY` | Remote computers when `SANDBOX_PROVIDER` is not `docker` |
| `EMAIL_PROVIDER` / `EMAIL_API_KEY` / `SMTP_URL` / `EMAIL_FROM` | Optional account email via Resend API or SMTP |
| `EXPO_ACCESS_TOKEN` | Mobile push when the Expo project enables enhanced push security |
| Messaging tokens (Slack, Telegram, …) | Only if you enable those surfaces |

Blank optional keys are normal for a minimal published-images boot. Pipedream
Connect keys appear only in source/Compose `.env.example`, not the images
example.

## Operator workflow

```bash
bash install-images.sh --prepare-only   # creates .env + fills empties
# inspect key NAMES only if debugging; never log values
bash install-images.sh                  # pull + up; preserves .env
curl -fsS http://127.0.0.1:3100/internal/health
```

If `sandbox` in `/internal/health` is `"none"` or the supervisor never becomes healthy,
check that `SANDBOX_SUPERVISOR_TOKEN` is set and non-empty for the Docker
computer path. A missing token is a setup failure, not an "optional tighten
later" item.

## Recovery without reprinting secrets

- Lost UI password: use SMTP recovery if configured; otherwise operator
  identity recovery is out of band (DB). Prefer not to rotate
  `BETTER_AUTH_SECRET` casually on a live deploy.
- Lost `.env`: from backup only. Recreating random secrets on a volume that
  still holds Postgres data will desynchronize passwords and encryption.
- Moving hosts: copy `.env` and volumes together; treat `.env` as secret
  material in transit.

## Related

- [Self-hosting](/docs/self-host/)

## Optional credential storage

Stored credentials can use self-hosted Infisical while short-lived secrets stay
encrypted in Postgres. See [Infisical setup, migration, and rollback](https://github.com/elie222/rakazo/blob/main/docs/infisical-secrets.md).
