Skip to content

Commit 7bbbf09

Browse files
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

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ kubectl apply -f config/samples/coder_v1alpha1_codercontrolplane.yaml
3434
kubectl get codercontrolplanes -A
3535
```
3636

37+
## Examples
38+
39+
- [`examples/cloudnativepg/`](examples/cloudnativepg/) - Deploy a `CoderControlPlane` with a CloudNativePG-managed PostgreSQL backend.
40+
3741
## KIND development cluster (for k9s demos)
3842

3943
Bootstrap a KIND cluster and install CRDs/RBAC (**this also switches your current kubectl context**):
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
apiVersion: v1
2+
kind: Namespace
3+
metadata:
4+
name: coder
5+
labels:
6+
app.kubernetes.io/part-of: coder-k8s-example

examples/cloudnativepg/README.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# CloudNativePG-backed CoderControlPlane example
2+
3+
This example deploys a `CoderControlPlane` backed by a PostgreSQL cluster managed by [CloudNativePG](https://cloudnative-pg.io/).
4+
5+
It creates:
6+
7+
- a `coder` namespace
8+
- a CloudNativePG `Cluster` named `coder-db`
9+
- a `CoderControlPlane` named `coder` wired to the CloudNativePG app Secret (`coder-db-app`)
10+
11+
## Prerequisites
12+
13+
- A Kubernetes cluster
14+
- `kubectl` configured for that cluster
15+
- `helm`
16+
17+
## 1. Install the CloudNativePG operator
18+
19+
```bash
20+
helm repo add cnpg https://cloudnative-pg.github.io/charts
21+
helm repo update
22+
helm upgrade --install cnpg cnpg/cloudnative-pg \
23+
--namespace cnpg-system \
24+
--create-namespace
25+
```
26+
27+
## 2. Install the coder-k8s controller
28+
29+
Follow [Deploy the controller (in-cluster)](../../docs/how-to/deploy-controller.md), or run:
30+
31+
```bash
32+
kubectl create namespace coder-system
33+
kubectl apply -f config/crd/bases/
34+
kubectl apply -f deploy/rbac.yaml
35+
kubectl apply -f deploy/controller-deployment.yaml
36+
kubectl rollout status deployment/coder-k8s-controller -n coder-system
37+
```
38+
39+
## 3. Deploy this example
40+
41+
```bash
42+
kubectl apply -f examples/cloudnativepg/
43+
```
44+
45+
The namespace manifest is prefixed (`00-namespace.yaml`) so `kubectl apply -f` creates `coder` before namespaced resources.
46+
47+
Wait for PostgreSQL and verify the generated Secret:
48+
49+
```bash
50+
kubectl -n coder wait --for=condition=Ready cluster/coder-db --timeout=10m
51+
kubectl -n coder get secret coder-db-app
52+
```
53+
54+
Wait for the `CoderControlPlane` deployment:
55+
56+
```bash
57+
kubectl -n coder rollout status deployment/coder --timeout=10m
58+
kubectl -n coder get codercontrolplane coder
59+
```
60+
61+
## 4. Access Coder
62+
63+
In one terminal:
64+
65+
```bash
66+
kubectl -n coder port-forward svc/coder 8080:80
67+
```
68+
69+
Then open:
70+
71+
- <http://localhost:8080/setup>
72+
73+
Use the setup flow to create the first admin user.
74+
75+
## Important limitation of this quickstart
76+
77+
This example sets:
78+
79+
- `CODER_ACCESS_URL=http://localhost:8080`
80+
81+
That value is convenient for UI smoke tests through `kubectl port-forward`, but it is not suitable for end-to-end workspace connectivity because in-cluster components cannot reach your local `localhost`.
82+
83+
For a full setup, expose Coder with an ingress/load balancer and set `CODER_ACCESS_URL` to a real external URL (for example, `https://coder.example.com`).
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
apiVersion: postgresql.cnpg.io/v1
2+
kind: Cluster
3+
metadata:
4+
name: coder-db
5+
namespace: coder
6+
spec:
7+
instances: 1
8+
storage:
9+
size: 5Gi
10+
bootstrap:
11+
initdb:
12+
database: coder
13+
owner: coder
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
apiVersion: coder.com/v1alpha1
2+
kind: CoderControlPlane
3+
metadata:
4+
name: coder
5+
namespace: coder
6+
spec:
7+
replicas: 1
8+
service:
9+
type: ClusterIP
10+
port: 80
11+
extraEnv:
12+
- name: CODER_PG_CONNECTION_URL
13+
valueFrom:
14+
secretKeyRef:
15+
name: coder-db-app
16+
key: uri
17+
- name: CODER_ACCESS_URL
18+
value: http://localhost:8080

0 commit comments

Comments
 (0)