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
14 changes: 14 additions & 0 deletions .github/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
changelog:
categories:
- title: Features
labels: ["type: enhancement 💅🏼", "type: exploration 🔮"]
- title: Bug Fixes
labels: ["type: bug 🐛"]
- title: Documentation
labels: ["area: documentation 📖"]
- title: Dependencies
labels: ["area: dependencies 📦"]
- title: Maintenance
labels: ["type: maintenance 🛠", "area: ci 👷🏽‍♀️"]
- title: Other Changes
labels: ["*"]
204 changes: 55 additions & 149 deletions .github/workflows/build-images.yaml
Original file line number Diff line number Diff line change
@@ -1,156 +1,62 @@
name: Build Images
name: Build Docker Images

on:
push:
branches: [main]
tags: ["v*"]
paths:
- "operator/**"
- "key-manager/**"
- "frontend/**"
- "model-downloader/**"
- ".github/workflows/build-images.yaml"
# The release workflow pins images to sha-<release-commit>, so the
# release commit (a Chart.yaml bump) must produce images with that
# sha. Building here on the bump ensures they exist. This runs in
# parallel with the release workflow (no hard gate in v1), so images
# are published around the same time the chart becomes consumable.
- "charts/nebari-llm-serving/Chart.yaml"
pull_request:
paths:
- "operator/**"
- "key-manager/**"
- "frontend/**"
- "model-downloader/**"
workflow_dispatch:

env:
REGISTRY: ghcr.io
IMAGE_PREFIX: ghcr.io/nebari-dev/nebari-llm-serving-pack

jobs:
build-operator:
name: Build and push operator image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

- name: Log in to GHCR
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata
id: meta
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
with:
images: ${{ env.IMAGE_PREFIX }}/operator
tags: |
type=sha
type=ref,event=branch
type=semver,pattern=v{{version}}
type=raw,value=latest,enable={{is_default_branch}}

- name: Build and push operator image
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
with:
context: operator/
file: operator/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

build-model-downloader:
name: Build and push model-downloader image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

- name: Log in to GHCR
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata
id: meta
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
with:
images: ${{ env.IMAGE_PREFIX }}/model-downloader
tags: |
type=sha
type=ref,event=branch
type=semver,pattern=v{{version}}
type=raw,value=latest,enable={{is_default_branch}}

- name: Build and push model-downloader image
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
with:
context: model-downloader/
file: model-downloader/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

build-key-manager:
name: Build and push key-manager image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

- name: Log in to GHCR
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata
id: meta
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
with:
images: ${{ env.IMAGE_PREFIX }}/key-manager
tags: |
type=sha
type=ref,event=branch
type=semver,pattern=v{{version}}
type=raw,value=latest,enable={{is_default_branch}}

- name: Build and push key-manager image
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
with:
context: .
file: key-manager/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

build-frontend:
name: Build and push frontend image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

- name: Log in to GHCR
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata
id: meta
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
with:
images: ${{ env.IMAGE_PREFIX }}/frontend
tags: |
type=sha
type=ref,event=branch
type=semver,pattern=v{{version}}
type=raw,value=latest,enable={{is_default_branch}}

- name: Build and push frontend image
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
with:
context: frontend/
file: frontend/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
operator:
uses: nebari-dev/.github/.github/workflows/pack-build-image.yaml@v1
with:
image: operator
context: operator/
push: ${{ github.event_name != 'pull_request' }}
secrets:
QUAY_TOKEN: ${{ secrets.QUAY_TOKEN }}

model-downloader:
uses: nebari-dev/.github/.github/workflows/pack-build-image.yaml@v1
with:
image: model-downloader
context: model-downloader/
push: ${{ github.event_name != 'pull_request' }}
secrets:
QUAY_TOKEN: ${{ secrets.QUAY_TOKEN }}

key-manager:
uses: nebari-dev/.github/.github/workflows/pack-build-image.yaml@v1
with:
image: key-manager
context: "."
dockerfile: key-manager/Dockerfile
push: ${{ github.event_name != 'pull_request' }}
secrets:
QUAY_TOKEN: ${{ secrets.QUAY_TOKEN }}

frontend:
uses: nebari-dev/.github/.github/workflows/pack-build-image.yaml@v1
with:
image: frontend
context: frontend/
push: ${{ github.event_name != 'pull_request' }}
secrets:
QUAY_TOKEN: ${{ secrets.QUAY_TOKEN }}
21 changes: 21 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Release Chart

on:
push:
branches: [main]
paths:
- "charts/nebari-llm-serving/Chart.yaml"

jobs:
release:
uses: nebari-dev/.github/.github/workflows/pack-release.yaml@v1
with:
chart-path: charts/nebari-llm-serving
chart-name: nebari-llm-serving
tag-paths: |
operator.image.tag
keyManager.image.tag
frontend.image.tag
modelDownloader.image.tag
secrets:
NEBARI_HELM_REPO_TOKEN: ${{ secrets.NEBARI_HELM_REPO_TOKEN }}
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,10 +236,10 @@ Admin applies LLMModel CR

| Image | Description |
|-------|-------------|
| `ghcr.io/nebari-dev/nebari-llm-serving-pack/operator` | LLM operator - reconciles LLMModel CRDs |
| `ghcr.io/nebari-dev/nebari-llm-serving-pack/key-manager` | Key manager REST API |
| `ghcr.io/nebari-dev/nebari-llm-serving-pack/frontend` | LLM serving pack React UI (nginx) |
| `ghcr.io/nebari-dev/nebari-llm-serving-pack/model-downloader` | Model download init container (distroless, pixi-managed) |
| `ghcr.io/nebari-dev/llm-serving-pack/operator` | LLM operator - reconciles LLMModel CRDs |
| `ghcr.io/nebari-dev/llm-serving-pack/key-manager` | Key manager REST API |
| `ghcr.io/nebari-dev/llm-serving-pack/frontend` | LLM serving pack React UI (nginx) |
| `ghcr.io/nebari-dev/llm-serving-pack/model-downloader` | Model download init container (distroless, pixi-managed) |

### Infrastructure requirements

Expand Down Expand Up @@ -281,7 +281,7 @@ make teardown

### Key manager UI

The key manager web UI is a [React](https://react.dev) + TypeScript app (Vite, Tailwind, shadcn/ui) in [`frontend/`](frontend/). In production it ships as its own nginx image (`ghcr.io/nebari-dev/nebari-llm-serving-pack/frontend`) that serves the SPA and proxies `/api` to the API-only key-manager; it is not embedded in the Go binary. For a one-command dev loop that needs **no Keycloak**:
The key manager web UI is a [React](https://react.dev) + TypeScript app (Vite, Tailwind, shadcn/ui) in [`frontend/`](frontend/). In production it ships as its own nginx image (`ghcr.io/nebari-dev/llm-serving-pack/frontend`) that serves the SPA and proxies `/api` to the API-only key-manager; it is not embedded in the Go binary. For a one-command dev loop that needs **no Keycloak**:

```bash
# One-time: copy dev/.env.example to dev/.env and set OPENROUTER_API_KEY
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ spec:
# ClusterIP. This is the service the NebariApp targets; auth is handled
# client-side by keycloak-js (PKCE/S256) and validated by the key-manager.
- name: frontend
image: {{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag | default .Chart.AppVersion }}
image: {{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag }}
imagePullPolicy: {{ .Values.frontend.image.pullPolicy | default "IfNotPresent" }}
ports:
- name: http
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ spec:
serviceAccountName: {{ include "nebari-llm-serving.fullname" . }}-key-manager
containers:
- name: key-manager
image: {{ .Values.keyManager.image.repository }}:{{ .Values.keyManager.image.tag | default .Chart.AppVersion }}
image: {{ .Values.keyManager.image.repository }}:{{ .Values.keyManager.image.tag }}
imagePullPolicy: {{ .Values.keyManager.image.pullPolicy | default "IfNotPresent" }}
ports:
- containerPort: 8080
Expand Down
4 changes: 3 additions & 1 deletion charts/nebari-llm-serving/templates/operator-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ spec:
serviceAccountName: {{ include "nebari-llm-serving.fullname" . }}-operator
containers:
- name: manager
image: {{ .Values.operator.image.repository }}:{{ .Values.operator.image.tag | default .Chart.AppVersion }}
image: {{ .Values.operator.image.repository }}:{{ .Values.operator.image.tag }}
imagePullPolicy: {{ .Values.operator.image.pullPolicy | default "IfNotPresent" }}
env:
- name: LLM_BASE_DOMAIN
Expand Down Expand Up @@ -48,6 +48,8 @@ spec:
value: {{ .Values.defaults.serving.image | quote }}
- name: LLM_DEFAULT_EPP_IMAGE
value: {{ .Values.defaults.epp.image | quote }}
- name: LLM_MODEL_DOWNLOADER_IMAGE
value: "{{ .Values.modelDownloader.image.repository }}:{{ .Values.modelDownloader.image.tag }}"
- name: LLM_DEFAULT_STORAGE_CLASS_NAME
value: {{ .Values.defaults.storage.storageClassName | quote }}
- name: LLM_CLUSTER_ISSUER_NAME
Expand Down
32 changes: 18 additions & 14 deletions charts/nebari-llm-serving/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,9 @@ keyManager:
groups:
- llm
image:
repository: ghcr.io/nebari-dev/nebari-llm-serving-pack/key-manager
# tag defaults to .Chart.AppVersion when empty so the chart version and
# the image version always move together. Override only when testing a
# specific image build (e.g. tag: 0.1.0-alpha.2 or tag: sha-abc1234).
tag: ""
repository: ghcr.io/nebari-dev/llm-serving-pack/key-manager
# Floating on main; pinned to a build sha at release time (see cicd-and-releasing).
tag: "latest"
pullPolicy: Always
auditInterval: 5m
oidcUserinfoURL: ""
Expand Down Expand Up @@ -176,10 +174,9 @@ frontend:
# Set enabled=false to skip the frontend (e.g. an API-only test install).
enabled: true
image:
repository: ghcr.io/nebari-dev/nebari-llm-serving-pack/frontend
# tag defaults to .Chart.AppVersion when empty so the chart and image
# versions move together. Override to pull a specific build.
tag: ""
repository: ghcr.io/nebari-dev/llm-serving-pack/frontend
# Floating on main; pinned to a build sha at release time (see cicd-and-releasing).
tag: "latest"
pullPolicy: Always
# port nginx listens on. Must be > 1024 - the container runs as non-root
# (UID 101) with all capabilities dropped and cannot bind privileged ports.
Expand All @@ -202,13 +199,20 @@ frontend:

operator:
image:
repository: ghcr.io/nebari-dev/nebari-llm-serving-pack/operator
# tag defaults to .Chart.AppVersion when empty so the chart version and
# the image version always move together. Override only when testing a
# specific image build (e.g. tag: 0.1.0-alpha.2 or tag: sha-abc1234).
tag: ""
repository: ghcr.io/nebari-dev/llm-serving-pack/operator
# Floating on main; pinned to a build sha at release time (see cicd-and-releasing).
tag: "latest"
pullPolicy: Always

# modelDownloader - the init/job image the operator uses to fetch model
# weights before a serving Deployment starts. Rendered into the operator's
# LLM_MODEL_DOWNLOADER_IMAGE env var (operator-deployment.yaml) rather than
# hardcoded, so it can be re-pinned without rebuilding the operator image.
modelDownloader:
image:
repository: ghcr.io/nebari-dev/llm-serving-pack/model-downloader
# Floating on main; pinned to a build sha at release time (see cicd-and-releasing).
tag: "latest"

defaults:
serving:
Expand Down
Loading
Loading