Local development
The dev/ directory provides a Makefile for local development with
kind, mirroring the
software-pack-template dev flow. It
creates a kind cluster with the full Nebari infrastructure stack — Envoy Gateway,
cert-manager, Keycloak, and the nebari-operator (pinned to v0.1.0-alpha.19) — then builds
and deploys every pack component: the apps-operator, the apps-api, the
apps-ui, and the apps-mcp server.
Prerequisites
Section titled “Prerequisites”docker, kind, helm, kubectl, git.
Quick start
Section titled “Quick start”cd dev
# Everything: kind cluster + Nebari stack + operator + api + ui + example app.# The first run takes ~5-10 minutes; later runs reuse the cluster.make up
open http://apps.nebari.test # the UIopen http://docs-site.apps.nebari.test # the example app# MCP endpoint for coding agents: http://apps.nebari.test/mcpmake up does, in order:
check-dns— starts the local wildcard-DNS container and verifies*.apps.nebari.testresolves to127.0.0.1; on macOS, offers to install the one-time/etc/resolverfile if it is missing (see below).cluster— creates the kind clusternebari-apps-dev(with host ports 80/443 mapped into the node — see below), then uses the nebari-operator’s dev scripts to install Envoy Gateway, cert-manager, Keycloak (realmnebari, loginadmin/nebari-admin), and the nebari-operator itself. Pins the gateway Service to fixed NodePorts and creates theappsnamespace labelednebari.dev/managed=true.images— buildsapps-operator:dev,apps-api:dev,apps-ui:dev, andapps-mcp:devand loads them into the kind cluster.deploy— installs the chart withclusterDomain=nebari.testandtls.enabled=false(plain HTTP — no certificate warnings locally).- Applies the inline example App and waits for
Running.
How host access works
Section titled “How host access works”No port-forwarding and no /etc/hosts edits — every app is reachable the moment its route
reconciles:
- DNS — a tiny CoreDNS container (
nebari-dev-dns, started bymake up) answers every*.nebari.testlookup with127.0.0.1on127.0.0.1:53535. A one-time/etc/resolver/nebari.testfile (below) tells macOS to use it for that domain only — fully offline, no public DNS involved, and unaffected by routers that filter loopback answers. The container has--restart unless-stopped, so it survives reboots;make downleaves it running (remove it withdocker rm -f nebari-dev-dns). - Routing —
dev/kind-config.yamlmaps host ports 80/443 to fixed NodePorts (30080/30443) on the kind node, and anEnvoyProxyresource pins the gateway Service to those NodePorts. Traffic tolocalhost:80reaches Envoy directly — even on Docker Desktop, where the kind network is not routable from the host — and Envoy fans out to every app byHostheader.
Type the http:// scheme explicitly (browsers auto-upgrade bare hostnames to https, which
is disabled locally).
The one-time resolver file — the only sudo in the whole flow, once per machine.
make up offers to install it when run interactively, or do it yourself:
sudo sh -c 'mkdir -p /etc/resolver && printf "nameserver 127.0.0.1\nport 53535\n" > /etc/resolver/nebari.test'(The domain is nebari.test rather than nebari.local because macOS routes .local
through mDNS/Bonjour, bypassing /etc/resolver; .test is the TLD reserved for this. On
Linux, route the domain to 127.0.0.1:53535 with a systemd-resolved drop-in —
DNS=127.0.0.1:53535, Domains=~nebari.test — or fall back to /etc/hosts entries.)
Everyday loop
Section titled “Everyday loop”make redeploy # rebuild all four images + restart the Deploymentsmake up-git # also deploy the git-sourced, SSO-protected examplemake down # delete the kind clusterAuth in local dev
Section titled “Auth in local dev”make up deploys with api.auth.enabled=false. The kind stack’s Keycloak issuer is the
in-cluster service URL, which a host browser cannot reach, so keycloak-js logins (UI)
and JWT validation (API) cannot complete locally. Launched apps still get their
SecurityPolicies; on a real Nebari cluster with a public Keycloak everything works end to
end.
Connecting a coding agent locally
Section titled “Connecting a coding agent locally”The MCP server is proxied on the same hostname as the UI, so a local agent can drive the whole stack:
claude mcp add --transport http nebari-apps http://apps.nebari.test/mcpBecause local dev runs with auth disabled, the authenticate tool reports not_required
and every tool works anonymously — ask the agent to “launch the site in ./my-site as a
public app called my-site” and it will call launch_app directly. See the
MCP server guide for the full tool list.
Poking at the stack
Section titled “Poking at the stack”curl http://apps.nebari.test/api/v1/apps # the API, proxied by the UIcurl http://apps.nebari.test/api/v1/analytics/summary
kubectl get apps -n apps # the App CRskubectl describe app docs-site -n apps # conditionskubectl logs -n nebari-apps deploy/nebari-apps-operator -fTroubleshooting
Section titled “Troubleshooting”- App stuck in
DeployingwithRoutingReady: False— checkkubectl describe nebariapp app-<name> -n apps. Failedphase, message about namespace opt-in — label the namespace:kubectl label namespace <ns> nebari.dev/managed=true.make upfails creating the kind cluster with a port-binding error — something on the host already listens on port 80 or 443; stop it, or change thehostPortvalues indev/kind-config.yaml(prefer freeing 80 — browsers append any non-default port to theHostheader, which Envoy matches exactly).- Browser can’t resolve
*.apps.nebari.test— runmake check-dns: it restarts the DNS container if needed and installs the one-time/etc/resolver/nebari.testfile on macOS. Verify the container directly withdig @127.0.0.1 -p 53535 anything.apps.nebari.test. - Cluster predates the NodePort setup (URLs time out) — port mappings only apply at
cluster creation; recreate with
make down && make up. - “Gateway not yet programmed” warning during cluster setup — expected: no LoadBalancer provider is installed; the gateway Service switches to NodePort in the step right after.
- Want to exercise the TLS path locally — redeploy with
--set tls.enabled=true; the local issuer is self-signed, so expect certificate warnings.