Skip to content

[FEATURE] Support custom kernel deployment in course github action #23

@ghost

Description

Feature Description

Add a new action-type input (article / kernel) to action.yaml so that mode: create and mode: update apply independently to both article deployment and custom kernel deployment. When action-type: kernel is set, the action builds a Docker image, pushes it to Google Artifact Registry (GCR), and registers it with Jupyter Enterprise Gateway (EG) — so instructors can ship a course-specific Python environment from the same action they already use to publish courses.

action-type: article (default — existing behaviour unchanged):

  • mode: create → create a new course or blog post
  • mode: update → update an existing course or blog post

action-type: kernel (new):

  • mode: create → build image + push to GCR + register new kernelspec on EG host + validate
  • mode: update → rebuild image + push new tag + patch existing kernelspec + validate

action-type defaults to article so every existing workflow continues working with zero changes.


Motivation

Kernels that power qBook course notebooks are currently deployed manually to the EG host — there is no CI pipeline, no version history in GCR, and no way for an instructor to self-serve a custom environment for their course.

This creates three problems:

No per-course kernel isolation. All courses share the kernels that were manually deployed. An instructor who needs a specific version of qiskit or pennylane cannot get a custom environment without server-level access.

No kernel availability check at publish time. The course publish step does not verify that kernels referenced in course.json are accessible to the instructor before the course goes live. Students discover a missing kernel mid-session, not at publish time.

No create vs update distinction for kernels. Creating a kernel for the first time (new kernelspec directory + EG restart) is a meaningfully different operation from updating one (patch image_name in existing kernel.json, no restart). There is currently no way to express this difference in the action.


Proposed Solution

Input structure

The new action-type input sits at the top level. Everything else flows from it:

action-type: article | kernel          ← new top-level switch (default: article)
  ├── mode: create | update            ← applies to both article and kernel
  ├── article-type: course | blog      ← only used when action-type is article
  └── kernel-name, gcr-*, eg-*, …     ← only used when action-type is kernel

create vs update for kernels

  mode: create mode: update
Build & push image Full build, commit SHA tag + :latest Rebuild with new commit SHA tag + :latest
kernelspec on EG host Install new directory via jupyter-docker-spec install Patch image_name in existing kernel.json only (SSH Python)
EG restart Yes — new kernelspec must be discovered No — running sessions unaffected; new starts use new image
Validate + smoke test Yes Yes

New example workflows

Two new files added to examples/ alongside the existing workflow.yml:

examples/kernel-create.yml — triggered manually (workflow_dispatch), used once when setting up a new kernel for the first time.

examples/kernel-update.yml — triggered automatically on push to main when kernel/** files change, used for ongoing kernel updates.

Implementation roadmap

  • Add action-type input + guard existing steps with if: inputs.action-type != 'kernel'
  • Add validate_kernel_inputs.py + Kernel Stage 1 step
  • Add GCP auth step + build_push_kernel.py + Kernel Stage 3
  • Add register_kernelspec.py (create) + patch_kernelspec.py (update) + Kernel Stage 4a/4b
  • Add validate_kernel_availability.py + smoke_test_kernel.py + Kernel Stage 5/6
  • Add separated emit-outputs steps + update outputs: block in action.yaml
  • Add examples/kernel-create.yml and examples/kernel-update.yml
  • Update examples/README.md with kernel mode quick start section

The full action.yaml and script implementations are attached to this issue.


Alternatives Considered

Keep a single mode input with values like kernel-create / kernel-update — rejected. Combining two concepts into one string breaks the existing create/update mental model that users already know from the article workflow and makes the input harder to document.

Separate action.yaml file for kernel deployment — rejected. A second action would duplicate the shared setup steps (UV install, Python, secret validation, notifications) and require instructors to maintain two action references in their course repos.

Validate kernels at runtime in qBook rather than at publish time — rejected. Runtime checks mean a student discovers a missing kernel mid-session. Failing fast at publish time ensures instructors fix the problem before any student is affected.

Implicit action-type detection from which inputs are present — rejected. Explicit is better for CI configuration. action-type defaulting to article preserves full backward compatibility without guesswork.


Additional Context

kernel-display-name must exactly match kernelId in course.json and display_name in kernel.json on the EG host. This is the string qBook sends to EG when a student opens a notebook. The validate_kernel_availability.py step enforces this via the qBraid API before the step can succeed.

mode: update does not restart EG. Running kernel sessions hold a reference to the container started with the previous image. Only new sessions use the new image. This is intentional — it avoids interrupting active students during a kernel update.

Pre-pulling the image is handled inside the deploy scripts. docker pull runs on the EG host immediately after the kernelspec is written, so the first student kernel start after a deploy is never a cold pull from GCR.

The EG host VM needs roles/artifactregistry.reader on its attached GCP service account so it can docker pull from Artifact Registry without manual credential management.

See attached files for the full action.yaml implementation and new script source code.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions