Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions .github/workflows/images.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,15 @@ jobs:
fail-fast: false
matrix:
include:
- image: apps-operator
- image: operator
context: operator
- image: apps-authz
- image: authz
context: authz
- image: apps-api
- image: api
context: api
- image: apps-ui
- image: ui
context: ui
- image: apps-mcp
- image: mcp
context: mcp
env:
IMAGE: ${{ matrix.image }}
Expand All @@ -75,8 +75,8 @@ jobs:
# Boots the real image so the assertion covers nginx's header
# inheritance, not just the config text.
- name: Security headers
if: matrix.image == 'apps-ui'
run: ui/scripts/check-headers.sh apps-ui:ci
if: matrix.image == 'ui'
run: ui/scripts/check-headers.sh ui:ci

# Not `needs: check`: a failing check must not stop `latest` tracking main,
# or the release commit's sha images from existing (release.yaml depends on
Expand All @@ -87,15 +87,15 @@ jobs:
fail-fast: false
matrix:
include:
- image: apps-operator
- image: operator
context: operator
- image: apps-authz
- image: authz
context: authz
- image: apps-api
- image: api
context: api
- image: apps-ui
- image: ui
context: ui
- image: apps-mcp
- image: mcp
context: mcp
permissions:
contents: read
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ start again on the next request

| Component | What it does | Image |
|---|---|---|
| apps-operator | Reconciles `App` resources into a Deployment, Service, ConfigMap and `NebariApp`, reports status, scales idle Apps to zero and serves the page they show while they start. | `apps-operator` |
| apps-api | REST API for CRUD, status, logs, events, analytics and zip/`.html` upload; writes `App` resources (and each App's write-only Secret) for the UI and the MCP server. | `apps-api` |
| apps-ui | The apps list as the home page (with a Needs attention banner, a Mine filter and Open links), app detail with logs and events, the launch form, and a Settings page (the UI and API versions, your groups, the cluster's settings, and how to connect an agent). | `apps-ui` |
| apps-mcp | MCP server at `/mcp` that lets coding agents launch and manage apps (tools), attach an app's spec, status, logs and events as context (resources), and follow the triage, launch and access-review workflows (prompts), with Keycloak device-flow login. | `apps-mcp` |
| apps-authz | Sidecar in the pods of Apps that can idle (records when the app last served a request) and of image Apps (gives them the one front port). It checks no tokens: `access.groups` are enforced at the gateway. | `apps-authz` |
| apps-operator | Reconciles `App` resources into a Deployment, Service, ConfigMap and `NebariApp`, reports status, scales idle Apps to zero and serves the page they show while they start. | `operator` |
| apps-api | REST API for CRUD, status, logs, events, analytics and zip/`.html` upload; writes `App` resources (and each App's write-only Secret) for the UI and the MCP server. | `api` |
| apps-ui | The apps list as the home page (with a Needs attention banner, a Mine filter and Open links), app detail with logs and events, the launch form, and a Settings page (the UI and API versions, your groups, the cluster's settings, and how to connect an agent). | `ui` |
| apps-mcp | MCP server at `/mcp` that lets coding agents launch and manage apps (tools), attach an app's spec, status, logs and events as context (resources), and follow the triage, launch and access-review workflows (prompts), with Keycloak device-flow login. | `mcp` |
| apps-authz | Sidecar in the pods of Apps that can idle (records when the app last served a request) and of image Apps (gives them the one front port). It checks no tokens: `access.groups` are enforced at the gateway. | `authz` |

Images are published to `ghcr.io/nebari-dev/apps-pack/<image>` and
`quay.io/nebari/apps-pack-<image>`. The UI signs in with keycloak-js and the API validates its
Expand Down
10 changes: 5 additions & 5 deletions charts/nebari-apps/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ wrong type fails the install.
| api.enabled | bool | `true` | Deploy apps-api (the REST API behind the UI and MCP server). |
| api.extraEnv | list | `[]` | Extra environment variables for apps-api, after the chart's own (an entry with the same name wins). |
| api.image.pullPolicy | string | `"IfNotPresent"` | Image pull policy. |
| api.image.repository | string | `"ghcr.io/nebari-dev/apps-pack/apps-api"` | apps-api image repository. |
| api.image.repository | string | `"ghcr.io/nebari-dev/apps-pack/api"` | apps-api image repository. |
| api.image.tag | string | `""` | Image tag. Empty defaults to the chart's appVersion. |
| api.replicaCount | int | `1` | apps-api replicas. |
| api.resources | object | `{"limits":{"cpu":"500m","memory":"256Mi"},"requests":{"cpu":"500m","memory":"256Mi"}}` | Container resources. Requests equal limits, so the pod is Guaranteed. |
Expand All @@ -47,7 +47,7 @@ wrong type fails the install.
| apps.networkPolicy.gatewayNamespace | string | `"envoy-gateway-system"` | Namespace of the Envoy gateway pods allowed to reach app pods. |
| appsDomain | string | `""` | Domain apps are exposed under: `https://<subdomain>.<appsDomain>`. Empty defaults to `apps.<clusterDomain>`, e.g. `my-app.apps.example.ai`. |
| authz.image.pullPolicy | string | `"IfNotPresent"` | Image pull policy. |
| authz.image.repository | string | `"ghcr.io/nebari-dev/apps-pack/apps-authz"` | apps-authz image repository. |
| authz.image.repository | string | `"ghcr.io/nebari-dev/apps-pack/authz"` | apps-authz image repository. |
| authz.image.tag | string | `""` | Image tag. Empty defaults to the chart's appVersion. |
| authz.resources | object | `{"limits":{"cpu":"100m","memory":"64Mi"},"requests":{"cpu":"10m","memory":"32Mi"}}` | Sidecar resources, per app pod that runs it (on top of the app container's own). |
| clusterDomain | string | `""` | Required. The cluster's base domain; there is no sensible default. |
Expand All @@ -60,7 +60,7 @@ wrong type fails the install.
| mcp.enabled | bool | `true` | Deploy the MCP server. Agents connect at `https://<ui hostname>/mcp` (streamable HTTP); the UI's nginx proxies `/mcp` to it, so there is no separate hostname. When off, `/mcp` returns 404. |
| mcp.extraEnv | list | `[]` | Extra environment variables for apps-mcp, after the chart's own (an entry with the same name wins). |
| mcp.image.pullPolicy | string | `"IfNotPresent"` | Image pull policy. |
| mcp.image.repository | string | `"ghcr.io/nebari-dev/apps-pack/apps-mcp"` | apps-mcp image repository. |
| mcp.image.repository | string | `"ghcr.io/nebari-dev/apps-pack/mcp"` | apps-mcp image repository. |
| mcp.image.tag | string | `""` | Image tag. Empty defaults to the chart's appVersion. |
| mcp.oauth.callbackPort | int | `33418` | Port of the MCP client's OAuth callback (`--callback-port` in Claude Code); must match the client's redirect URI. |
| mcp.oauth.clientId | string | `""` | Client id of a public Keycloak client (standard flow, PKCE S256) with the redirect URI `http://localhost:<callbackPort>/callback`. Its tokens are accepted by apps-api and apps-mcp, and the UI's Settings page puts it in the `claude mcp add` command. Empty: MCP clients must bring their own way to a token. |
Expand All @@ -71,7 +71,7 @@ wrong type fails the install.
| nameOverride | string | `""` | Override the chart name used in resource names. |
| operator.extraEnv | list | `[]` | Extra environment variables for the operator container. |
| operator.image.pullPolicy | string | `"IfNotPresent"` | Image pull policy. |
| operator.image.repository | string | `"ghcr.io/nebari-dev/apps-pack/apps-operator"` | Operator image repository. |
| operator.image.repository | string | `"ghcr.io/nebari-dev/apps-pack/operator"` | Operator image repository. |
| operator.image.tag | string | `""` | Image tag. Empty defaults to the chart's appVersion. |
| operator.leaderElection | bool | `true` | Elect a leader so only one replica reconciles at a time. |
| operator.replicaCount | int | `1` | Operator replicas (one leads when `leaderElection` is on). |
Expand All @@ -97,7 +97,7 @@ wrong type fails the install.
| ui.extraEnv | list | `[]` | Extra environment variables for the UI's nginx container. |
| ui.hostname | string | `""` | Hostname for the UI. Empty defaults to the apps domain itself, so the UI lives at `https://apps.<clusterDomain>` and launched apps at `https://<name>.apps.<clusterDomain>`. |
| ui.image.pullPolicy | string | `"IfNotPresent"` | Image pull policy. |
| ui.image.repository | string | `"ghcr.io/nebari-dev/apps-pack/apps-ui"` | apps-ui image repository. |
| ui.image.repository | string | `"ghcr.io/nebari-dev/apps-pack/ui"` | apps-ui image repository. |
| ui.image.tag | string | `""` | Image tag. Empty defaults to the chart's appVersion. |
| ui.landingPage.category | string | `"Platform"` | Landing-page group the tile appears in. |
| ui.landingPage.description | string | `"Launch and manage web applications on this cluster."` | Tile blurb. |
Expand Down
10 changes: 5 additions & 5 deletions charts/nebari-apps/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ apps:
operator:
image:
# -- Operator image repository.
repository: ghcr.io/nebari-dev/apps-pack/apps-operator
repository: ghcr.io/nebari-dev/apps-pack/operator
# -- Image tag. Empty defaults to the chart's appVersion.
tag: ""
# -- Image pull policy.
Expand All @@ -142,7 +142,7 @@ api:
enabled: true
image:
# -- apps-api image repository.
repository: ghcr.io/nebari-dev/apps-pack/apps-api
repository: ghcr.io/nebari-dev/apps-pack/api
# -- Image tag. Empty defaults to the chart's appVersion.
tag: ""
# -- Image pull policy.
Expand Down Expand Up @@ -185,7 +185,7 @@ ui:
enabled: true
image:
# -- apps-ui image repository.
repository: ghcr.io/nebari-dev/apps-pack/apps-ui
repository: ghcr.io/nebari-dev/apps-pack/ui
# -- Image tag. Empty defaults to the chart's appVersion.
tag: ""
# -- Image pull policy.
Expand Down Expand Up @@ -281,7 +281,7 @@ mcp:
enabled: true
image:
# -- apps-mcp image repository.
repository: ghcr.io/nebari-dev/apps-pack/apps-mcp
repository: ghcr.io/nebari-dev/apps-pack/mcp
# -- Image tag. Empty defaults to the chart's appVersion.
tag: ""
# -- Image pull policy.
Expand Down Expand Up @@ -393,7 +393,7 @@ pythonImage: ghcr.io/prefix-dev/pixi:0.68.1-noble
authz:
image:
# -- apps-authz image repository.
repository: ghcr.io/nebari-dev/apps-pack/apps-authz
repository: ghcr.io/nebari-dev/apps-pack/authz
# -- Image tag. Empty defaults to the chart's appVersion.
tag: ""
# -- Image pull policy.
Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/branding.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ Running the standalone `apps-ui` image without a chart, branding resolves from,
-e API_URL=http://apps-api:8080 \
-e BRANDING_TITLE="Acme Apps" \
-e BRANDING_LOGO_URL=https://cdn.acme.example/logo.svg \
ghcr.io/nebari-dev/apps-pack/apps-ui
ghcr.io/nebari-dev/apps-pack/ui
```

3. **Built-in Nebari defaults** for any field still unset.
Expand Down
5 changes: 2 additions & 3 deletions docs/src/content/docs/ci-cd.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ description: What CI checks on every pull request, how images and charts are bui
| | UI e2e + a11y | Playwright on Chromium against the UI job's build (apps-api mocked): a user flow or keyboard path that breaks, or a `critical`/`serious` axe violation (WCAG 2.0/2.1 A and AA) on any page in light or dark, at desktop width and at 390px (where a page that scrolls sideways also fails); uploads the HTML report every run |
| | Helm chart | `helm lint`/`helm template` errors, `values.schema.json`, a stale chart README, chart RBAC assertions, the alert rules (promtool), kubeconform on the examples and rendered chart |
| | Repo invariants | a path in `AGENTS.md` or the skill that does not exist, version strings that disagree with the release manifest, a pin file that disagrees with its Dockerfile, actionlint, zizmor |
| **Images** (`images.yaml`) | one per image | the image does not build, or (`apps-ui`) serves the wrong security headers; on changes to the images' sources only |
| **Images** (`images.yaml`) | one per image | the image does not build, or (`ui`) serves the wrong security headers; on changes to the images' sources only |
| **Docs** (`docs.yml`) | docs | the site's tests or build, or a broken external link (on docs changes only) |

CI also runs weekly, so advisories published after a merge surface even when nothing
Expand All @@ -29,8 +29,7 @@ Every action is pinned to a commit SHA, and every workflow starts from

## Images

`images.yaml` builds the five images (`apps-operator`, `apps-authz`, `apps-api`,
`apps-ui`, `apps-mcp`). Pull requests build each image once, on `linux/amd64`, without
`images.yaml` builds the five images (`operator`, `authz`, `api`, `ui`, `mcp`). Pull requests build each image once, on `linux/amd64`, without
pushing. Every merge to `main` builds them for `linux/amd64` and `linux/arm64` through the
org's reusable `pack-build-image` workflow, pushes `ghcr.io/nebari-dev/apps-pack/<image>`
and `quay.io/nebari/apps-pack-<image>`, tagged `sha-<short commit>` and `latest`, and signs
Expand Down
10 changes: 5 additions & 5 deletions docs/src/content/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ only `clusterDomain` and `keycloak.url` need changing.
| `staticImage` | `nginxinc/nginx-unprivileged:1.27-alpine` | Serves static app content; must listen on 8080 as non-root. The operator mounts its nginx server config (security headers, `try_files`) over `/etc/nginx/conf.d`, so a replacement image should be nginx-based or ignore that directory. |
| `gitImage` | `alpine/git:v2.47.2` | Init-container image that clones `git` sources. |
| `pythonImage` | `ghcr.io/prefix-dev/pixi:0.68.1-noble` | Runs Python/pixi apps (`runtime.pixiTask`); must provide `pixi` and run as non-root. |
| `authz.image.repository` / `tag` / `pullPolicy` | `ghcr.io/nebari-dev/apps-pack/apps-authz` / appVersion / `IfNotPresent` | The sidecar the operator adds to apps that can idle and to image apps: it takes port 8080, proxies every request to the app and records when the app last served one. It checks no tokens; `access.groups` are enforced at the gateway (see [Access control](/launching-apps/#access-control)). |
| `authz.image.repository` / `tag` / `pullPolicy` | `ghcr.io/nebari-dev/apps-pack/authz` / appVersion / `IfNotPresent` | The sidecar the operator adds to apps that can idle and to image apps: it takes port 8080, proxies every request to the app and records when the app last served one. It checks no tokens; `access.groups` are enforced at the gateway (see [Access control](/launching-apps/#access-control)). |
| `authz.resources` | `10m`/`32Mi` requests, `100m`/`64Mi` limits | The sidecar's requests/limits, on top of the app container's, in every pod that runs it. |

### Image registries
Expand Down Expand Up @@ -82,7 +82,7 @@ apps:

| Value | Default | Purpose |
|---|---|---|
| `operator.image.repository` / `tag` / `pullPolicy` | `ghcr.io/nebari-dev/apps-pack/apps-operator` / `""` (chart `appVersion`) / `IfNotPresent` | Operator image. |
| `operator.image.repository` / `tag` / `pullPolicy` | `ghcr.io/nebari-dev/apps-pack/operator` / `""` (chart `appVersion`) / `IfNotPresent` | Operator image. |
| `operator.replicaCount` | `1` | Replicas; more than one needs `leaderElection`. |
| `operator.leaderElection` | `true` | Lease-based leader election in the release namespace. |
| `operator.resources` | `500m/256Mi` (requests = limits) | Pod resources. |
Expand Down Expand Up @@ -112,7 +112,7 @@ namespace.
| Value | Default | Purpose |
|---|---|---|
| `api.enabled` | `true` | Deploy the REST API (the UI and MCP are thin clients over it). |
| `api.image.repository` / `tag` / `pullPolicy` | `ghcr.io/nebari-dev/apps-pack/apps-api` / `""` / `IfNotPresent` | API image. |
| `api.image.repository` / `tag` / `pullPolicy` | `ghcr.io/nebari-dev/apps-pack/api` / `""` / `IfNotPresent` | API image. |
| `api.replicaCount` | `1` | The API is stateless; more replicas are safe. |
| `api.auth.enabled` | `true` | Keycloak JWT bearer auth for the API and keycloak-js login in the UI. **Disable only for local development**; off, every caller is anonymous with full access. Rendering fails if it is off while `tls.enabled=true`, unless `api.auth.insecureAllowAnonymous` is set. |
| `api.auth.insecureAllowAnonymous` | `false` | Explicit opt-in to an unauthenticated API on a TLS install. Leave off. |
Expand All @@ -137,7 +137,7 @@ in).
| Value | Default | Purpose |
|---|---|---|
| `ui.enabled` | `true` | Deploy the web UI (also the nginx front door that proxies `/api/` and `/mcp`). |
| `ui.image.repository` / `tag` / `pullPolicy` | `ghcr.io/nebari-dev/apps-pack/apps-ui` / `""` / `IfNotPresent` | UI image. |
| `ui.image.repository` / `tag` / `pullPolicy` | `ghcr.io/nebari-dev/apps-pack/ui` / `""` / `IfNotPresent` | UI image. |
| `ui.replicaCount` | `1` | Replicas. |
| `ui.hostname` | `<appsDomain>` | Hostname the UI is served at. |
| `ui.title` | `Nebari Apps` | Browser-tab title. |
Expand All @@ -154,7 +154,7 @@ in).
| Value | Default | Purpose |
|---|---|---|
| `mcp.enabled` | `true` | Deploy the MCP server at `https://<ui hostname>/mcp` (proxied by the UI's nginx; no separate hostname). When off, `/mcp` returns 404. |
| `mcp.image.repository` / `tag` / `pullPolicy` | `ghcr.io/nebari-dev/apps-pack/apps-mcp` / `""` / `IfNotPresent` | MCP image. |
| `mcp.image.repository` / `tag` / `pullPolicy` | `ghcr.io/nebari-dev/apps-pack/mcp` / `""` / `IfNotPresent` | MCP image. |
| `mcp.replicaCount` | `1` | **Keep at 1**: handshake-era MCP sessions are held in process memory (authentication is per request and does not depend on them). |
| `mcp.oauth.clientId` | `""` | Public Keycloak client MCP clients sign in with ([MCP authentication](/mcp/#the-sign-in-client)); create it in Keycloak first. The chart adds it to the allowed clients of apps-api and apps-mcp, and the UI's `claude mcp add` command passes it as `--client-id`. Empty: MCP clients cannot sign in on their own and need a token from elsewhere. |
| `mcp.oauth.callbackPort` | `33418` | The MCP client's OAuth callback port (`--callback-port`); the client's redirect URI must be `http://localhost:<port>/callback`. |
Expand Down
9 changes: 9 additions & 0 deletions docs/src/content/docs/upgrading.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,15 @@ Status fields in the backup are ignored; the operator recomputes them.

## Version notes

### Upgrading to the shorter image names

The images dropped their `apps-` prefix: `ghcr.io/nebari-dev/apps-pack/apps-operator` is now
`ghcr.io/nebari-dev/apps-pack/operator`, and the same for `authz`, `api`, `ui` and `mcp`
(on Quay, `quay.io/nebari/apps-pack-operator` and so on). The chart's defaults follow, so
nothing changes unless you set `*.image.repository` yourself, for example to mirror the
images: point those values at the new names. Images built before the rename stay at the old
paths; new tags are published only under the new ones.

### Upgrading to gateway group enforcement

**Breaking:** `access.groups` are enforced at the gateway by nebari-operator
Expand Down
2 changes: 1 addition & 1 deletion operator/internal/controller/authz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
appsv1alpha1 "github.com/nebari-dev/nebari-apps-pack/operator/api/v1alpha1"
)

const testAuthzImage = "ghcr.io/nebari-dev/apps-pack/apps-authz:test"
const testAuthzImage = "ghcr.io/nebari-dev/apps-pack/authz:test"

func restrictedApp(app *appsv1alpha1.App, groups []string) *appsv1alpha1.App {
app.Spec.Access.Public = false
Expand Down
Loading