okengine 0.8.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/site/content/docs/deployment/docker-swarm.mdx +228 -0
- package/site/content/docs/deployment/docker.mdx +212 -0
- package/site/content/docs/deployment/index.mdx +83 -0
- package/site/content/docs/deployment/kubernetes.mdx +176 -0
- package/site/content/docs/deployment/meta.json +5 -0
- package/site/content/docs/deployment/reverse-proxy.mdx +216 -0
- package/site/content/docs/elements/channel.mdx +10 -5
- package/site/content/docs/elements/signal.mdx +10 -8
- package/site/content/docs/elements/store.mdx +34 -0
- package/site/content/docs/get-started/index.mdx +5 -0
- package/site/content/docs/index.mdx +5 -0
- package/site/content/docs/meta.json +10 -1
- package/site/content/docs/plugins/index.mdx +1 -2
- package/site/content/docs/plugins/magic-link.mdx +2 -2
- package/site/content/docs/plugins/meta.json +1 -2
- package/site/content/docs/plugins/otp.mdx +214 -0
- package/site/content/docs/plugins/two-factor.mdx +2 -2
- package/site/content/docs/reference/cli.md +3 -2
- package/site/content/docs/reference/configuration.mdx +16 -0
- package/site/content/docs/reference/environment-variables.mdx +9 -8
- package/site/content/docs/reference/plugins.mdx +1 -1
- package/src/auth/auth.test.ts +36 -0
- package/src/auth/bindings.ts +3 -12
- package/src/auth/identity.ts +33 -0
- package/src/auth/index.ts +5 -0
- package/src/auth/otp-capability.ts +119 -0
- package/src/auth/otp-seal.test.ts +61 -0
- package/src/auth/otp-seal.ts +84 -0
- package/src/auth/schema.ts +3 -0
- package/src/auth/sessions.ts +26 -27
- package/src/auth/tables.ts +4 -0
- package/src/auth/verification.ts +61 -1
- package/src/cli/dev-app-runner.ts +4 -0
- package/src/cli/docker.ts +4 -1
- package/src/cli/load-config.images.test.ts +4 -0
- package/src/cli/load-config.ts +3 -0
- package/src/cli/registry.ts +1 -1
- package/src/console/server/operator-db.ts +34 -9
- package/src/docker/compose.ts +162 -6
- package/src/docker/derive.ts +60 -3
- package/src/docker/docker.test.ts +374 -1
- package/src/docker/helpers.ts +2 -0
- package/src/docker/index.ts +11 -0
- package/src/docker/recipes/caddy.ts +51 -0
- package/src/docker/recipes/dragonfly.ts +31 -0
- package/src/docker/recipes/index.ts +25 -2
- package/src/docker/recipes/pgdog.ts +84 -0
- package/src/docker/recipes/redis.ts +6 -3
- package/src/docker/recipes/traefik.ts +83 -0
- package/src/docker/recipes/valkey.ts +30 -0
- package/src/docker/stack-id.ts +5 -0
- package/src/docker/types.ts +18 -0
- package/src/drivers/channel-taqnyat-whatsapp.ts +94 -0
- package/src/drivers/channel-types.ts +1 -0
- package/src/elements/channel/otp-delivery.test.ts +76 -0
- package/src/elements/channel/otp-delivery.ts +291 -0
- package/src/elements/channel/runtime.ts +152 -114
- package/src/elements/channel.ts +12 -2
- package/src/index.ts +3 -0
- package/src/kernel/app.ts +60 -4
- package/src/kernel/boot-bind/channel.ts +51 -0
- package/src/kernel/boot-bind/gate.ts +14 -19
- package/src/kernel/boot-bind/honor-config.test.ts +18 -0
- package/src/kernel/boot-bind/signal.ts +20 -0
- package/src/kernel/boot-bind/store.test.ts +82 -0
- package/src/kernel/boot-bind/store.ts +22 -0
- package/src/kernel/boot.test.ts +5 -5
- package/src/kernel/fx.test.ts +3 -0
- package/src/kernel/fx.ts +49 -0
- package/src/kernel/graceful-shutdown.test.ts +76 -0
- package/src/kernel/graceful-shutdown.ts +106 -0
- package/src/kernel/horizontal-child.ts +257 -0
- package/src/kernel/horizontal.integration.test.ts +229 -0
- package/src/kernel/index.ts +8 -0
- package/src/kernel/ready.test.ts +76 -0
- package/src/plugins/auth-delivery.mailpit.integration.test.ts +5 -5
- package/src/plugins/auth-methods.security.test.ts +19 -29
- package/src/plugins/auth-methods.test.ts +7 -6
- package/src/plugins/index.ts +12 -8
- package/src/plugins/magic-link.ts +1 -23
- package/src/plugins/otp.test.ts +238 -0
- package/src/plugins/otp.ts +572 -0
- package/src/plugins/taqnyat.live.test.ts +5 -7
- package/src/release/official-plugins.ts +1 -2
- package/site/content/docs/plugins/email-otp.mdx +0 -117
- package/site/content/docs/plugins/phone-number.mdx +0 -172
- package/src/plugins/email-otp.ts +0 -214
- package/src/plugins/phone-number.ts +0 -206
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "okengine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "One law. Eight elements. Ten exports. One package. One manifest. Every backend need is derived, never added.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"lint": "oxlint --quiet",
|
|
51
51
|
"lint:fix": "oxlint --fix",
|
|
52
52
|
"fmt": "oxfmt",
|
|
53
|
+
"format": "oxfmt",
|
|
53
54
|
"fmt:check": "oxfmt --check",
|
|
54
55
|
"test": "bun test --pass-with-no-tests",
|
|
55
56
|
"test:coverage": "bun test --coverage --pass-with-no-tests",
|
|
@@ -73,7 +74,7 @@
|
|
|
73
74
|
"ajv-formats": "^3.0.1",
|
|
74
75
|
"intl-messageformat": "^11.2.13",
|
|
75
76
|
"oxc-parser": "^0.142.0",
|
|
76
|
-
"sently": "1.2.
|
|
77
|
+
"sently": "1.2.1"
|
|
77
78
|
},
|
|
78
79
|
"devDependencies": {
|
|
79
80
|
"@base-ui/react": "^1.6.0",
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Docker Swarm
|
|
3
|
+
description: Deploy the generated compose stack with docker stack deploy — PgDog, HEALTHCHECK readiness, graceful SIGTERM, known multi-instance limits, and optional Caddy or Traefik.
|
|
4
|
+
icon: Ship
|
|
5
|
+
source: README.md
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Swarm consumes the same files `oke docker --prod` already writes. It adds
|
|
9
|
+
built-in orchestration (scale, rolling updates, routing mesh) with a lower
|
|
10
|
+
learning curve than Kubernetes. Scale replaceable replicas; share durable drivers.
|
|
11
|
+
|
|
12
|
+
<Callout title="The one rule">
|
|
13
|
+
Deploy with `docker stack deploy`. Docker’s single `HEALTHCHECK` targets `GET /_/ready`. Treat
|
|
14
|
+
every task as replaceable — shared Postgres / Redis / S3, SIGTERM via `stop_grace_period`, honest
|
|
15
|
+
Signal/Channel process-local limits.
|
|
16
|
+
</Callout>
|
|
17
|
+
|
|
18
|
+
## Quick start
|
|
19
|
+
|
|
20
|
+
<Steps>
|
|
21
|
+
|
|
22
|
+
<Step>
|
|
23
|
+
### Derive the prod compose layers
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
oke docker --prod
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Emits `Dockerfile`, per-role layers, `compose.prod.yml` (`HEALTHCHECK` →
|
|
30
|
+
`/_/ready`, `deploy.*`, `stop_grace_period: 30s`), and **`compose.all.yml`**
|
|
31
|
+
(layers 1–3 merged). `compose.override.yml` is yours (never written by oke).
|
|
32
|
+
|
|
33
|
+
</Step>
|
|
34
|
+
|
|
35
|
+
<Step>
|
|
36
|
+
### Build the app image, init Swarm, deploy
|
|
37
|
+
|
|
38
|
+
`docker stack deploy` ignores `build` — tag first (name matches `compose.yml`).
|
|
39
|
+
Prefer the single merged file:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
docker build -t oke-<app>:latest -f docker/Dockerfile .
|
|
43
|
+
docker swarm init
|
|
44
|
+
cd docker
|
|
45
|
+
docker stack deploy -c compose.all.yml myapp
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Optional: `-c compose.override.yml` after that for local tweaks. Create
|
|
49
|
+
`secrets:` names via `docker secret create`, or drop those keys in
|
|
50
|
+
`compose.override.yml` while using `.env.docker`.
|
|
51
|
+
|
|
52
|
+
</Step>
|
|
53
|
+
|
|
54
|
+
<Step>
|
|
55
|
+
### Scale and roll
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
docker service scale myapp_app=3
|
|
59
|
+
docker service update --image oke-<app>:latest myapp_app
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Prod overlay: `update_config` (`parallelism: 1`, `order: start-first`,
|
|
63
|
+
`failure_action: rollback`) and `restart_policy.condition: on-failure`.
|
|
64
|
+
Tweak in `compose.override.yml`.
|
|
65
|
+
|
|
66
|
+
</Step>
|
|
67
|
+
|
|
68
|
+
</Steps>
|
|
69
|
+
|
|
70
|
+
## Share backing services
|
|
71
|
+
|
|
72
|
+
Pin docker/prod drivers to shared stores:
|
|
73
|
+
|
|
74
|
+
| Concern | Driver | Shared backend |
|
|
75
|
+
| ----------------------------- | ---------------------------- | ------------------------------------------------- |
|
|
76
|
+
| SQL / durable journal / clock | `postgres` | Postgres (optionally via PgDog) |
|
|
77
|
+
| Gate rate counters | `redis` (`drivers.store.kv`) | Redis / Valkey / Dragonfly (`images["store.kv"]`) |
|
|
78
|
+
| Files | `s3` | S3-compatible object store |
|
|
79
|
+
| Signal | see known limits below | — |
|
|
80
|
+
|
|
81
|
+
## PgDog pooling
|
|
82
|
+
|
|
83
|
+
Bun.SQL defaults to **10** connections per process — fine for one replica.
|
|
84
|
+
Scale the Swarm service and `N × pool` can exceed Postgres `max_connections`.
|
|
85
|
+
|
|
86
|
+
When both `store.sql` and `pgdog` are pinned (docker/prod default in templates),
|
|
87
|
+
`DATABASE_URL` points at PgDog on port `6432` — transaction pooling, wire-protocol
|
|
88
|
+
transparent to Bun.SQL / Drizzle. No app code changes.
|
|
89
|
+
|
|
90
|
+
**Why PgDog.** Transaction pooling fixes the connection math. Naive poolers can
|
|
91
|
+
leak session state (`SET`, RLS vars, `LISTEN`/`NOTIFY`) across clients; PgDog
|
|
92
|
+
re-applies those under transaction mode.
|
|
93
|
+
|
|
94
|
+
Read-replica routing (`BEGIN READ ONLY` → replica) is documented readiness —
|
|
95
|
+
not configured in the generated stack yet.
|
|
96
|
+
|
|
97
|
+
## Redis-protocol images (Redis · Valkey · Dragonfly)
|
|
98
|
+
|
|
99
|
+
`images["store.kv"]` defaults to Redis (most mature). Pin Valkey or Dragonfly
|
|
100
|
+
the same way — driver id stays `redis`, same `REDIS_URL`. Image table + one-line
|
|
101
|
+
licenses: [Store · KV](/docs/elements/store#kv).
|
|
102
|
+
|
|
103
|
+
## HEALTHCHECK (not two probes)
|
|
104
|
+
|
|
105
|
+
Swarm has one Docker `HEALTHCHECK` — no separate readiness/liveness.
|
|
106
|
+
|
|
107
|
+
| Endpoint | Role in OKE | Swarm |
|
|
108
|
+
| -------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------ |
|
|
109
|
+
| `GET /_/ready` | Kernel readiness (`booting` → `orphan_scan` → `ready`) | **Use this** — mesh + rolling updates wait; `start_period: 60s` covers orphan resume |
|
|
110
|
+
| `GET /health` | App liveness (create-oke) | External monitors only — not a second native probe |
|
|
111
|
+
|
|
112
|
+
`/_/ready` returns `200 { ready: true }` only after boot and the durable orphan
|
|
113
|
+
scan finish. While booting or scanning it returns
|
|
114
|
+
`503 { ready: false, reason: "booting" | "orphan_scan" }`.
|
|
115
|
+
|
|
116
|
+
**Consequence:** orphan scan delays “healthy” (and traffic) on purpose. After
|
|
117
|
+
`start_period`, failing `/_/ready` can mark the task unhealthy and hit
|
|
118
|
+
`restart_policy` — Swarm cannot drain traffic without also counting toward restart.
|
|
119
|
+
|
|
120
|
+
## Graceful shutdown
|
|
121
|
+
|
|
122
|
+
On `SIGTERM` / `SIGINT`, `installGracefulShutdown` (wired by `oke dev`’s app
|
|
123
|
+
runner; call it next to `createBunRuntime().serve` in custom entries):
|
|
124
|
+
|
|
125
|
+
1. Stops accepting new connections
|
|
126
|
+
2. Releases **Clock** cron leases and **Journal** run leases held by this instance
|
|
127
|
+
3. Closes element runtimes and exits
|
|
128
|
+
|
|
129
|
+
Swarm sends SIGTERM on service update and scale-down the same way.
|
|
130
|
+
|
|
131
|
+
Signal message leases have no proactive release today — survivors reclaim after
|
|
132
|
+
the visibility TTL (default 30s). `compose.prod.yml` sets `stop_grace_period: 30s`
|
|
133
|
+
(peer of K8s `terminationGracePeriodSeconds`) so release can finish.
|
|
134
|
+
|
|
135
|
+
## Reverse proxy
|
|
136
|
+
|
|
137
|
+
Swarm's routing mesh reaches tasks; it does **not** issue TLS certificates.
|
|
138
|
+
For HTTPS on the same files, opt in to `images.proxy`:
|
|
139
|
+
|
|
140
|
+
| Choice | Best for | How routing works |
|
|
141
|
+
| ---------------------------- | ------------------------------------------ | ------------------------------------------------------------------ |
|
|
142
|
+
| **Caddy** (`caddy:2-alpine`) | Single instance, simplest automatic HTTPS | Generated `Caddyfile` → `reverse_proxy app:6530` |
|
|
143
|
+
| **Traefik** (`traefik:v3.3`) | Horizontally scaled compose / Swarm stacks | Docker labels on `app`; replicas auto-discovered and load-balanced |
|
|
144
|
+
|
|
145
|
+
```typescript title="oke.config.ts"
|
|
146
|
+
images: {
|
|
147
|
+
// …
|
|
148
|
+
proxy: "caddy:2-alpine", // or "traefik:v3.3"
|
|
149
|
+
},
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
```bash title="docker/.env.docker"
|
|
153
|
+
OKE_PROXY_HOST=app.example.com
|
|
154
|
+
OKE_PROXY_ACME_EMAIL=admin@example.com
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Include `compose.proxy.yml` in the `-c` list. Edge publishes `80`/`443`.
|
|
158
|
+
Set `allowedHosts` to your public hostname ([Security](/docs/reference/security)).
|
|
159
|
+
|
|
160
|
+
Traefik never mounts raw `docker.sock` — only a filtered
|
|
161
|
+
`tecnativa/docker-socket-proxy` on the internal network. Full custom-domain,
|
|
162
|
+
Let's Encrypt, and socket-proxy rationale → [Reverse proxy](/docs/deployment/reverse-proxy).
|
|
163
|
+
|
|
164
|
+
## Known multi-instance limits
|
|
165
|
+
|
|
166
|
+
| Surface | Multi-instance today |
|
|
167
|
+
| ---------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
168
|
+
| Clock (`postgres` / shared `file`) | Shared leader election |
|
|
169
|
+
| Journal (`postgres` / shared `file`) | Shared durable runs + orphan resume |
|
|
170
|
+
| Gate rates (`drivers.store.kv: redis`) | Shared counters |
|
|
171
|
+
| Store SQL / KV redis / files `s3` | Shared |
|
|
172
|
+
| Store files `fs` | **Per-instance filesystem** — boot warns; use `s3` when scaled |
|
|
173
|
+
| Signal `redis` | Emit relays to Redis; **consume / `live` / `drain` are process-local outbox** until Streams consume ships |
|
|
174
|
+
| Channel suppression / consent / receipts | **Default stores are process-local** — opt-out on replica A is invisible to B until a durable driver ships |
|
|
175
|
+
|
|
176
|
+
## Troubleshooting
|
|
177
|
+
|
|
178
|
+
<Accordions>
|
|
179
|
+
<Accordion title="Stack deploy ignores my Dockerfile build">
|
|
180
|
+
|
|
181
|
+
Build and tag first (`docker build -t oke-<app>:latest …`). Swarm runs the
|
|
182
|
+
`image:` tag only — never compose `build:`.
|
|
183
|
+
|
|
184
|
+
</Accordion>
|
|
185
|
+
<Accordion title="New replica gets traffic before orphan scan finishes">
|
|
186
|
+
|
|
187
|
+
Confirm `compose.prod.yml` is in the `-c` list and the task health-checks
|
|
188
|
+
`/_/ready`. Without that HEALTHCHECK, Swarm treats “container started” as ready.
|
|
189
|
+
|
|
190
|
+
</Accordion>
|
|
191
|
+
<Accordion title="Rate limits look twice as high after scaling to 2">
|
|
192
|
+
|
|
193
|
+
`drivers.store.kv` must be `redis` with `REDIS_URL` set. A missing URL **fails
|
|
194
|
+
boot** (no silent memory fallback). Declaring `memory` for local is fine;
|
|
195
|
+
docker/prod should stay on redis.
|
|
196
|
+
|
|
197
|
+
</Accordion>
|
|
198
|
+
<Accordion title="Files uploaded on task A are missing on task B">
|
|
199
|
+
|
|
200
|
+
`drivers.store.files: "fs"` is single-host. Switch docker/prod to `s3`
|
|
201
|
+
(create-oke templates already do).
|
|
202
|
+
|
|
203
|
+
</Accordion>
|
|
204
|
+
<Accordion title="Multi-node workers cannot reach store-sql">
|
|
205
|
+
|
|
206
|
+
Generated `networks.oke.driver` is `bridge` (fine for single-node `swarm init`).
|
|
207
|
+
Multi-node: set `driver: overlay` in `compose.override.yml`.
|
|
208
|
+
|
|
209
|
+
</Accordion>
|
|
210
|
+
</Accordions>
|
|
211
|
+
|
|
212
|
+
## Learn more
|
|
213
|
+
|
|
214
|
+
- [Deployment](/docs/deployment) — choose compose, Swarm, or Kubernetes
|
|
215
|
+
- [Docker](/docs/deployment/docker) — plain compose without Swarm
|
|
216
|
+
- [Reverse proxy](/docs/deployment/reverse-proxy) — Caddy / Traefik in full
|
|
217
|
+
- [Kubernetes](/docs/deployment/kubernetes) — Deployment, probes, Ingress
|
|
218
|
+
- [CLI](/docs/reference/cli) — `oke docker --prod`, `oke start`
|
|
219
|
+
- [Store](/docs/elements/store) — PgDog and facets
|
|
220
|
+
- [Flow](/docs/elements/flow) — durable journal
|
|
221
|
+
|
|
222
|
+
## Next
|
|
223
|
+
|
|
224
|
+
<Cards>
|
|
225
|
+
<Card title="Deployment" href="/docs/deployment" />
|
|
226
|
+
<Card title="Docker" href="/docs/deployment/docker" />
|
|
227
|
+
<Card title="Kubernetes" href="/docs/deployment/kubernetes" />
|
|
228
|
+
</Cards>
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Docker
|
|
3
|
+
description: Deploy with plain docker compose — shared drivers, PgDog, readiness, graceful SIGTERM, and opt-in Caddy or Traefik when you need TLS without an orchestrator.
|
|
4
|
+
icon: Package
|
|
5
|
+
source: README.md
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Plain `docker compose` is the simplest production shape: single or few app
|
|
9
|
+
instances on one host (or a small set you manage yourself). Derive with
|
|
10
|
+
`oke docker --prod`; opt into a proxy only when you need HTTPS at the edge.
|
|
11
|
+
|
|
12
|
+
<Callout title="The one rule">
|
|
13
|
+
Treat every app container as replaceable. Put durable state in Postgres / Redis / S3, wait on `GET
|
|
14
|
+
/_/ready`, give SIGTERM time to release leases, and leave `images.proxy` unset until you need TLS
|
|
15
|
+
or `--scale app=N`.
|
|
16
|
+
</Callout>
|
|
17
|
+
|
|
18
|
+
## Quick start
|
|
19
|
+
|
|
20
|
+
<Steps>
|
|
21
|
+
|
|
22
|
+
<Step>
|
|
23
|
+
### Derive compose
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
oke docker --prod
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Writes `Dockerfile`, `compose.yml`, per-role layers, and `compose.prod.yml`.
|
|
30
|
+
Also emits **`compose.all.yml`** (same layers merged) — optional single file.
|
|
31
|
+
Without a proxy, the app still publishes port `6530`.
|
|
32
|
+
|
|
33
|
+
</Step>
|
|
34
|
+
|
|
35
|
+
<Step>
|
|
36
|
+
### Share backing services
|
|
37
|
+
|
|
38
|
+
Pin docker/prod drivers to shared stores (create-oke templates already do):
|
|
39
|
+
|
|
40
|
+
| Concern | Driver | Shared backend |
|
|
41
|
+
| ----------------------------- | ---------------------------- | ------------------------------------------------- |
|
|
42
|
+
| SQL / durable journal / clock | `postgres` | Postgres (optionally via PgDog) |
|
|
43
|
+
| Gate rate counters | `redis` (`drivers.store.kv`) | Redis / Valkey / Dragonfly (`images["store.kv"]`) |
|
|
44
|
+
| Files | `s3` | S3-compatible object store |
|
|
45
|
+
| Signal | see known limits below | — |
|
|
46
|
+
|
|
47
|
+
</Step>
|
|
48
|
+
|
|
49
|
+
<Step>
|
|
50
|
+
### Bring the stack up
|
|
51
|
+
|
|
52
|
+
Default — organized per-role files (`oke docker` prints the `-f` order):
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
cd docker
|
|
56
|
+
docker compose -f compose.yml -f compose.store.sql.yml \
|
|
57
|
+
-f compose.pgdog.yml -f compose.prod.yml up -d
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Pass every `-f` file listed. For TLS at the edge, see
|
|
61
|
+
[Reverse proxy](#reverse-proxy), then include `compose.proxy.yml`.
|
|
62
|
+
|
|
63
|
+
Single-file alternative: `docker compose -f compose.all.yml up -d`
|
|
64
|
+
(add `-f compose.override.yml` if you use one).
|
|
65
|
+
|
|
66
|
+
</Step>
|
|
67
|
+
|
|
68
|
+
</Steps>
|
|
69
|
+
|
|
70
|
+
## PgDog pooling
|
|
71
|
+
|
|
72
|
+
Bun.SQL defaults to **10** connections per process — fine for one instance.
|
|
73
|
+
Scale out and `N × pool` can exceed Postgres `max_connections`.
|
|
74
|
+
|
|
75
|
+
When both `store.sql` and `pgdog` are pinned (docker/prod default in templates),
|
|
76
|
+
`DATABASE_URL` points at PgDog on port `6432` — transaction pooling, wire-protocol
|
|
77
|
+
transparent to Bun.SQL / Drizzle. No app code changes.
|
|
78
|
+
|
|
79
|
+
**Why PgDog.** Transaction pooling fixes the connection math. Naive poolers can
|
|
80
|
+
leak session state (`SET`, RLS vars, `LISTEN`/`NOTIFY`) across clients; PgDog
|
|
81
|
+
re-applies those under transaction mode.
|
|
82
|
+
|
|
83
|
+
Read-replica routing (`BEGIN READ ONLY` → replica) is documented readiness —
|
|
84
|
+
not configured in the generated stack yet.
|
|
85
|
+
|
|
86
|
+
## Redis-protocol images (Redis · Valkey · Dragonfly)
|
|
87
|
+
|
|
88
|
+
`images["store.kv"]` defaults to Redis (most mature). Pin Valkey or Dragonfly
|
|
89
|
+
the same way — driver id stays `redis`, same `REDIS_URL`. Image table + one-line
|
|
90
|
+
licenses: [Store · KV](/docs/elements/store#kv).
|
|
91
|
+
|
|
92
|
+
## Readiness and liveness
|
|
93
|
+
|
|
94
|
+
| Endpoint | Role | On plain compose |
|
|
95
|
+
| -------------- | ----------------------------------------------------------- | ---------------------------------------------------------- |
|
|
96
|
+
| `GET /_/ready` | Kernel readiness (`booting` → `orphan_scan` → `ready`) | `compose.prod.yml` HEALTHCHECK; load balancers should wait |
|
|
97
|
+
| `GET /health` | App liveness (create-oke ships one) — cheap “process alive” | External monitors only — not orphan-scan aware |
|
|
98
|
+
|
|
99
|
+
`/_/ready` returns `200 { ready: true }` only after boot and the durable orphan
|
|
100
|
+
scan finish. While booting or scanning it returns
|
|
101
|
+
`503 { ready: false, reason: "booting" | "orphan_scan" }`.
|
|
102
|
+
|
|
103
|
+
**Consequence:** orphan scan delays “healthy” (and traffic) on purpose so a
|
|
104
|
+
new replica is not hit mid-takeover.
|
|
105
|
+
|
|
106
|
+
## Graceful shutdown
|
|
107
|
+
|
|
108
|
+
On `SIGTERM` / `SIGINT`, `installGracefulShutdown` (wired by `oke dev`’s app
|
|
109
|
+
runner; call it next to `createBunRuntime().serve` in custom entries):
|
|
110
|
+
|
|
111
|
+
1. Stops accepting new connections
|
|
112
|
+
2. Releases **Clock** cron leases and **Journal** run leases held by this instance
|
|
113
|
+
3. Closes element runtimes and exits
|
|
114
|
+
|
|
115
|
+
Signal message leases have no proactive release today — survivors reclaim after
|
|
116
|
+
the visibility TTL (default 30s). `compose.prod.yml` sets `stop_grace_period: 30s`
|
|
117
|
+
so the release path can finish (≥ typical Clock / Journal / Signal lease TTL).
|
|
118
|
+
|
|
119
|
+
## Reverse proxy
|
|
120
|
+
|
|
121
|
+
Omit `images.proxy` until you need HTTPS or horizontal scale without fighting a
|
|
122
|
+
host port bind. Then pick one:
|
|
123
|
+
|
|
124
|
+
| Choice | Best for | How routing works |
|
|
125
|
+
| ---------------------------- | ----------------------------------------- | ------------------------------------------------------------------ |
|
|
126
|
+
| **Caddy** (`caddy:2-alpine`) | Single instance, simplest automatic HTTPS | Generated `Caddyfile` → `reverse_proxy app:6530` |
|
|
127
|
+
| **Traefik** (`traefik:v3.3`) | `docker compose up --scale app=N` | Docker labels on `app`; replicas auto-discovered and load-balanced |
|
|
128
|
+
|
|
129
|
+
```typescript title="oke.config.ts"
|
|
130
|
+
images: {
|
|
131
|
+
// …
|
|
132
|
+
proxy: "caddy:2-alpine", // or "traefik:v3.3"
|
|
133
|
+
},
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
```bash title="docker/.env.docker"
|
|
137
|
+
OKE_PROXY_HOST=app.example.com
|
|
138
|
+
OKE_PROXY_ACME_EMAIL=admin@example.com
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Re-run `oke docker --prod`. Edge publishes `80`/`443`; `app` stays internal.
|
|
142
|
+
Set `allowedHosts` to your public hostname ([Security](/docs/reference/security)).
|
|
143
|
+
|
|
144
|
+
Traefik never mounts raw `docker.sock` — only a filtered
|
|
145
|
+
`tecnativa/docker-socket-proxy` on the internal network. Full custom-domain,
|
|
146
|
+
Let's Encrypt, and socket-proxy rationale → [Reverse proxy](/docs/deployment/reverse-proxy).
|
|
147
|
+
|
|
148
|
+
## Known multi-instance limits
|
|
149
|
+
|
|
150
|
+
| Surface | Multi-instance today |
|
|
151
|
+
| ---------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
152
|
+
| Clock (`postgres` / shared `file`) | Shared leader election |
|
|
153
|
+
| Journal (`postgres` / shared `file`) | Shared durable runs + orphan resume |
|
|
154
|
+
| Gate rates (`drivers.store.kv: redis`) | Shared counters |
|
|
155
|
+
| Store SQL / KV redis / files `s3` | Shared |
|
|
156
|
+
| Store files `fs` | **Per-instance filesystem** — boot warns; use `s3` when scaled |
|
|
157
|
+
| Signal `redis` | Emit relays to Redis; **consume / `live` / `drain` are process-local outbox** until Streams consume ships |
|
|
158
|
+
| Channel suppression / consent / receipts | **Default stores are process-local** — opt-out on replica A is invisible to B until a durable driver ships |
|
|
159
|
+
|
|
160
|
+
## Troubleshooting
|
|
161
|
+
|
|
162
|
+
<Accordions>
|
|
163
|
+
<Accordion title="compose up --scale app=N fails with port already allocated">
|
|
164
|
+
|
|
165
|
+
You still have `6530:6530` on `app`. Confirm `images.proxy` is set and
|
|
166
|
+
`compose.proxy.yml` is in the `-f` list so derivation omitted the host bind.
|
|
167
|
+
|
|
168
|
+
</Accordion>
|
|
169
|
+
<Accordion title="New replica gets traffic before orphan scan finishes">
|
|
170
|
+
|
|
171
|
+
Confirm `compose.prod.yml` is in the `-f` list and the healthcheck hits
|
|
172
|
+
`/_/ready`. Without it, compose treats “container started” as ready.
|
|
173
|
+
|
|
174
|
+
</Accordion>
|
|
175
|
+
<Accordion title="Rate limits look twice as high after scaling to 2">
|
|
176
|
+
|
|
177
|
+
`drivers.store.kv` must be `redis` with `REDIS_URL` set. A missing URL **fails
|
|
178
|
+
boot** (no silent memory fallback). Declaring `memory` for local is fine;
|
|
179
|
+
docker/prod should stay on redis.
|
|
180
|
+
|
|
181
|
+
</Accordion>
|
|
182
|
+
<Accordion title="Files uploaded on instance A are missing on B">
|
|
183
|
+
|
|
184
|
+
`drivers.store.files: "fs"` is single-host. Switch docker/prod to `s3`
|
|
185
|
+
(create-oke templates already do).
|
|
186
|
+
|
|
187
|
+
</Accordion>
|
|
188
|
+
<Accordion title="Host header rejected (403)">
|
|
189
|
+
|
|
190
|
+
Serve validates `Host`. Add your public hostname to `allowedHosts`
|
|
191
|
+
([Security](/docs/reference/security)).
|
|
192
|
+
|
|
193
|
+
</Accordion>
|
|
194
|
+
</Accordions>
|
|
195
|
+
|
|
196
|
+
## Learn more
|
|
197
|
+
|
|
198
|
+
- [Deployment](/docs/deployment) — choose compose, Swarm, or Kubernetes
|
|
199
|
+
- [Reverse proxy](/docs/deployment/reverse-proxy) — Caddy / Traefik in full
|
|
200
|
+
- [Docker Swarm](/docs/deployment/docker-swarm) — `docker stack deploy` on the same files
|
|
201
|
+
- [Kubernetes](/docs/deployment/kubernetes) — Deployment, probes, Ingress
|
|
202
|
+
- [CLI](/docs/reference/cli) — `oke docker`, `oke docker --prod`
|
|
203
|
+
- [Store](/docs/elements/store) — PgDog and facets
|
|
204
|
+
- [Security](/docs/reference/security) — `allowedHosts`
|
|
205
|
+
|
|
206
|
+
## Next
|
|
207
|
+
|
|
208
|
+
<Cards>
|
|
209
|
+
<Card title="Deployment" href="/docs/deployment" />
|
|
210
|
+
<Card title="Reverse proxy" href="/docs/deployment/reverse-proxy" />
|
|
211
|
+
<Card title="Docker Swarm" href="/docs/deployment/docker-swarm" />
|
|
212
|
+
</Cards>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Deployment
|
|
3
|
+
description: Choose plain docker compose, Docker Swarm, or Kubernetes — then follow that path's complete page for PgDog, probes, graceful shutdown, and TLS.
|
|
4
|
+
icon: Server
|
|
5
|
+
source: README.md
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
OKE apps are process-stateless when shared drivers back Store, Clock, Journal,
|
|
9
|
+
and Gate. Pick an orchestrator (or none) first; each path page below is
|
|
10
|
+
self-contained after you choose.
|
|
11
|
+
|
|
12
|
+
<Callout title="The one rule">
|
|
13
|
+
Decide the deploy shape here, then open **one** path page and follow it end to end. Do not mix
|
|
14
|
+
recipes across pages — Docker proxy compose ≠ Kubernetes Ingress.
|
|
15
|
+
</Callout>
|
|
16
|
+
|
|
17
|
+
## Which path?
|
|
18
|
+
|
|
19
|
+
| Path | Best for | Trade-off |
|
|
20
|
+
| --------------------------------------------- | -------------------------------------------- | ---------------------------------------------------- |
|
|
21
|
+
| [Docker](/docs/deployment/docker) (compose) | Simplest — single or few instances | You run scale / restart / TLS yourself (or opt in) |
|
|
22
|
+
| [Docker Swarm](/docs/deployment/docker-swarm) | Built-in orchestration, lower learning curve | Smaller ecosystem than Kubernetes; mesh ≠ certs |
|
|
23
|
+
| [Kubernetes](/docs/deployment/kubernetes) | Full orchestration, largest ecosystem | Highest operational surface (Ingress, probes, RBAC…) |
|
|
24
|
+
|
|
25
|
+
**Consequence:** start with plain compose until you need Swarm's service mesh /
|
|
26
|
+
rolling updates or Kubernetes' cluster ecosystem. Same multi-instance contract
|
|
27
|
+
on every path — shared Postgres/Redis/S3, not sticky pod identity.
|
|
28
|
+
|
|
29
|
+
## Capabilities (every path)
|
|
30
|
+
|
|
31
|
+
These already ship. Each path page explains them in full in that page's words:
|
|
32
|
+
|
|
33
|
+
| Capability | What it guarantees | Deep dive |
|
|
34
|
+
| --------------------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------ |
|
|
35
|
+
| **PgDog pooling** | docker/prod SQL goes through transaction pooler (`DATABASE_URL` → `:6432`) when pinned | Path pages · [Store](/docs/elements/store) |
|
|
36
|
+
| **Graceful SIGTERM** | `installGracefulShutdown` releases Clock + Journal leases, then drains | Path pages |
|
|
37
|
+
| **Readiness vs liveness** | `GET /_/ready` (kernel) vs app `GET /health` | Path pages |
|
|
38
|
+
| **Horizontal proof** | Two-process integration test against live Postgres + Redis | Changelog / CI (`OKE_TEST_POSTGRES_URL` + Redis) |
|
|
39
|
+
| **Reverse proxy (compose/Swarm)** | Opt-in Caddy or Traefik (`images.proxy`) | [Reverse proxy](/docs/deployment/reverse-proxy) |
|
|
40
|
+
| **Ingress (Kubernetes)** | Cluster-native Ingress / Gateway API | [Kubernetes](/docs/deployment/kubernetes) |
|
|
41
|
+
|
|
42
|
+
Honesty notes (Signal consume / Channel suppression still process-local) live
|
|
43
|
+
on each path page — not papered over here.
|
|
44
|
+
|
|
45
|
+
## Pick a path
|
|
46
|
+
|
|
47
|
+
<Cards>
|
|
48
|
+
<Card
|
|
49
|
+
title="Docker"
|
|
50
|
+
description="Plain compose — simplest single or few-instance deploy."
|
|
51
|
+
href="/docs/deployment/docker"
|
|
52
|
+
/>
|
|
53
|
+
<Card
|
|
54
|
+
title="Docker Swarm"
|
|
55
|
+
description="stack deploy on the generated compose layers."
|
|
56
|
+
href="/docs/deployment/docker-swarm"
|
|
57
|
+
/>
|
|
58
|
+
<Card
|
|
59
|
+
title="Kubernetes"
|
|
60
|
+
description="Deployment, probes, shared drivers, Ingress."
|
|
61
|
+
href="/docs/deployment/kubernetes"
|
|
62
|
+
/>
|
|
63
|
+
<Card
|
|
64
|
+
title="Reverse proxy"
|
|
65
|
+
description="Caddy vs Traefik — TLS, domains, socket-proxy why."
|
|
66
|
+
href="/docs/deployment/reverse-proxy"
|
|
67
|
+
/>
|
|
68
|
+
</Cards>
|
|
69
|
+
|
|
70
|
+
## Learn more
|
|
71
|
+
|
|
72
|
+
- [Installation](/docs/get-started/installation) — scaffold and `oke dev --docker`
|
|
73
|
+
- [CLI](/docs/reference/cli) — `oke docker --prod`, `oke start`
|
|
74
|
+
- [Configuration](/docs/reference/configuration) — `images` and drivers
|
|
75
|
+
- [Store](/docs/elements/store) — PgDog and facets
|
|
76
|
+
|
|
77
|
+
## Next
|
|
78
|
+
|
|
79
|
+
<Cards>
|
|
80
|
+
<Card title="Docker" href="/docs/deployment/docker" />
|
|
81
|
+
<Card title="Docker Swarm" href="/docs/deployment/docker-swarm" />
|
|
82
|
+
<Card title="Kubernetes" href="/docs/deployment/kubernetes" />
|
|
83
|
+
</Cards>
|