Skip to content

Commit 04ce2db

Browse files
authored
docs: third-party developer onboarding and self-hosted deployment guides (#2899)
Add getting-started/your-first-project (scaffold via create-objectstack, project anatomy, REST calls, validate gate, artifact build) and deployment/self-hosting (systemd, Docker, Compose+Postgres, health probes, reverse proxy, multi-node notes). Fix the stale create-objectstack README template list and wire nav/cross-links.
1 parent e46169c commit 04ce2db

7 files changed

Lines changed: 563 additions & 51 deletions

File tree

content/docs/deployment/index.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ managed KMS / Vault provider behind the same `ICryptoProvider` seam.
167167

168168
## Related
169169

170+
- [Self-Hosted Deployment](/docs/deployment/self-hosting)
170171
- [Single-Environment Mode](/docs/deployment/single-project-mode)
171172
- [Environment-Scoped Routing](/docs/api/environment-routing)
172173
- [Publish, Versioning & Preview](/docs/deployment/publish-and-preview)

content/docs/deployment/meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"icon": "Cloud",
44
"pages": [
55
"index",
6+
"self-hosting",
67
"vercel",
78
"production-readiness",
89
"publish-and-preview",
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
---
2+
title: Self-Hosted Deployment
3+
description: Run a compiled ObjectStack app on your own infrastructure — bare Node.js, systemd, Docker, and Docker Compose with Postgres, including health checks, reverse-proxy wiring, and the secrets you must pin.
4+
---
5+
6+
# Self-Hosted Deployment
7+
8+
This guide takes the artifact produced by `os build` / `os compile` and runs it
9+
on infrastructure **you** operate: a Linux host, a Docker container, or a
10+
compose stack with Postgres. It complements the platform-specific
11+
[Vercel guide](/docs/deployment/vercel) and assumes you have read
12+
[Deployment Modes](/docs/deployment).
13+
14+
The deployment model is deliberately simple:
15+
16+
```
17+
objectstack.config.ts ──(os build, CI)──▶ dist/objectstack.json ──(os start, server)──▶ running app
18+
```
19+
20+
- The **artifact** (`dist/objectstack.json`) is a portable, self-describing
21+
JSON file — your entire app. Build it once in CI; the host needs no
22+
TypeScript and no build step.
23+
- **`os start`** boots a production server directly from that artifact
24+
([reference](/docs/getting-started/cli#os-start)).
25+
- **Deployment config stays outside the artifact.** Database URL, secrets, and
26+
environment identity are injected via `OS_*` environment variables or flags.
27+
28+
## The minimum viable production environment
29+
30+
Four values every self-hosted deployment must pin — everything else has a
31+
workable default:
32+
33+
| Variable | Why it must be set |
34+
|:---|:---|
35+
| `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://…`, `libsql://…`, or a mounted `file:…` path. |
36+
| `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_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. |
38+
| `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. |
39+
40+
Generate strong values once and store them in your secret manager:
41+
42+
```bash
43+
OS_AUTH_SECRET=$(openssl rand -hex 32)
44+
OS_SECRET_KEY=$(openssl rand -hex 32)
45+
```
46+
47+
The full catalog is in
48+
[Environment Variables](/docs/deployment/environment-variables).
49+
50+
## Option 1 — Bare Node.js (systemd)
51+
52+
The simplest deployment: Node 18+ and the CLI on a Linux host.
53+
54+
```bash
55+
# On the host — no repo clone, just the CLI and your artifact
56+
npm install -g @objectstack/cli
57+
scp dist/objectstack.json server:/opt/my-app/objectstack.json
58+
```
59+
60+
```ini title="/etc/systemd/system/my-app.service"
61+
[Unit]
62+
Description=My ObjectStack App
63+
After=network.target postgresql.service
64+
65+
[Service]
66+
Type=simple
67+
User=objectstack
68+
WorkingDirectory=/opt/my-app
69+
Environment=NODE_ENV=production
70+
Environment=OS_ARTIFACT_PATH=/opt/my-app/objectstack.json
71+
Environment=OS_PORT=8080
72+
EnvironmentFile=/opt/my-app/secrets.env # OS_DATABASE_URL, OS_AUTH_SECRET, OS_SECRET_KEY
73+
ExecStart=/usr/bin/os start
74+
Restart=on-failure
75+
76+
[Install]
77+
WantedBy=multi-user.target
78+
```
79+
80+
```bash
81+
sudo systemctl enable --now my-app
82+
curl -fsS http://localhost:8080/api/v1/health
83+
```
84+
85+
Upgrades are atomic: replace the artifact file and restart the service. Roll
86+
back by restoring the previous artifact.
87+
88+
## Option 2 — Docker
89+
90+
The artifact model maps cleanly onto containers: the image contains Node, the
91+
CLI, and one JSON file.
92+
93+
```dockerfile title="Dockerfile"
94+
# ── Build stage: compile TypeScript metadata to the artifact ─────────
95+
FROM node:22-slim AS build
96+
WORKDIR /app
97+
COPY package*.json ./
98+
RUN npm ci
99+
COPY . .
100+
RUN npx os build # → dist/objectstack.json
101+
102+
# ── Runtime stage: CLI + artifact only ───────────────────────────────
103+
FROM node:22-slim
104+
WORKDIR /srv/app
105+
RUN npm install -g @objectstack/cli
106+
COPY --from=build /app/dist/objectstack.json ./objectstack.json
107+
108+
ENV NODE_ENV=production \
109+
OS_ARTIFACT_PATH=/srv/app/objectstack.json \
110+
OS_PORT=8080
111+
EXPOSE 8080
112+
113+
HEALTHCHECK --interval=30s --timeout=3s --start-period=15s \
114+
CMD node -e "fetch('http://localhost:8080/api/v1/health').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"
115+
116+
CMD ["os", "start"]
117+
```
118+
119+
```bash
120+
docker build -t my-app .
121+
docker run -p 8080:8080 \
122+
-e OS_DATABASE_URL="postgres://user:pass@db-host:5432/myapp" \
123+
-e OS_AUTH_SECRET \
124+
-e OS_SECRET_KEY \
125+
my-app
126+
```
127+
128+
<Callout type="warn">
129+
**Never bake `OS_AUTH_SECRET` / `OS_SECRET_KEY` into the image.** Pass them at
130+
runtime from your orchestrator's secret store. And never rely on the
131+
auto-minted dev crypto key inside a container — it lives on the ephemeral
132+
filesystem and dies with it.
133+
</Callout>
134+
135+
## Option 3 — Docker Compose with Postgres
136+
137+
A complete single-host production stack:
138+
139+
```yaml title="docker-compose.yml"
140+
services:
141+
app:
142+
build: .
143+
ports:
144+
- "8080:8080"
145+
environment:
146+
OS_DATABASE_URL: postgres://objectstack:${POSTGRES_PASSWORD}@db:5432/myapp
147+
OS_AUTH_SECRET: ${OS_AUTH_SECRET}
148+
OS_SECRET_KEY: ${OS_SECRET_KEY}
149+
depends_on:
150+
db:
151+
condition: service_healthy
152+
restart: unless-stopped
153+
154+
db:
155+
image: postgres:17
156+
environment:
157+
POSTGRES_USER: objectstack
158+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
159+
POSTGRES_DB: myapp
160+
volumes:
161+
- pgdata:/var/lib/postgresql/data
162+
healthcheck:
163+
test: ["CMD-SHELL", "pg_isready -U objectstack -d myapp"]
164+
interval: 5s
165+
timeout: 3s
166+
retries: 10
167+
restart: unless-stopped
168+
169+
volumes:
170+
pgdata:
171+
```
172+
173+
```bash
174+
# .env next to docker-compose.yml (never committed)
175+
POSTGRES_PASSWORD=…
176+
OS_AUTH_SECRET=…
177+
OS_SECRET_KEY=…
178+
179+
docker compose up -d
180+
curl -fsS http://localhost:8080/api/v1/health
181+
```
182+
183+
Prefer SQLite on a single small host? Skip the `db` service, mount a volume,
184+
and point `OS_DATABASE_URL` at it: `file:/srv/data/app.db` (with
185+
`- appdata:/srv/data` on the app service). See
186+
[Drivers](/docs/data-modeling/drivers) for when to reach for which database.
187+
188+
## Health checks & orchestration
189+
190+
Every runtime exposes two probe endpoints — wire them into Docker
191+
`HEALTHCHECK`, Kubernetes probes, or your load balancer:
192+
193+
| Endpoint | Meaning | Use as |
194+
|:---|:---|:---|
195+
| `GET /api/v1/health` | Process is up and serving HTTP | Liveness probe |
196+
| `GET /api/v1/ready` | Kernel booted, ready for traffic | Readiness probe |
197+
198+
On Kubernetes, the same image works unchanged: mount the secrets as env vars,
199+
point `readinessProbe` at `/api/v1/ready`, and scale — but read the multi-node
200+
note below first.
201+
202+
## Reverse proxy & TLS
203+
204+
Terminate TLS in front of the app (Caddy, nginx, Traefik, or your cloud LB)
205+
and keep three things in sync with the public origin:
206+
207+
```bash
208+
OS_AUTH_URL=https://app.example.com # auth callbacks / cookie origin
209+
OS_TRUSTED_ORIGINS=https://app.example.com # CORS allow-list
210+
OS_PORT=8080 # must match the proxy upstream
211+
```
212+
213+
```text title="Caddyfile"
214+
app.example.com {
215+
reverse_proxy localhost:8080
216+
}
217+
```
218+
219+
A drifted port or origin is the classic self-hosting failure: the app runs,
220+
but logins bounce and browsers block API calls. Enable HSTS and tune security
221+
headers only after TLS is confirmed — see
222+
[Production Readiness](/docs/deployment/production-readiness).
223+
224+
## Scaling beyond one node
225+
226+
The default in-process coordination (locks, queues, schedules) is
227+
single-node. Before running replicas, set `OS_CLUSTER_DRIVER` — the runtime
228+
then treats the deployment as multi-node and **refuses to boot without an
229+
explicit `OS_SECRET_KEY`** rather than minting per-node keys that can't
230+
decrypt each other's secrets. All replicas must share the same
231+
`OS_SECRET_KEY`, `OS_AUTH_SECRET`, and database. See
232+
[Cluster](/docs/kernel/cluster).
233+
234+
## Go-live
235+
236+
Before pointing real users at the deployment, walk the
237+
[Production Readiness](/docs/deployment/production-readiness) checklist —
238+
security headers, rate limits, metrics, error reporting, backup/restore
239+
drills, and data-retention windows.
240+
241+
<Callout type="tip">
242+
**Your self-hosted app is AI-operable out of the box:** every deployment
243+
serves an MCP server at `/api/v1/mcp` under the same permissions and RLS.
244+
Disable with `OS_MCP_SERVER_ENABLED=false`. See
245+
[Your app as an MCP server](/docs/api#your-app-as-an-mcp-server).
246+
</Callout>
247+
248+
## Related
249+
250+
- [Deployment Modes](/docs/deployment) — the map of local / standalone / Cloud
251+
- [`os start` reference](/docs/getting-started/cli#os-start) — every flag and env var
252+
- [Environment Variables](/docs/deployment/environment-variables) — the full catalog
253+
- [Deploy to Vercel](/docs/deployment/vercel) — the serverless alternative
254+
- [Troubleshooting & FAQ](/docs/deployment/troubleshooting)

content/docs/getting-started/index.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,5 @@ For more troubleshooting, see the [Troubleshooting & FAQ](/docs/deployment/troub
197197
- [Architecture](/docs/concepts/architecture) — The three-layer protocol stack
198198
- [Glossary](/docs/getting-started/glossary) — Key terminology
199199
- [Build with Claude Code](/docs/getting-started/build-with-claude-code) — Build your first app end-to-end with an agent
200+
- [Your First Project](/docs/getting-started/your-first-project) — The hands-on path: scaffold from npm, extend by hand, call the API
200201
- [Anatomy of an ObjectStack App](/docs/getting-started/quick-start) — Read the metadata an agent writes, so you can verify it

content/docs/getting-started/meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"index",
77
"how-ai-development-works",
88
"build-with-claude-code",
9+
"your-first-project",
910
"quick-start",
1011
"examples",
1112
"cli",

0 commit comments

Comments
 (0)