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.
Files changed (89) hide show
  1. package/package.json +3 -2
  2. package/site/content/docs/deployment/docker-swarm.mdx +228 -0
  3. package/site/content/docs/deployment/docker.mdx +212 -0
  4. package/site/content/docs/deployment/index.mdx +83 -0
  5. package/site/content/docs/deployment/kubernetes.mdx +176 -0
  6. package/site/content/docs/deployment/meta.json +5 -0
  7. package/site/content/docs/deployment/reverse-proxy.mdx +216 -0
  8. package/site/content/docs/elements/channel.mdx +10 -5
  9. package/site/content/docs/elements/signal.mdx +10 -8
  10. package/site/content/docs/elements/store.mdx +34 -0
  11. package/site/content/docs/get-started/index.mdx +5 -0
  12. package/site/content/docs/index.mdx +5 -0
  13. package/site/content/docs/meta.json +10 -1
  14. package/site/content/docs/plugins/index.mdx +1 -2
  15. package/site/content/docs/plugins/magic-link.mdx +2 -2
  16. package/site/content/docs/plugins/meta.json +1 -2
  17. package/site/content/docs/plugins/otp.mdx +214 -0
  18. package/site/content/docs/plugins/two-factor.mdx +2 -2
  19. package/site/content/docs/reference/cli.md +3 -2
  20. package/site/content/docs/reference/configuration.mdx +16 -0
  21. package/site/content/docs/reference/environment-variables.mdx +9 -8
  22. package/site/content/docs/reference/plugins.mdx +1 -1
  23. package/src/auth/auth.test.ts +36 -0
  24. package/src/auth/bindings.ts +3 -12
  25. package/src/auth/identity.ts +33 -0
  26. package/src/auth/index.ts +5 -0
  27. package/src/auth/otp-capability.ts +119 -0
  28. package/src/auth/otp-seal.test.ts +61 -0
  29. package/src/auth/otp-seal.ts +84 -0
  30. package/src/auth/schema.ts +3 -0
  31. package/src/auth/sessions.ts +26 -27
  32. package/src/auth/tables.ts +4 -0
  33. package/src/auth/verification.ts +61 -1
  34. package/src/cli/dev-app-runner.ts +4 -0
  35. package/src/cli/docker.ts +4 -1
  36. package/src/cli/load-config.images.test.ts +4 -0
  37. package/src/cli/load-config.ts +3 -0
  38. package/src/cli/registry.ts +1 -1
  39. package/src/console/server/operator-db.ts +34 -9
  40. package/src/docker/compose.ts +162 -6
  41. package/src/docker/derive.ts +60 -3
  42. package/src/docker/docker.test.ts +374 -1
  43. package/src/docker/helpers.ts +2 -0
  44. package/src/docker/index.ts +11 -0
  45. package/src/docker/recipes/caddy.ts +51 -0
  46. package/src/docker/recipes/dragonfly.ts +31 -0
  47. package/src/docker/recipes/index.ts +25 -2
  48. package/src/docker/recipes/pgdog.ts +84 -0
  49. package/src/docker/recipes/redis.ts +6 -3
  50. package/src/docker/recipes/traefik.ts +83 -0
  51. package/src/docker/recipes/valkey.ts +30 -0
  52. package/src/docker/stack-id.ts +5 -0
  53. package/src/docker/types.ts +18 -0
  54. package/src/drivers/channel-taqnyat-whatsapp.ts +94 -0
  55. package/src/drivers/channel-types.ts +1 -0
  56. package/src/elements/channel/otp-delivery.test.ts +76 -0
  57. package/src/elements/channel/otp-delivery.ts +291 -0
  58. package/src/elements/channel/runtime.ts +152 -114
  59. package/src/elements/channel.ts +12 -2
  60. package/src/index.ts +3 -0
  61. package/src/kernel/app.ts +60 -4
  62. package/src/kernel/boot-bind/channel.ts +51 -0
  63. package/src/kernel/boot-bind/gate.ts +14 -19
  64. package/src/kernel/boot-bind/honor-config.test.ts +18 -0
  65. package/src/kernel/boot-bind/signal.ts +20 -0
  66. package/src/kernel/boot-bind/store.test.ts +82 -0
  67. package/src/kernel/boot-bind/store.ts +22 -0
  68. package/src/kernel/boot.test.ts +5 -5
  69. package/src/kernel/fx.test.ts +3 -0
  70. package/src/kernel/fx.ts +49 -0
  71. package/src/kernel/graceful-shutdown.test.ts +76 -0
  72. package/src/kernel/graceful-shutdown.ts +106 -0
  73. package/src/kernel/horizontal-child.ts +257 -0
  74. package/src/kernel/horizontal.integration.test.ts +229 -0
  75. package/src/kernel/index.ts +8 -0
  76. package/src/kernel/ready.test.ts +76 -0
  77. package/src/plugins/auth-delivery.mailpit.integration.test.ts +5 -5
  78. package/src/plugins/auth-methods.security.test.ts +19 -29
  79. package/src/plugins/auth-methods.test.ts +7 -6
  80. package/src/plugins/index.ts +12 -8
  81. package/src/plugins/magic-link.ts +1 -23
  82. package/src/plugins/otp.test.ts +238 -0
  83. package/src/plugins/otp.ts +572 -0
  84. package/src/plugins/taqnyat.live.test.ts +5 -7
  85. package/src/release/official-plugins.ts +1 -2
  86. package/site/content/docs/plugins/email-otp.mdx +0 -117
  87. package/site/content/docs/plugins/phone-number.mdx +0 -172
  88. package/src/plugins/email-otp.ts +0 -214
  89. 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.8.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.0"
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>