Security
What leaves your network (nothing), which ports are open, where secrets live, and the hardening steps worth doing.
What leaves your network
Nothing that we require.
Licence verification is a local signature check against a public key compiled into the build. An instance that has never been online works identically to one that has.
Your records, documents, attachments and agent conversations stay on your infrastructure. There is no telemetry endpoint to disable, because there is no telemetry to send.
The exception is anything you configure to reach out — an external S3 endpoint, an SMTP server, an agent provider such as an LLM API. Those are your choices, and each one is a place data can leave. An air-gapped deployment simply does not configure them.
Ports
| Port | Exposed | Why |
|---|---|---|
3000/tcp | Yes, this is the app | Put TLS in front of it |
Postgres 5432 | No | Container network only |
SeaweedFS 8333 | Yes, object storage | The browser fetches attachments directly, so it must be reachable — put TLS in front of it too |
SeaweedFS 9333 | No | Container network only |
Two published ports, and both belong behind the same reverse proxy. Object storage is published because the product hands storage URLs to the browser — presigned PUT for upload, redirect for download, the same shape as S3 on Busabase Cloud. Access to it is still gated by signed URLs and credentials, not by being unreachable.
Do not publish anything else — in particular, do not add -p 5432:5432 "temporarily" to look at the database. Use docker compose exec postgres psql.
Where secrets live
| Secret | Where | Notes |
|---|---|---|
| Postgres password | .env (Compose) or /data/.credentials (all-in-one) | Generated, never baked into the image |
| Object storage keys | .env and seaweed-s3.json | Two files, must match — this is what init.sh handles |
| Session signing key | BETTER_AUTH_SECRET | Leaking it lets anyone forge a signed-in session |
All of these are generated per deployment. Two customers pulling the same image do not share any secret, and neither do we know yours.
/data/.credentials is mode 600. init.sh writes .env as 600 too, and backup.sh sets 600 on everything it produces. If you copy these files around, keep the mode.
BETTER_AUTH_SECRET is the one to be careful with. A leaked database password requires network access to the database to exploit; a leaked session key can be used from anywhere that can reach the login page. Rotate it if it has ever been in a shared document, a chat message, or a git repository — every existing session is invalidated, which is the point.
Least privilege
The Compose deployment's app container runs as a non-root user. The all-in-one image runs as root inside the container, because supervisord has to drop privileges to postgres for the database process — the app itself is still reachable only through the published port.
If your policy forbids root inside containers, use the Compose deployment. That is the honest answer; there is no flag that makes the all-in-one rootless today.
Hardening worth doing
- TLS at the proxy, with
APP_URLset to thehttps://address. Session cookies followAPP_URL, so getting this right is a security setting, not just a cosmetic one. - Restrict who can reach port 3000 at the firewall to the proxy only.
- Back up
.envand/data/.credentialsto somewhere with the same protection as production, and no lower. - Set
SYSTEM_ADMIN_EMAILto a real, monitored address rather than leaving the default. - Rotate
BETTER_AUTH_SECRETon any suspicion of exposure, accepting that it signs everyone out.
Audit
Premium keeps a change history for records and documents, and the system admin console can export the audit log. That covers "who changed what inside Busabase". It does not cover host-level access — if someone can docker exec into the container, they are past every control described here, so treat shell access to the host as the real trust boundary.