Generate Kubernetes Helm charts backed by Canonical Ubuntu OCI images (rocks). Use when creating a new Helm chart from scratch for a rock; achieving feature parity with an upstream chart (Bitnami, ArtifactHub, GitHub); adding features to an existing chart. Trigger phrases are "generate helm chart", "create helm chart based on upstream chart", "helm chart for app", "feature parity with upstream", "add feature to chart", "scaffold chart", "ubuntu rock helm", "rock-backed chart". Produces chart scaffolding, values.yaml with image.digest, PSS-Restricted security defaults, Pebble-wired probes, tests, and README.md.
78
—
Does it follow best practices?
Impact
—
No eval scenarios have been run
Advisory
Suggest reviewing before use
Generate Kubernetes Helm charts backed by Canonical Ubuntu OCI images (rocks).
| Mode | Trigger | Example |
|---|---|---|
| Zero-to-One | New chart from a rock | "Generate a Helm chart for ubuntu/nginx:1.28-26.04_stable" |
| Feature Development | Add features chart | "Add missing features from https://artifacthub.io/packages/helm/bitnami/nginx into the Nginx chart" or "Add env var FOO to chart Nginx" or "Create a chart for ubuntu/nginx:1.28-26.04_stable based on https://artifacthub.io/packages/helm/bitnami/nginx" |
helm-generator skill (from https://github.com/pantheon-org/tekhne/tree/main/skills/ci-cd/helm/generator) must be installed. If missing: npx tessl i pantheon-ai/helm-toolkit@0.1.0 --skill helm-generatorscripts/setup.sh. If any tools are missing, they can be installed by running scripts/setup.sh --install.chart already exists?
├── YES → Feature Development Mode → follow "Feature Development"
└── NO → reference chart provided?
├── YES → Feature Development Mode → follow "Zero-to-One", then "Feature Development"
└── NO → Zero-to-One Mode → follow "Zero-to-One"Rock image format: [<host>/]<repo>/<name>:<tag>
<host> defaults to docker.io; <repo> defaults to ubuntu
Confirm the image exists with scripts/inspect-rock.sh inspect [<host>/]<repo>/<name>:<tag>
Get the rock's OCI entrypoint with scripts/inspect-rock.sh entrypoint [<host>/]<repo>/<name>:<tag>
Get the rock's metadata, including the OCI annotation for its description, with scripts/inspect-rock.sh metadata [<host>/]<repo>/<name>:<tag>
Inspect the rock's filesystem layout with scripts/inspect-rock.sh filesystem [<host>/]<repo>/<name>:<tag>
If the entrypoint is pebble (most common):
Read the Pebble documentation to understand runtime behavior:
https://raw.githubusercontent.com/canonical/pebble/refs/heads/master/docs/index.mdpebble enter): https://raw.githubusercontent.com/canonical/pebble/refs/heads/master/docs/reference/cli-commands.mdhttps://raw.githubusercontent.com/canonical/pebble/refs/heads/master/docs/reference/layer-specification.mdObtain the Pebble plan (services, commands, checks) from the running image:
# If OCI ENTRYPOINT is "pebble enter":
docker run --rm <repo>/<name>:<tag> plan
# If OCI ENTRYPOINT has "--args":
docker run --rm <repo>/<name>:<tag> \; planUse the plan to derive:
Invoke the helm-generator skill from pantheon-ai/helm-toolkit. Follow its instructions and then apply the Canonical overrides below.
When generating a chart, always apply the toolkit's generation workflow first, then layer on these Canonical overrides.
values.yaml — required image block:
image:
repository: [<host>/]<repo>/<name> # full path, no tag
tag: "<channel-track>" # mutable tag
digest: "" # sha256 pin; empty = unpinned
pullPolicy: IfNotPresentThe image.digest value should be set. This value can be retrieved by running scripts/inspect-rock.sh digest <repo>/<name>:<tag>.
Deployment image reference (digest wiring):
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}{{- if .Values.image.digest }}@{{ .Values.image.digest }}{{- end }}"Chart.yaml fields:
description: "Helm chart for <chart-name> backed by the Ubuntu rock `<rock-name>`.\n\n<rock-description>"
home: https://ubuntu.com/containersWhere <rock-description> comes from:
scripts/inspect-rock.sh metadata <repo>/<name>:<tag> | jq '."org.opencontainers.image.description"'Charts' consistency:
If the chart's folder (i.e. the <chart-path> parent folder) contains other charts,
invoke the skill ubuntu-helm-analyzer to analyze the existing charts and
ensure that:
Chart.yaml has a unique name:image: block, values.schema.json structure, field naming, etc.)Keep Pebble state in memory:
Ensure every container deployment has the environment variable PEBBLE_PERSIST set to never. Otherwise the PSS security context below won't be able to apply readOnlyRootFilesystem: true.
Security context (PSS-Restricted — all Deployment/StatefulSet templates):
Always strive for:
securityContext:
runAsNonRoot: true
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: [ALL]
seccompProfile:
type: RuntimeDefaultIf needing to adjust the security context after validating the chart, then justify any deviation from the above with an inline comment (e.g., if a rock is using root as the default OCI user and cannot be run as nonroot, then you can make an exception and allow runAsNonRoot: false).
Probes must reflect whether the application actually started — not merely
whether the Pebble daemon is up. This distinction matters because helm install --wait
(and the integration test) gate on the readiness probe: a probe that
reports ready too eagerly will let a broken deployment pass as healthy.
When the rock's Pebble plan defines health checks:, map them to Kubernetes
probes via [/bin/pebble, health] — Pebble then exits non-zero if a check fails:
livenessProbe:
exec:
command: [/bin/pebble, health]
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command: [/bin/pebble, health]
initialDelaySeconds: 5
periodSeconds: 5WARNING: If the plan defines no
checks:,pebble healthsucceeds as soon as the Pebble daemon is running, even while the actual service crash-loops. Inspect the plan (pebble plan/ the baked layer) and confirm checks exist before usingpebble health. When there are no checks, probe the service directly instead — for a networked service, atcpSocketprobe on its port needs no in-container tooling and only succeeds once the app binds the port:readinessProbe: tcpSocket: port: <service-port-name>
LICENSE fileIF requested, add a LICENSE file in the <chart-path>.
If not yet present, generate <chart-path>/templates/tests/test-connection.yaml with a test that connects to the deployed service and verifies the application actually started (e.g. open a TCP connection to the service's port, or perform an application-level request). Do not merely run pebble health in a throwaway pod — that does not exercise the deployed workload and can pass even when the app failed to start.
Add a <chart-path>/task.yaml file. This is a Spread task definition (see https://github.com/canonical/spread), and should look like this:
summary: Spread tests for the <chart-name> Helm chart
environment:
HELM_RELEASE: test-<chart-name>
HELM_REGISTRY: '$(HOST: echo "${HELM_REGISTRY:-}")'
HELM_REGISTRY_USERNAME: '$(HOST: echo "${HELM_REGISTRY_USERNAME:-}")'
HELM_REGISTRY_PASSWORD: '$(HOST: echo "${HELM_REGISTRY_PASSWORD:-}")'
execute: |
# Common file from $PROJECT_ROOT/spread/lib/
helm-test
# <OPTIONAL ADDITIONAL TESTS>
restore: |
helm uninstall "$HELM_RELEASE" --wait || trueIf, and ONLY IF requested by the user, add a <chart-path>/policy.rego file with OPA policies that are specific for the chart
Add a <chart-path>/tests/ directory with YAML test files for Helm unittest (see https://github.com/helm-unittest/helm-unittest)
Invoke skill: ubuntu-helm-validator.
→ On failure: apply the Failure and Retry Protocol (max 5 attempts).
Invoke skill: ubuntu-helm-docs with CHART_DIR=<chart-path>
Pre-condition: chart already scaffolded (via "Zero-to-One" mode, or is pre-existing).
Invoke skill: ubuntu-helm-analyzer with the existing chart path.
IF a reference chart is provided: the goal is to reach parity with the upstream chart. Invoke skill: ubuntu-helm-analyzer with the reference chart URL/path. Skip features marked deprecated (prompt user if in doubt).
For each applicable feature in order:
{{ include "<chart>.fullname" . }} references{{ include "<chart>.labels" . | nindent N }}values.yaml with sensible defaultstemplates/ directoryvalues.yaml — add new keys for this feature, with defaults that keep the feature disabled by default (e.g., ingress.enabled: false)values.schema.json ONLY if it exists already, adding the JSON Schema entries for the new values keysChart.yaml, _helpers.tpl (unless adding new named template needed for this feature), or any existing template that was already passing tests<chart-path>/templates/tests/test-connection.yaml, <chart-path>/task.yaml, <chart-path>/policy.rego, and/or <chart-path>/tests/ files as needed to cover the new featureCHART_DIR=<chart-path>Produce after any workflow completes. Deliver as a chat message (interactive) or PR comment.
## Helm Chart Generation Report
**Rock image**: docker.io/<repo>/<name>:<tag>
**Reference chart**: <name> v<version> — <url> (if applicable)
### Feature Summary
| Feature | Status | Notes |
| -------------- | ------------------- | ----------- |
| `<feature-id>` | succeeded / dropped | Drop reason |
**Totals**: Succeeded: N | Dropped: M | Total: N+M
### Dropped Feature Details
<for each dropped feature: final error + what was tried><chart-path>/ contains all required files, including templates/tests/test-connection.yaml, tests/ and task.yamlhelm-validator passes all stages (lint, schema, security, dry-run)<chart-path>/README.md documents 100% of valuesimage.digest present in values.yamlimage: template uses digest-override wiringsecurityContextOn any skill failure (lint error, test failure, non-zero exit):
565995c
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.