Commit 7bbbf09
authored
🤖 feat: add CloudNativePG-backed CoderControlPlane example (#38)
Summary
This PR adds a runnable multi-resource example that deploys a
`CoderControlPlane` backed by CloudNativePG-managed PostgreSQL.
Background
The operator already supports database wiring through `spec.extraEnv`,
so we can provide a realistic Coder + Postgres setup without CRD or
controller changes. The goal is an easier end-to-end demo manifest set
and walkthrough.
Implementation
- Added `examples/cloudnativepg/namespace.yaml` to create a dedicated
`coder` namespace.
- Added `examples/cloudnativepg/cnpg-cluster.yaml` with a
single-instance PVC-backed CNPG cluster (`coder-db`) that bootstraps
database/user `coder`.
- Added `examples/cloudnativepg/codercontrolplane.yaml` wiring:
- `CODER_PG_CONNECTION_URL` from CNPG Secret `coder-db-app` key `uri`
- `CODER_ACCESS_URL=http://localhost:8080` for local `kubectl
port-forward` smoke testing
- Added `examples/cloudnativepg/README.md` with prerequisites, install
steps (CNPG + controller), deploy steps, readiness checks, and
first-user setup flow.
- Added a root `README.md` examples link to discover the new
CloudNativePG example.
Validation
- `make verify-vendor`
- `make test`
- `make build`
- `make lint` *(fails in this workspace due pre-existing formatting
drift under `tmpfork/coder/**`, unrelated to this change)*
Risks
Low risk. This change is documentation/manifests only and does not
modify controller runtime behavior or APIs. The main user caveat is that
`CODER_ACCESS_URL=http://localhost:8080` is intentionally scoped to
local port-forward smoke tests.
---
<details>
<summary>📋 Implementation Plan</summary>
# Plan: Example CoderControlPlane deployment backed by CloudNativePG
## Context / Why
We want a **realistic, easier demo** of the `coder-k8s` operator that
includes a managed PostgreSQL database.
CloudNativePG (CNPG) is a widely used Postgres operator that:
- provisions a Postgres cluster backed by PVCs
- creates an application `Secret` containing a ready-to-use connection
string (`uri`)
The `CoderControlPlane` CR already supports arbitrary env injection via
`spec.extraEnv`, so we can wire CNPG → Coder with **no CRD changes**.
## Evidence (what we verified)
- `internal/controller/codercontrolplane_controller.go`
- Reconciles a `Deployment` and `Service` named exactly after the
`CoderControlPlane` (`metadata.name`).
- Runs `coder server --http-address=0.0.0.0:3000` and exposes it via a
`Service` port (default `80`) → target port `3000`.
- Passes `spec.extraEnv` directly to the Coder container.
- `api/v1alpha1/codercontrolplane_types.go`: `spec.extraEnv` and
`spec.service.{type,port,annotations}` exist; no dedicated DB fields.
- `docs/how-to/deploy-controller.md`: canonical install steps for CRDs +
RBAC + controller deployment (in namespace `coder-system`).
- `./tmpfork/coder/helm/coder/README.md` + `values.yaml` (Coder
upstream):
- DB is configured via `CODER_PG_CONNECTION_URL`.
- `CODER_ACCESS_URL` is expected in typical setups; Helm chart may
default it, but our operator does not.
- CNPG research (sub-agent): CNPG `Cluster` creates a
`<clusterName>-app` Secret that includes key `uri` (Postgres URL) and a
`<clusterName>-rw` Service.
- Coder “first user” + access URL behavior (sub-agent):
- Fresh deployments serve a `/setup` flow to create the first admin
user.
- No `CODER_DISABLE_AUTH` exists.
- If `CODER_ACCESS_URL` is unset, Coder may attempt to create a
temporary public tunnel (`*.try.coder.app`).
## Implementation plan
### 1) Add a new example directory
Create a new example folder that is multi-resource and runnable via
`kubectl apply`:
**Proposed location**: `examples/cloudnativepg/`
Files:
- `examples/cloudnativepg/README.md`
- `examples/cloudnativepg/namespace.yaml`
- `examples/cloudnativepg/cnpg-cluster.yaml`
- `examples/cloudnativepg/codercontrolplane.yaml`
- (optional) `examples/cloudnativepg/kustomization.yaml` to enable
`kubectl apply -k`.
Rationale: `config/samples/` is good for single-CR “shape” examples, but
this demo needs multiple components (namespace + CNPG cluster +
CoderControlPlane).
### 2) CNPG: define a PVC-backed Postgres cluster
Add `cnpg-cluster.yaml` using CNPG’s `Cluster` CRD.
Minimal single-instance dev/demo setup:
```yaml
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: coder-db
namespace: coder
spec:
instances: 1
storage:
size: 5Gi
bootstrap:
initdb:
database: coder
owner: coder
```
Notes to include in README:
- CNPG will create:
- Secret: `coder-db-app` (contains `uri`, `username`, `password`,
`database`, ...)
- Service: `coder-db-rw` (primary read-write endpoint)
- For a more “realistic” HA demo, users can increase `spec.instances` to
`3`.
### 3) coder-k8s: wire the CNPG Secret into `CoderControlPlane`
Add `codercontrolplane.yaml` that references the CNPG-generated Secret.
```yaml
apiVersion: coder.com/v1alpha1
kind: CoderControlPlane
metadata:
name: coder
namespace: coder
spec:
# Optional: omit `image` to use the operator default (ghcr.io/coder/coder:latest)
replicas: 1
service:
type: ClusterIP
port: 80
extraEnv:
- name: CODER_PG_CONNECTION_URL
valueFrom:
secretKeyRef:
name: coder-db-app
key: uri
# See README section on port-forward vs. real ingress/LB URLs.
- name: CODER_ACCESS_URL
value: "http://localhost:8080"
```
<details>
<summary>Why <code>CODER_ACCESS_URL=http://localhost:8080</code> is
tricky</summary>
- It makes the **browser** experience easy when you run:
- `kubectl -n coder port-forward svc/coder 8080:80`
- But any Coder components **running inside the cluster** (e.g.,
workspace agents) cannot reach `localhost:8080` because `localhost`
resolves to themselves.
- Coder may also log warnings because it periodically checks
`${CODER_ACCESS_URL}/healthz` from inside the pod; with `localhost` +
port-forward, that check cannot succeed in-cluster.
So this example should explicitly frame `localhost` as a **UI smoke
test**.
If we want an end-to-end workspace demo, the README should describe the
alternative:
- expose Coder via Ingress / LoadBalancer / NodePort and set
`CODER_ACCESS_URL` to that externally reachable address, **or**
- leave `CODER_ACCESS_URL` unset and rely on Coder’s tunnel feature
(requires outbound internet).
</details>
### 4) README: end-to-end walkthrough
The README should include:
1. **Prereqs**
- Kubernetes cluster
- `kubectl`
- `helm` (for CNPG install)
2. **Install CloudNativePG operator**
- Recommended (Helm):
```bash
helm repo add cnpg https://cloudnative-pg.github.io/charts
helm upgrade --install cnpg cnpg/cloudnative-pg --namespace cnpg-system
--create-namespace
```
- Alternative (plain manifest): apply a pinned CNPG release YAML from
the CNPG GitHub releases.
3. **Install the coder-k8s controller**
- Follow `docs/how-to/deploy-controller.md` (summarized):
```bash
kubectl create namespace coder-system
kubectl apply -f config/crd/bases/
kubectl apply -f deploy/rbac.yaml
kubectl apply -f deploy/controller-deployment.yaml
kubectl rollout status deployment/coder-k8s-controller -n coder-system
```
- (Optional) Deploy the aggregated API server separately; not required
for `CoderControlPlane`.
4. **Deploy the example**
- `kubectl apply -f examples/cloudnativepg/` (or `kubectl apply -k ...`
if we add kustomize)
- Wait for Postgres:
- `kubectl -n coder wait --for=condition=Ready cluster/coder-db
--timeout=...`
- Confirm the Secret exists:
- `kubectl -n coder get secret coder-db-app`
5. **Port-forward to test the UI**
- `kubectl -n coder port-forward svc/coder 8080:80`
- Open `http://localhost:8080/setup` to create the first admin user.
6. **Callouts / limitations**
- Port-forward mode is for UI validation; workspaces need a real
reachable `CODER_ACCESS_URL`.
### 5) (Optional) Provide two kustomize overlays
If we want a smoother UX without editing YAML, add overlays:
- `examples/cloudnativepg/overlays/port-forward/` →
`CODER_ACCESS_URL=http://localhost:8080`
- `examples/cloudnativepg/overlays/ingress-or-lb/` → placeholder
`CODER_ACCESS_URL=https://coder.example.com` and `service.type:
LoadBalancer` (or Ingress notes)
This is optional; we can start with a single manifest + README
instructions and add overlays later if desired.
## Acceptance criteria / validation
When implemented, we should be able to:
- Apply manifests (client-side dry-run succeeds).
- Observe CNPG cluster becomes `Ready` and creates `coder-db-app` with
key `uri`.
- Observe `CoderControlPlane` becomes Ready (deployment has ready
replicas).
- Port-forward and reach the Coder `/setup` page.
---
## Non-goals
- Building a fully production-ready Coder + CNPG configuration (backups,
HA tuning, monitoring).
- End-to-end workspace agent connectivity while using `localhost` as the
access URL (requires a real external URL).
</details>
---
_Generated with [`mux`](https://github.com/coder/mux) • Model:
`openai:gpt-5.3-codex` • Thinking: `xhigh`_
---
_Generated with `mux` • Model: `openai:gpt-5.3-codex` • Thinking:
`xhigh` • Cost: `$1.12`_
<!-- mux-attribution: model=openai:gpt-5.3-codex thinking=xhigh
costs=1.12 -->1 parent 5b3c81f commit 7bbbf09
5 files changed
Lines changed: 124 additions & 0 deletions
File tree
- examples/cloudnativepg
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
37 | 41 | | |
38 | 42 | | |
39 | 43 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
0 commit comments