Skip to content

Commit f539ef6

Browse files
committed
docs(#30120): image refusal, carrying generated values over, the admin notice
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cyhB3ePno23ywn1PQd
1 parent 2e917a6 commit f539ef6

2 files changed

Lines changed: 53 additions & 6 deletions

File tree

‎content/docs/deployment/self-hosting.mdx‎

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ workable default:
3434
| Variable | Why it must be set |
3535
|:---|:---|
3636
| `OS_DATABASE_URL` | Without it, data lands in a SQLite file under the ObjectStack home directory (`~/.objectstack`, or `<cwd>/.objectstack` next to a project config) — fine for one box, wrong for containers. Use `postgres://…`, `mysql://…`, `mongodb://…`, `libsql://…`, or a mounted `file:…` path (`postgres://…`'s driver is an **optional** package too — `npm install pg`, or the boot fails loudly rather than degrading to SQLite; `libsql://` / Turso is inferred, but its driver is an **optional** package — `npm install @objectstack/driver-turso`, or the boot fails loudly rather than degrading to SQLite; see [Drivers](/docs/data-modeling/drivers)). `mysql://…` is a supported deployment target that carries three dialect caveats — two of them integrity guarantees MySQL cannot enforce at the database — see [Drivers → MySQL dialect caveats](/docs/data-modeling/drivers#mysql-dialect-caveats) before choosing it; its driver is likewise an **optional** package — `npm install mysql2`, or the boot fails loudly rather than degrading to SQLite. `mongodb://…` is **single-tenant only**: the MongoDB driver has no row-level tenant isolation and refuses to boot unless the tenancy posture is `single` — see [Drivers → Multi-tenancy](/docs/data-modeling/drivers#multi-tenancy-not-supported). |
37-
| `OS_AUTH_SECRET` | Session secret for the auth plugin (`AUTH_SECRET` is the legacy alias). Without it, `/api/v1/auth/*` is **silently skipped** — the server runs unauthenticated. |
37+
| `OS_AUTH_SECRET` | Session secret for the auth plugin (`AUTH_SECRET` is the legacy alias). Without it, `os start` generates one and keeps it in `auth-secret` under the ObjectStack home directory, reused on restart: lose that file (a replaced container) and everyone is signed out. |
3838
| `OS_SECRET_KEY` | 32-byte master key encrypting every stored secret (`openssl rand -hex 32`). On a container's ephemeral filesystem the auto-minted key is **lost on restart**, making previously-encrypted secrets undecryptable. |
3939
| `OS_PORT` | `os start` **fails loudly** if the port is busy (it never auto-shifts like `os dev`). Pin it and keep your reverse-proxy upstream in sync. |
4040

@@ -45,6 +45,40 @@ OS_AUTH_SECRET=$(openssl rand -hex 32)
4545
OS_SECRET_KEY=$(openssl rand -hex 32)
4646
```
4747

48+
### When the three are not set
49+
50+
**The official image refuses to start** unless `OS_DATABASE_URL`,
51+
`OS_AUTH_SECRET` and `OS_SECRET_KEY` are all set explicitly. It exits naming
52+
the ones that are missing, before it writes a database file or a secret: a
53+
container's disk is the part that gets replaced, so booting on it would lose
54+
the data, sign everyone out and leave every saved credential unreadable. The
55+
image turns the check on with `OS_DEPLOYMENT_DURABILITY_CHECK_ENABLED=true` in
56+
its `ENV`.
57+
58+
- An explicit `OS_DATABASE_URL=file:/path/on/a/volume/app.db` passes: SQLite on
59+
a mounted volume stays a choice ([below](#docker-compose-with-postgres)).
60+
- **Already running on values the image generated?** Carry them over before you
61+
set new ones, or the saved credentials stop decrypting and every session
62+
ends. They are files in the old container's home, `/home/node/.objectstack`
63+
(`docker exec <container> cat <file>`, or read them from its volume):
64+
`OS_AUTH_SECRET` is the contents of `auth-secret`, `OS_SECRET_KEY` the
65+
contents of `dev-crypto-key`, and the data is `data/objectstack.db`
66+
(`OS_DATABASE_URL=file:…` on a kept volume, or migrate it to your database).
67+
The refusal prints these paths and says which of them the container holds.
68+
69+
**Every other `os start` keeps booting.** In production mode, when it keeps any
70+
of the three on its own disk, it prints a warning block naming each file and the
71+
variable that moves it off that disk. Administrators also see it in the Console:
72+
a standing notice on the home page and at the top of Setup's System Overview,
73+
listing the same files with a button that copies the variables to set, plus two
74+
lower-severity lines when uploads are stored on the local disk or no email
75+
service is set up. Members never see it, and it is not on the public auth
76+
config (it is read from `GET /api/settings/deployment/notice`, which needs
77+
`manage_platform_settings`). An administrator can hide it: the hide is written
78+
to the audit log as a `config_change` (and refused where no audit log is
79+
mounted), it lasts for that administrator in that browser until the findings
80+
change, and System Overview keeps one small line saying the notice is hidden.
81+
4882
The full catalog is in
4983
[Environment Variables](/docs/deployment/environment-variables).
5084

@@ -252,7 +286,9 @@ curl -fsS http://localhost:8080/api/v1/health
252286

253287
Prefer SQLite on a single small host? Skip the `db` service, mount a volume,
254288
and point `OS_DATABASE_URL` at it: `file:/srv/data/app.db` (with
255-
`- appdata:/srv/data` on the app service). See
289+
`- appdata:/srv/data` on the app service). Set it explicitly even then: the
290+
official image refuses to start on its default database file, which lives in
291+
the container layer ([When the three are not set](#when-the-three-are-not-set)). See
256292
[Drivers](/docs/data-modeling/drivers) for when to reach for which database.
257293

258294
## Health checks & orchestration

‎docker/README.md‎

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,15 +87,26 @@ package set or on a version range.
8787
## What the image presets
8888

8989
- `OS_ARTIFACT_PATH=/srv/app/objectstack.json`, `OS_PORT=8080`,
90-
`NODE_ENV=production`
90+
`NODE_ENV=production`, `OS_DEPLOYMENT_DURABILITY_CHECK_ENABLED=true`
9191
- Runs as the non-root `node` user
9292
- `HEALTHCHECK` on `/api/v1/health` (liveness); use `/api/v1/ready` as the
9393
readiness probe in orchestrators
9494

9595
**You must inject at runtime:** `OS_DATABASE_URL`, `OS_AUTH_SECRET`,
96-
`OS_SECRET_KEY` — never bake them into an image. Full variable catalog and
97-
reverse-proxy / multi-node guidance:
98-
[Self-Hosted Deployment](https://objectstack.ai/docs/deployment/self-hosting).
96+
`OS_SECRET_KEY` — never bake them into an image. **The image refuses to start
97+
without all three** (`OS_DEPLOYMENT_DURABILITY_CHECK_ENABLED=true`): it exits
98+
naming the missing ones, because without them the data would be a SQLite file in
99+
the container layer and both secrets would be generated there, all lost when the
100+
container is replaced. An explicit `OS_DATABASE_URL=file:/path/on/a/volume/app.db`
101+
passes, so SQLite on a mounted volume still works.
102+
103+
Upgrading a container that ran on values the image generated? Carry them over
104+
first, from the old container's `/home/node/.objectstack`: `OS_AUTH_SECRET` is
105+
the contents of `auth-secret`, `OS_SECRET_KEY` the contents of `dev-crypto-key`,
106+
and the data is `data/objectstack.db`. The refusal prints these paths.
107+
108+
Full variable catalog and reverse-proxy / multi-node guidance:
109+
[Self-Hosted Deployment](https://objectstack.ai/docs/deployment/self-hosting#when-the-three-are-not-set).
99110

100111
## Local build of this image
101112

0 commit comments

Comments
 (0)