Skip to content

Nebari integration

If you are running skillsctl on a cluster managed by Nebari, you can use the NebariApp CRD instead of configuring an Ingress manually. The nebari-operator handles routing, TLS, and OIDC authentication automatically.

  • nebari-operator v0.1.0-alpha.16 or later, with KEYCLOAK_EXTERNAL_URL set on the controller deployment (the nic deploy configures this automatically). Device flow client provisioning requires alpha.16+.
  • The target namespace must have the nebari.dev/managed=true label (ArgoCD’s managedNamespaceMetadata handles this automatically)

When nebariapp.enabled=true, the Helm chart creates a NebariApp resource. The nebari-operator processes that resource and provisions:

  • An HTTPRoute to route traffic to the skillsctl service
  • A TLS certificate for the hostname via cert-manager
  • A confidential Keycloak OIDC client for the server
  • A public device flow Keycloak client for CLI authentication
  • A Kubernetes Secret with client credentials, issuer URL, and device flow client ID
  • RBAC (Role + RoleBinding) scoping Secret access to the app’s ServiceAccount

This means you do not need to configure Ingress, cert-manager, or Keycloak clients manually.

Use NebariApp when:

  • You are deploying to a cluster running the nebari-operator
  • You want to reuse the cluster’s Keycloak for authentication

Use ingress.enabled=true when:

  • You are on a non-Nebari cluster
  • You have your own Ingress controller and TLS setup
  • You are using an external OIDC provider

Do not enable both at the same time.

Most Nebari clusters use ArgoCD for GitOps. See examples/argocd-nebari.yaml in the repository for a complete ArgoCD Application manifest.

The key values:

nebariapp:
enabled: true
hostname: skillsctl.your-domain.com
routing:
routes:
- pathPrefix: /
auth:
enabled: true
provider: keycloak
provisionClient: true
enforceAtGateway: false
deviceFlowClient:
enabled: true
scopes:
- openid
- profile
- email

Important settings:

  • routing.routes is required - without it, no HTTPRoute is created and the service is unreachable
  • enforceAtGateway: false lets the server validate bearer tokens directly, which is required for CLI device flow authentication
  • deviceFlowClient.enabled: true provisions a public Keycloak client for skillsctl auth login

The ArgoCD app should use managedNamespaceMetadata to label the namespace and ServerSideApply=true for compatibility with operator-managed resources:

syncPolicy:
managedNamespaceMetadata:
labels:
nebari.dev/managed: "true"
syncOptions:
- CreateNamespace=true
- ServerSideApply=true

If you prefer Helm over ArgoCD, install from the published Helm repository:

Terminal window
helm repo add nebari https://nebari-dev.github.io/helm-repository/
helm repo update
helm install skillsctl nebari/skillsctl \
--namespace skillsctl --create-namespace \
-f values.yaml

Or directly from the OCI registry:

Terminal window
helm install skillsctl oci://quay.io/nebari/charts/skillsctl \
--namespace skillsctl --create-namespace \
-f values.yaml

Or from a local checkout:

Terminal window
git clone https://github.com/nebari-dev/skillsctl.git
helm install skillsctl ./skillsctl/chart \
--namespace skillsctl --create-namespace \
-f values.yaml

Label the namespace for the operator:

Terminal window
kubectl label namespace skillsctl nebari.dev/managed=true

When NebariApp is enabled with auth.provisionClient: true, the nebari-operator handles OIDC automatically. You do not need to set oidc.issuerURL or oidc.clientID in your values - the operator writes them to a Kubernetes Secret, and the Helm chart injects them as environment variables.

The server’s /auth/config endpoint returns the issuer URL, client ID, and device flow client ID so the CLI can self-configure. Users just run skillsctl auth login.

Check that the NebariApp reconciled successfully:

Terminal window
kubectl describe nebariapp skillsctl -n skillsctl

All conditions should be True:

  • Ready - overall status
  • TLSReady - certificate provisioned
  • RoutingReady - HTTPRoute created
  • AuthReady - OIDC clients provisioned

Check the Secret was created with all expected keys:

Terminal window
kubectl get secret skillsctl-oidc-client -n skillsctl -o jsonpath='{.data}' | jq 'keys'

Expected: ["client-id", "client-secret", "device-client-id", "issuer-url"]

Check RBAC:

Terminal window
kubectl get role,rolebinding -n skillsctl

Expected: skillsctl-oidc-secret-reader Role and RoleBinding.

Test the health endpoint:

Terminal window
curl https://skillsctl.your-domain.com/healthz

Expected: ok

Test auth config:

Terminal window
curl https://skillsctl.your-domain.com/auth/config

Expected: JSON with enabled: true, issuer_url, client_id, and device_client_id.