| name | ubuntu-helm-creator |
| description | 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. |
| argument-hint | <chart-name> [[<host>/]<repo>/<name>:<tag>] [<reference-chart>] |
Ubuntu Helm Creator
Generate Kubernetes Helm charts backed by Canonical Ubuntu OCI images (rocks).
Modes
Prerequisites
Instructions
Step 0 — Determine mode
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"
Zero-to-One — Build a new chart from scratch
1. Resolve the rock image
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>
2. Understand the rock's runtime
If the entrypoint is pebble (most common):
Read the Pebble documentation to understand runtime behavior:
- Overview:
https://raw.githubusercontent.com/canonical/pebble/refs/heads/master/docs/index.md
- CLI commands (including
pebble enter): https://raw.githubusercontent.com/canonical/pebble/refs/heads/master/docs/reference/cli-commands.md
- Layer specification:
https://raw.githubusercontent.com/canonical/pebble/refs/heads/master/docs/reference/layer-specification.md
Obtain the Pebble plan (services, commands, checks) from the running image:
docker run --rm <repo>/<name>:<tag> plan
docker run --rm <repo>/<name>:<tag> \; plan
Use the plan to derive:
- Services → Deployment container args / command
- Checks → Kubernetes liveness/readiness probes (see Pebble-wired probes)
3. Generate the chart
Invoke the helm-generator skill from pantheon-ai/helm-toolkit. Follow its instructions and then apply the Canonical overrides below.
Canonical overrides (authoritative)
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>
tag: "<channel-track>"
digest: ""
pullPolicy: IfNotPresent
The 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/containers
Where <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:
- the new chart's
Chart.yaml has a unique name:
- the new chart's configuration follows the same conventions as the existing charts (e.g.,
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: RuntimeDefault
If 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).
Pebble-wired probes
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: 5
WARNING: If the plan defines no checks:, pebble health succeeds 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 using pebble health. When there are no checks, probe the service
directly instead — for a networked service, a tcpSocket probe on its port
needs no in-container tooling and only succeeds once the app binds the port:
readinessProbe:
tcpSocket:
port: <service-port-name>
LICENSE file
IF requested, add a LICENSE file in the <chart-path>.
4. Add tests
-
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
restore: |
helm uninstall "$HELM_RELEASE" --wait || true
-
If, 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)
5. Validate
Invoke skill: ubuntu-helm-validator.
→ On failure: apply the Failure and Retry Protocol (max 5 attempts).
6. Document
Invoke skill: ubuntu-helm-docs with CHART_DIR=<chart-path>
Feature Development — Add features to an existing chart
Pre-condition: chart already scaffolded (via "Zero-to-One" mode, or is pre-existing).
1. Analyse the existing target chart
Invoke skill: ubuntu-helm-analyzer with the existing chart path.
2. Collect the new features
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).
3. Inject features one by one
For each applicable feature in order:
- Read the feature's source template(s) from the reference chart
- Adapt them to the working chart's conventions:
- Replace hardcoded names with
{{ include "<chart>.fullname" . }} references
- Replace hardcoded labels with
{{ include "<chart>.labels" . | nindent N }}
- Ensure all values are exposed in
values.yaml with sensible defaults
- Apply PSS-Restricted security context if the feature adds a container
- Write the adapted template(s) to the working chart's
templates/ directory
- Update
values.yaml — add new keys for this feature, with defaults that keep the feature disabled by default (e.g., ingress.enabled: false)
- Update
values.schema.json ONLY if it exists already, adding the JSON Schema entries for the new values keys
- Do NOT modify
Chart.yaml, _helpers.tpl (unless adding new named template needed for this feature), or any existing template that was already passing tests
- Update the chart's tests. Add or modify
<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 feature
- Validate the updated chart by invoking the skill ubuntu-helm-validator
- On failure apply Failure and Retry Protocol (max 5 attempts per feature)
- If still failing after 5 attempts:
- Roll back all changes for this feature
- Record failure reason for the final report
- Continue to next feature
- On success:
- Update the README of the updated chart by invoking the skill ubuntu-helm-docs with
CHART_DIR=<chart-path>
Final Report
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>
Success Criteria
Failure and Retry Protocol
On any skill failure (lint error, test failure, non-zero exit):
- Read the full error output — do not guess the root cause
- Identify the specific failing line or check
- Apply a targeted fix to the chart or template
- Retry the failing skill
- If the identical error recurs, try a different approach — do not repeat the same fix