{"id":50797099,"url":"https://github.com/nebari-dev/nebari-software-pack-template","last_synced_at":"2026-06-12T15:30:30.735Z","repository":{"id":340954418,"uuid":"1167648830","full_name":"nebari-dev/nebari-software-pack-template","owner":"nebari-dev","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-15T13:21:45.000Z","size":125,"stargazers_count":0,"open_issues_count":3,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-15T15:27:52.007Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Makefile","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nebari-dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-26T14:28:43.000Z","updated_at":"2026-05-15T13:21:48.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/nebari-dev/nebari-software-pack-template","commit_stats":null,"previous_names":["nebari-dev/nebari-software-pack-template"],"tags_count":0,"template":true,"template_full_name":null,"purl":"pkg:github/nebari-dev/nebari-software-pack-template","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nebari-dev%2Fnebari-software-pack-template","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nebari-dev%2Fnebari-software-pack-template/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nebari-dev%2Fnebari-software-pack-template/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nebari-dev%2Fnebari-software-pack-template/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nebari-dev","download_url":"https://codeload.github.com/nebari-dev/nebari-software-pack-template/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nebari-dev%2Fnebari-software-pack-template/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34251773,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-12T02:00:06.859Z","response_time":109,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-12T15:30:29.841Z","updated_at":"2026-06-12T15:30:30.724Z","avatar_url":"https://github.com/nebari-dev.png","language":"Makefile","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Nebari Software Pack Template\n\n[![Lint](https://github.com/nebari-dev/nebari-software-pack-template/actions/workflows/lint.yaml/badge.svg)](https://github.com/nebari-dev/nebari-software-pack-template/actions/workflows/lint.yaml)\n[![Test](https://github.com/nebari-dev/nebari-software-pack-template/actions/workflows/test.yaml/badge.svg)](https://github.com/nebari-dev/nebari-software-pack-template/actions/workflows/test.yaml)\n[![Integration Test](https://github.com/nebari-dev/nebari-software-pack-template/actions/workflows/test-integration.yaml/badge.svg)](https://github.com/nebari-dev/nebari-software-pack-template/actions/workflows/test-integration.yaml)\n\nA template repository for building **Nebari Software Packs** - Kubernetes\napplications that deploy on the [Nebari](https://nebari.dev) platform with\noptional routing, TLS, and OIDC authentication.\n\n## Table of Contents\n\n- [What is a Nebari Software Pack?](#what-is-a-nebari-software-pack)\n- [Prerequisites](#prerequisites)\n- [Getting Started](#getting-started)\n- [Repository Structure](#repository-structure)\n- [The NebariApp CRD](#the-nebariapp-crd)\n- [Example 1: Vanilla YAML (Plain Manifests)](#example-1-vanilla-yaml-plain-manifests)\n- [Example 2: Kustomize (Nginx)](#example-2-kustomize-nginx)\n- [Example 3: Helm - Basic Pack (Nginx)](#example-3-helm---basic-pack-nginx)\n- [Example 4: Helm - Auth-Aware Pack (FastAPI)](#example-4-helm---auth-aware-pack-fastapi)\n- [Example 5: Helm - Wrapping an Existing Chart (Podinfo)](#example-5-helm---wrapping-an-existing-chart-podinfo)\n- [How Authentication Works](#how-authentication-works)\n- [Local Development](#local-development)\n- [CI/CD Pipeline](#cicd-pipeline)\n- [Deploying to a Nebari Cluster](#deploying-to-a-nebari-cluster)\n- [Customizing for Your Own Application](#customizing-for-your-own-application)\n- [Troubleshooting](#troubleshooting)\n\n## What is a Nebari Software Pack?\n\nA **software pack** is any Kubernetes deployment that includes a **NebariApp**\ncustom resource. The NebariApp tells the\n[nebari-operator](https://github.com/nebari-dev/nebari-operator) to auto-configure:\n\n- **Routing** - Creates an HTTPRoute on the shared Envoy Gateway\n- **TLS** - Provisions a certificate via cert-manager\n- **Authentication** - Sets up Keycloak OIDC via an Envoy Gateway SecurityPolicy\n\nThe NebariApp CRD is the integration point between your application and the\nNebari platform. How you deploy the rest of your application is up to you -\n**Helm charts, Kustomize overlays, and plain YAML manifests** are all supported.\nAll three are first-class deployment methods in ArgoCD.\n\n```mermaid\ngraph LR\n    User --\u003e|HTTPS| EG[Envoy Gateway]\n    EG --\u003e|No session?| KC[Keycloak]\n    KC --\u003e|Auth code| EG\n    EG --\u003e|IdToken cookie| HR[HTTPRoute]\n    HR --\u003e SVC[Service]\n    SVC --\u003e Pod\n\n    style EG fill:#e1f5fe\n    style KC fill:#fff3e0\n    style HR fill:#e8f5e9\n```\n\n## Prerequisites\n\n- [kubectl](https://kubernetes.io/docs/tasks/tools/)\n\nDepending on your deployment method:\n- [Helm 3](https://helm.sh/docs/intro/install/) (for Helm examples)\n- [Docker](https://docs.docker.com/get-docker/) (for building custom images)\n\nFor local development (optional):\n- [kind](https://kind.sigs.k8s.io/)\n\n## Getting Started\n\n1. **Use this template** - Click \"Use this template\" on GitHub to create your own repo\n\n2. **Clone your new repo**\n   ```bash\n   git clone https://github.com/YOUR-ORG/YOUR-REPO.git\n   cd YOUR-REPO\n   ```\n\n3. **Pick an example** that matches your use case:\n\n   **No tooling dependencies:**\n   - `examples/vanilla-yaml/` - Plain YAML manifests, just `kubectl apply`\n   - `examples/kustomize-nginx/` - Kustomize overlays for per-environment config\n\n   **Helm-based:**\n   - `examples/basic-nginx/` - Simplest possible Helm chart\n   - `examples/auth-fastapi/` - Custom app that reads auth tokens\n   - `examples/wrap-existing-chart/` - Wrapping an existing Helm chart (most common for Helm)\n\n4. **Search and replace** `my-pack` with your pack name:\n   ```bash\n   # Preview changes\n   grep -r \"my-pack\" examples/vanilla-yaml/\n\n   # Replace (using your pack name)\n   find . -type f -name \"*.yaml\" -o -name \"*.tpl\" -o -name \"*.txt\" -o -name \"Makefile\" | \\\n     xargs sed -i 's/my-pack/your-pack-name/g'\n   ```\n\n5. **Deploy locally** to test:\n   ```bash\n   # Vanilla YAML (simplest)\n   kubectl apply -f examples/vanilla-yaml/deployment.yaml \\\n                 -f examples/vanilla-yaml/service.yaml\n   kubectl port-forward svc/my-pack 8080:80\n\n   # Or with Helm\n   helm install test examples/basic-nginx/chart/\n   kubectl port-forward svc/test-my-pack 8080:80\n\n   # Open http://localhost:8080\n   ```\n\n## Repository Structure\n\n```\nnebari-software-pack-template/\n  .github/workflows/\n    build-image.yaml             # Build + publish auth-fastapi image to GHCR\n    lint.yaml                    # Manifest validation (all examples)\n    test.yaml                    # Integration tests on kind cluster\n    test-integration.yaml        # NebariApp integration tests (full stack)\n    release.yaml                 # Helm package + GitHub release + gh-pages index\n  examples/\n    vanilla-yaml/                # Example 1: Plain Kubernetes manifests\n      deployment.yaml            # nginx Deployment\n      service.yaml               # ClusterIP Service\n      nebariapp.yaml             # NebariApp CRD resource\n      README.md\n    kustomize-nginx/             # Example 2: Kustomize-based pack\n      base/\n        kustomization.yaml       # References base resources\n        deployment.yaml\n        service.yaml\n        nebariapp.yaml\n      overlays/\n        dev/                     # Dev overlay: dev hostname, no auth\n          kustomization.yaml\n          nebariapp-patch.yaml\n        production/              # Prod overlay: prod hostname, auth + groups\n          kustomization.yaml\n          nebariapp-patch.yaml\n      README.md\n    basic-nginx/                 # Example 3: Simplest Helm chart\n      chart/\n        Chart.yaml\n        values.yaml\n        templates/\n          _helpers.tpl           # Name, label, selector helpers\n          nebariapp.yaml         # NebariApp CRD (conditional)\n          deployment.yaml        # Kubernetes Deployment\n          service.yaml           # ClusterIP Service\n          NOTES.txt              # Post-install instructions\n      README.md\n    auth-fastapi/                # Example 4: Custom app reading IdToken\n      app/\n        main.py                  # FastAPI reading IdToken cookie\n        requirements.txt\n        templates/index.html     # User info display\n      Dockerfile\n      chart/                     # Same structure as basic-nginx\n      README.md\n    wrap-existing-chart/         # Example 5: Wrapping podinfo via Helm\n      chart/\n        Chart.yaml               # Has podinfo as a dependency\n        Chart.lock\n        values.yaml              # Podinfo overrides + NebariApp config\n        templates/\n          _helpers.tpl\n          nebariapp.yaml         # Points to podinfo's service\n          NOTES.txt\n      README.md\n  dev/\n    Makefile                     # Local dev with full Nebari stack on kind\n    .cache/                      # (gitignored) Cloned nebari-operator scripts\n  docs/\n    nebariapp-crd-reference.md   # Full NebariApp field reference\n    auth-flow.md                 # Authentication flow details\n  .gitignore\n  .editorconfig\n  LICENSE                        # Apache 2.0\n  README.md                      # This file\n```\n\n## The NebariApp CRD\n\nThe **NebariApp** custom resource is the integration point between your pack and the\nNebari platform. When you create a NebariApp, the\n[nebari-operator](https://github.com/nebari-dev/nebari-operator) watches for it and\nautomatically configures routing, TLS, and authentication.\n\nHere's a fully annotated example:\n\n```yaml\napiVersion: reconcilers.nebari.dev/v1\nkind: NebariApp\nmetadata:\n  name: my-pack\nspec:\n  # The domain where your app will be accessible\n  hostname: my-pack.nebari.example.com\n\n  # The Kubernetes Service that should receive traffic\n  service:\n    name: my-pack           # Service name in the same namespace\n    port: 80                # Service port (1-65535)\n\n  # Optional: path-based routing rules\n  routing:\n    routes:\n      - pathPrefix: /       # Match all paths (default behavior)\n        pathType: PathPrefix # PathPrefix or Exact\n    tls:\n      enabled: true         # Auto-provision TLS certificate (default: true)\n\n  # Optional: OIDC authentication\n  auth:\n    enabled: true                   # Require login (default: false)\n    provider: keycloak              # keycloak or generic-oidc\n    provisionClient: true           # Auto-create Keycloak client (default: true)\n    # redirectURI: /oauth2/callback # OAuth callback path (default shown; rarely needs overriding)\n    scopes:                         # OIDC scopes to request\n      - openid\n      - profile\n      - email\n    groups:                         # Restrict to specific groups (optional)\n      - admin\n    enforceAtGateway: true          # Create SecurityPolicy at gateway (default: true)\n\n  # Which gateway to use: \"public\" (default) or \"internal\"\n  gateway: public\n```\n\nThe NebariApp is just a Kubernetes resource. It can live in a plain YAML file, a\nKustomize base, or a Helm template. In Helm charts, you can make the NebariApp\nconditional so the chart works both standalone and on Nebari:\n\n```yaml\n{{- if .Values.nebariapp.enabled }}\napiVersion: reconcilers.nebari.dev/v1\nkind: NebariApp\n...\n{{- end }}\n```\n\nWith plain YAML or Kustomize, the NebariApp manifest is always present. When\ndeploying standalone, simply skip that file or exclude it from your apply command.\n\n### Beyond the basics\n\nThe fields shown above cover the common cases. The operator also supports several\nmore specialized features. Each is documented in\n[docs/nebariapp-crd-reference.md](docs/nebariapp-crd-reference.md):\n\n- **`routing.publicRoutes`** - paths that bypass OIDC auth (e.g., `/healthz`,\n  webhooks, public APIs).\n- **`routing.annotations`** - extra annotations on the generated HTTPRoute, useful\n  for ArgoCD tracking or other tooling.\n- **`auth.forwardAccessToken`** - send the user's access token to your app as\n  `Authorization: Bearer \u003ctoken\u003e` so it can decode the JWT itself.\n- **`auth.denyRedirect`** - return 401 instead of redirecting to Keycloak when\n  matching headers are present. Most useful alongside `auth.spaClient` to avoid\n  PKCE races when an SPA fires several requests on page load.\n- **`auth.spaClient`** - provision a separate public Keycloak client for browser-\n  based PKCE flows (React + `keycloak-js`, etc.).\n- **`auth.deviceFlowClient`** - provision a public client for the OAuth2 Device\n  Authorization Grant (CLIs and native apps).\n- **`auth.keycloakConfig`** - declaratively manage Keycloak realm groups and\n  client-level protocol mappers from the NebariApp.\n- **`auth.tokenExchange`** - opt this app into RFC 8693 token exchange so other\n  NebariApp clients can mint tokens for its audience.\n- **`landingPage`** - register the app on the Nebari landing page with an icon,\n  category, priority, and optional health check.\n- **`serviceAccountName`** - which ServiceAccount the operator should grant access\n  to the OIDC client Secret.\n- **`service.namespace`** - point the NebariApp at a Service in a different\n  namespace.\n\nFor the complete field reference, see [docs/nebariapp-crd-reference.md](docs/nebariapp-crd-reference.md).\n\n## Example 1: Vanilla YAML (Plain Manifests)\n\nThe simplest possible pack. Plain Kubernetes manifests with no tooling\ndependencies beyond `kubectl`.\n\n**What it demonstrates:**\n- Lowest barrier to entry\n- NebariApp as a plain YAML file alongside Deployment and Service\n- No templating or tooling required\n\n```bash\n# Deploy standalone (skip the NebariApp)\nkubectl apply -f examples/vanilla-yaml/deployment.yaml \\\n              -f examples/vanilla-yaml/service.yaml\nkubectl port-forward svc/my-pack 8080:80\n# Open http://localhost:8080\n\n# Deploy on Nebari (edit nebariapp.yaml hostname first)\nkubectl apply -f examples/vanilla-yaml/\n```\n\nSee [examples/vanilla-yaml/README.md](examples/vanilla-yaml/README.md) for the full walkthrough.\n\n## Example 2: Kustomize (Nginx)\n\nUses [Kustomize](https://kustomize.io/) overlays to manage environment-specific\nNebariApp configuration. Same nginx app as the vanilla example, but with\nstructured per-environment patches.\n\n**What it demonstrates:**\n- Kustomize base with overlays for dev and production\n- Patching hostname and auth settings per environment\n- No Helm dependency\n\n```bash\n# Preview the dev overlay\nkubectl kustomize examples/kustomize-nginx/overlays/dev/\n\n# Deploy the dev overlay on Nebari\nkubectl apply -k examples/kustomize-nginx/overlays/dev/\n\n# Deploy the production overlay (auth enabled, group-restricted)\nkubectl apply -k examples/kustomize-nginx/overlays/production/\n```\n\nSee [examples/kustomize-nginx/README.md](examples/kustomize-nginx/README.md) for the full walkthrough.\n\n## Example 3: Helm - Basic Pack (Nginx)\n\nThe simplest possible Helm-based pack. Deploys a stock nginx container with\noptional Nebari integration via a conditional NebariApp template.\n\n**What it demonstrates:**\n- Minimum viable Helm chart structure\n- Conditional NebariApp template (`nebariapp.enabled` toggle)\n- Toggling between standalone and Nebari modes\n\n```bash\n# Deploy standalone\nhelm install test-basic examples/basic-nginx/chart/\nkubectl port-forward svc/test-basic-my-pack 8080:80\n# Open http://localhost:8080\n\n# Deploy on Nebari\nhelm install my-pack examples/basic-nginx/chart/ \\\n  --set nebariapp.enabled=true \\\n  --set nebariapp.hostname=my-pack.nebari.example.com\n\n# Deploy on Nebari with auth\nhelm install my-pack examples/basic-nginx/chart/ \\\n  --set nebariapp.enabled=true \\\n  --set nebariapp.hostname=my-pack.nebari.example.com \\\n  --set nebariapp.auth.enabled=true\n```\n\nSee [examples/basic-nginx/README.md](examples/basic-nginx/README.md) for the full walkthrough.\n\n## Example 4: Helm - Auth-Aware Pack (FastAPI)\n\nA custom Python app that reads the IdToken cookie set by Envoy Gateway after\nKeycloak authentication. Shows how to consume authenticated user identity.\n\n**What it demonstrates:**\n- Building a custom container image\n- Reading the IdToken cookie to get user claims\n- Rendering user info (username, email, groups)\n\nThe key code in `app/main.py`:\n\n```python\ndef get_id_token(request: Request) -\u003e str | None:\n    \"\"\"Extract IdToken from Envoy Gateway's OIDC filter cookies.\n\n    Envoy Gateway sets a cookie named IdToken-\u003csuffix\u003e where \u003csuffix\u003e\n    is an 8-char hex string derived from the SecurityPolicy UID.\n    \"\"\"\n    for name, value in request.cookies.items():\n        if name.startswith(\"IdToken-\"):\n            return value\n    return None\n```\n\n```bash\n# Run locally (shows \"Not Authenticated\" - no IdToken cookie without Envoy Gateway)\ndocker run -p 8000:8000 ghcr.io/nebari-dev/nebari-software-pack-template/auth-fastapi-example:latest\n\n# Deploy on Nebari with auth\nhelm install my-pack examples/auth-fastapi/chart/ \\\n  --set nebariapp.enabled=true \\\n  --set nebariapp.hostname=my-pack.nebari.example.com\n```\n\nSee [examples/auth-fastapi/README.md](examples/auth-fastapi/README.md) for the full walkthrough.\n\n## Example 5: Helm - Wrapping an Existing Chart (Podinfo)\n\n**This is the most realistic Helm use case.** Most Helm-based packs wrap\nexisting software - you don't write your own Deployment or Service. You add\nthe upstream chart as a dependency and create a NebariApp that points to its\nservice.\n\n**What it demonstrates:**\n- Chart.yaml dependency on an existing chart\n- Overriding upstream values\n- NebariApp pointing to the upstream service\n- No custom Deployment or Service templates needed\n\n```yaml\n# Chart.yaml - just add the dependency\ndependencies:\n  - name: podinfo\n    version: 6.10.1\n    repository: oci://ghcr.io/stefanprodan/charts\n```\n\nThe only template you write is `nebariapp.yaml`, which points to podinfo's service:\n\n```yaml\nspec:\n  service:\n    name: {{ .Release.Name }}-podinfo   # Upstream service\n    port: 9898\n```\n\n**You don't rewrite the app. You just connect it to Nebari.**\n\n```bash\n# Build dependencies\nhelm dependency update examples/wrap-existing-chart/chart/\n\n# Deploy standalone\nhelm install test-wrap examples/wrap-existing-chart/chart/\nkubectl port-forward svc/test-wrap-podinfo 9898:9898\n\n# Deploy on Nebari\nhelm install my-pack examples/wrap-existing-chart/chart/ \\\n  --set nebariapp.enabled=true \\\n  --set nebariapp.hostname=my-pack.nebari.example.com\n```\n\nSee [examples/wrap-existing-chart/README.md](examples/wrap-existing-chart/README.md) for the full walkthrough.\n\n## How Authentication Works\n\nWhen a NebariApp has `auth.enabled: true`, the nebari-operator creates an Envoy\nGateway SecurityPolicy that handles the full OIDC flow:\n\n```\n1. User visits my-pack.nebari.example.com\n2. Envoy Gateway checks for a valid session cookie\n   - No cookie? Redirect to Keycloak login page\n3. User authenticates with Keycloak\n4. Keycloak redirects back with an authorization code\n5. Envoy Gateway exchanges the code for tokens\n6. Envoy Gateway sets cookies:\n   - IdToken-\u003csuffix\u003e     (JWT with user claims)\n   - AccessToken-\u003csuffix\u003e\n   - RefreshToken-\u003csuffix\u003e\n   (\u003csuffix\u003e is an 8-char, lower-case hex derived from the SecurityPolicy UID via FNV-32a)\n7. Request (now with cookies) is forwarded to your app\n```\n\n**What the operator automates:**\n- Creates a Keycloak OIDC client (when `provisionClient: true`)\n- Stores client credentials in a Kubernetes Secret\n- Creates an Envoy Gateway SecurityPolicy with the OIDC configuration\n- Creates an HTTPRoute directing traffic to your service\n- Provisions a TLS certificate via cert-manager\n\n**What your app can do:**\n- Read the `IdToken-*` cookies to get the JWT (see Example 4)\n- Decode the JWT payload to extract claims: `preferred_username`, `email`, `groups`\n- The JWT signature is already verified by Envoy Gateway - you only need to base64-decode the payload\n\n**If your app handles OAuth natively** (like Grafana), set `enforceAtGateway: false`.\nThe operator will still provision the OIDC client and store credentials in a Secret,\nbut won't create a SecurityPolicy. Your app reads the credentials from the Secret\nand handles the OAuth flow itself.\n\nFor more details, see [docs/auth-flow.md](docs/auth-flow.md).\n\n## Local Development\n\nThe `dev/` directory provides a Makefile for local development with\n[kind](https://kind.sigs.k8s.io/). Running any `up-*` target automatically\ncreates a kind cluster with the full Nebari infrastructure stack - MetalLB,\nEnvoy Gateway, cert-manager, Keycloak, and the nebari-operator - so every\nexample deploys with NebariApp enabled, routing, TLS, and authentication\nworking just like a real Nebari cluster.\n\nThe first `make up-*` run takes ~5-10 minutes (cluster and infrastructure\nsetup). Subsequent runs reuse the existing cluster and are fast.\n\n```bash\ncd dev\n\n# Deploy vanilla YAML example\nmake up-vanilla\n\n# Deploy kustomize example (dev overlay)\nmake up-kustomize\n\n# Deploy Helm nginx example\nmake up-basic\n\n# Deploy podinfo Helm example\nmake up-podinfo\n\n# Deploy FastAPI Helm example (auth enabled, uses pre-built GHCR image)\nmake up-fastapi\n\n# Update /etc/hosts with NebariApp hostnames\nmake update-hosts\n\n# Delete the kind cluster\nmake down\n```\n\nEach `up-*` target deploys with NebariApp enabled at `https://my-pack.nebari.local`,\nwaits for the NebariApp Ready condition, and updates `/etc/hosts` so you can access\nthe app in your browser.\n\n### What's not included\n\nThe local dev environment does not include ArgoCD. If you need to develop or\ntest the ArgoCD Application that wraps your software pack, you'll need to set\nthat up separately. In the future, Nebari will support pointing at a local Git\nrepo (and creating a temporary one if none is provided) so ArgoCD-based\nworkflows can be tested locally without an external repository.\n\n## CI/CD Pipeline\n\n### Lint (`lint.yaml`)\n\nRuns on every push and PR. Validates all examples:\n\n- `kubectl apply --dry-run=client` for the vanilla YAML example\n- `kubectl kustomize` for each Kustomize overlay\n- `helm lint` and `helm template` for each Helm chart (both NebariApp enabled and disabled)\n\n### Build Image (`build-image.yaml`)\n\nRuns on pushes to main that modify `examples/auth-fastapi/app/` or the\n`Dockerfile`, plus manual dispatch. Builds and publishes the auth-fastapi\nexample image to `ghcr.io/nebari-dev/nebari-software-pack-template/auth-fastapi-example`.\n\n### Test (`test.yaml`)\n\nRuns on every push and PR. Standalone integration tests on a kind cluster:\n\n- Creates a kind cluster\n- Deploys each example with `nebariapp.enabled=false` (no operator required)\n- Waits for pods and runs HTTP health checks via port-forward\n- Validates that each example works as a standalone Kubernetes deployment\n\n### Integration Test (`test-integration.yaml`)\n\nRuns on pushes to main and PRs that modify `examples/`, `dev/`, or the workflow\nfile. Tests each example with `nebariapp.enabled=true` on a full Nebari\ninfrastructure stack:\n\n- Creates a kind cluster with MetalLB, Envoy Gateway, cert-manager, and Keycloak\n- Installs the nebari-operator from a pinned release (currently `v0.1.0-alpha.19`)\n- Deploys each example with NebariApp enabled and a `*.nebari.local` hostname\n- Verifies NebariApp reaches `Ready` condition (HTTPRoute created, TLS configured)\n- For auth-enabled examples (kustomize production, auth-fastapi), verifies\n  SecurityPolicy is created\n\nThis catches bugs in NebariApp configuration, operator compatibility, and routing\nsetup that the standalone test cannot detect.\n\n### Release (`release.yaml`)\n\nManual dispatch. Packages and releases a Helm chart:\n\n- Packages the selected chart as a `.tgz`\n- Creates a GitHub release with the package\n- Updates the `gh-pages` branch with a Helm repo index\n\n## Deploying to a Nebari Cluster\n\n### Option A: ArgoCD Application (recommended)\n\nArgoCD supports all three deployment methods. Set the `source` section based on\nyour pack type:\n\n**ArgoCD with Helm:**\n\n```yaml\napiVersion: argoproj.io/v1alpha1\nkind: Application\nmetadata:\n  name: my-pack\n  namespace: argocd\nspec:\n  project: default\n  source:\n    repoURL: https://github.com/YOUR-ORG/YOUR-REPO.git\n    targetRevision: main\n    path: examples/basic-nginx/chart    # or your chart path\n    helm:\n      valuesObject:\n        nebariapp:\n          enabled: true\n          hostname: my-pack.nebari.example.com\n          auth:\n            enabled: true\n  destination:\n    server: https://kubernetes.default.svc\n    namespace: my-pack\n  syncPolicy:\n    automated:\n      prune: true\n      selfHeal: true\n    syncOptions:\n      - CreateNamespace=true\n```\n\n**ArgoCD with Kustomize:**\n\n```yaml\napiVersion: argoproj.io/v1alpha1\nkind: Application\nmetadata:\n  name: my-pack\n  namespace: argocd\nspec:\n  project: default\n  source:\n    repoURL: https://github.com/YOUR-ORG/YOUR-REPO.git\n    targetRevision: main\n    path: examples/kustomize-nginx/overlays/production\n    # ArgoCD auto-detects kustomization.yaml\n  destination:\n    server: https://kubernetes.default.svc\n    namespace: my-pack\n  syncPolicy:\n    automated:\n      prune: true\n      selfHeal: true\n    syncOptions:\n      - CreateNamespace=true\n```\n\n**ArgoCD with plain YAML (directory):**\n\n```yaml\napiVersion: argoproj.io/v1alpha1\nkind: Application\nmetadata:\n  name: my-pack\n  namespace: argocd\nspec:\n  project: default\n  source:\n    repoURL: https://github.com/YOUR-ORG/YOUR-REPO.git\n    targetRevision: main\n    path: examples/vanilla-yaml\n    directory:\n      recurse: false\n  destination:\n    server: https://kubernetes.default.svc\n    namespace: my-pack\n  syncPolicy:\n    automated:\n      prune: true\n      selfHeal: true\n    syncOptions:\n      - CreateNamespace=true\n```\n\n### Option B: kubectl apply (plain YAML)\n\n```bash\n# Edit nebariapp.yaml with your hostname first\nkubectl apply -f examples/vanilla-yaml/ \\\n  --namespace my-pack\n```\n\n### Option C: kubectl apply -k (Kustomize)\n\n```bash\nkubectl apply -k examples/kustomize-nginx/overlays/production/ \\\n  --namespace my-pack\n```\n\n### Option D: Helm install\n\n```bash\nhelm install my-pack ./chart/ \\\n  --namespace my-pack \\\n  --create-namespace \\\n  --set nebariapp.enabled=true \\\n  --set nebariapp.hostname=my-pack.nebari.example.com \\\n  --set nebariapp.auth.enabled=true\n```\n\n### Verifying the deployment\n\n```bash\n# Check the NebariApp status\nkubectl get nebariapp -n my-pack\n\n# Check conditions (should all be True when ready)\nkubectl describe nebariapp my-pack -n my-pack\n\n# Expected conditions:\n#   RoutingReady: True    - HTTPRoute created\n#   TLSReady: True        - Certificate provisioned\n#   AuthReady: True       - SecurityPolicy created (if auth enabled)\n#   Ready: True           - All components ready\n```\n\n## Customizing for Your Own Application\n\n### Search and replace\n\n| Token | Replace with | Where |\n|-------|-------------|-------|\n| `my-pack` | Your pack name (lowercase, hyphenated) | All YAML files, chart files, Makefile |\n| `OWNER/REPO` or `YOUR-ORG/YOUR-REPO` | Your GitHub org/repo | Workflows, README |\n\nThe placeholder `my-pack` is valid YAML/Helm syntax, so linting passes on the\ntemplate repo as-is.\n\n### Replacing the container image\n\nIn `values.yaml` (Helm) or directly in `deployment.yaml` (vanilla/Kustomize):\n\n```yaml\n# Helm values.yaml\nimage:\n  repository: your-registry/your-image\n  tag: \"1.0.0\"\n```\n\n```yaml\n# Plain YAML or Kustomize deployment.yaml\ncontainers:\n  - name: your-app\n    image: your-registry/your-image:1.0.0\n```\n\n### Adding resources\n\nCommon additions:\n\n- **ConfigMap** - Configuration files mounted into pods\n- **Secret** - Credentials (in Helm, use `lookup()` for ArgoCD safety)\n- **PersistentVolumeClaim** - Persistent storage\n- **ServiceAccount** - Pod identity for RBAC\n\nFor Helm charts, add these to `templates/`. For Kustomize, add them to `base/`\nand reference them in `kustomization.yaml`. For vanilla YAML, add them as\nadditional files.\n\n### Multiple routes\n\nIf your app serves multiple paths:\n\n```yaml\n# In the NebariApp spec (any deployment method)\nrouting:\n  routes:\n    - pathPrefix: /api\n      pathType: PathPrefix\n    - pathPrefix: /dashboard\n      pathType: PathPrefix\n```\n\n### Restricting access to specific groups\n\n```yaml\n# In the NebariApp spec (any deployment method)\nauth:\n  enabled: true\n  groups:\n    - admin\n    - data-science-team\n```\n\n## Troubleshooting\n\n### NebariApp shows `NamespaceNotOptedIn`\n\nThe namespace needs the label that opts it in for nebari-operator processing:\n\n```bash\nkubectl label namespace my-pack nebari.dev/managed=true\n```\n\n### NebariApp shows `ServiceNotFound`\n\nThe NebariApp's `spec.service.name` doesn't match any Service in the namespace.\nCheck the service name:\n\n```bash\nkubectl get svc -n my-pack\n```\n\nFor Helm-based wrapped charts, the service name follows the upstream chart's\nnaming convention (usually `\u003crelease\u003e-\u003cchart-name\u003e`).\n\n### Auth not working / no redirect to Keycloak\n\n1. Check that `auth.enabled` is `true` in the NebariApp spec\n2. Check that the nebari-operator is running:\n   ```bash\n   kubectl get pods -n nebari-system -l app=nebari-operator\n   ```\n3. Check the NebariApp conditions:\n   ```bash\n   kubectl describe nebariapp my-pack -n my-pack\n   ```\n   Look for `AuthReady` condition.\n\n### TLS certificate not provisioning\n\n1. Check cert-manager is running:\n   ```bash\n   kubectl get pods -n cert-manager\n   ```\n2. Check the Certificate resource:\n   ```bash\n   kubectl get certificate -n my-pack\n   kubectl describe certificate my-pack-tls -n my-pack\n   ```\n\n### No IdToken cookie in the app\n\n1. Ensure you're accessing through the configured hostname (not via port-forward)\n2. Check that the SecurityPolicy was created:\n   ```bash\n   kubectl get securitypolicy -n my-pack\n   ```\n3. Check Envoy Gateway logs:\n   ```bash\n   kubectl logs -n envoy-gateway-system -l app=envoy-gateway\n   ```\n\n### `helm dependency update` fails for wrapped charts\n\nFor OCI-based dependencies, ensure Helm 3.8+ is installed:\n\n```bash\nhelm version\nhelm dependency update examples/wrap-existing-chart/chart/\n```\n\n## License\n\nApache 2.0 - see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnebari-dev%2Fnebari-software-pack-template","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnebari-dev%2Fnebari-software-pack-template","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnebari-dev%2Fnebari-software-pack-template/lists"}