Skip to main content

OCI Registries and Distribution

In Lessons 1-6, you built Helm charts locally: templating logic, dependency composition, hooks, and validation. These charts work perfectly in your development environment. But when you need to share charts across teams, use them in production, or compose them as dependencies in other charts, storing them as directories on your laptop doesn't scale.

Helm solved this by adopting OCI (Open Container Initiative) standards. The same registry that stores your Docker images—Docker Hub, Amazon ECR, Azure Container Registry—now stores your Helm charts. This unifies distribution: one registry for both container images AND charts, one authentication mechanism, one versioning strategy.

This lesson teaches you how to package charts into distributable artifacts, authenticate with registries, push and pull charts, consume charts from OCI URLs, and implement a chart versioning strategy aligned with semantic versioning. By the end, you'll publish your AI agent's Helm chart to a shared registry where teammates and production systems can discover and install it.


What You'll Learn

This lesson covers the complete workflow for distributing Helm charts via OCI-compliant registries.

Concept Groups

ConceptsTopics CoveredWhy It Matters
OCI Fundamentals (1-3)OCI adoption rationale, packaging with helm package, OCI URL formatUnderstand why Helm moved to OCI and how chart distribution works
Registry Operations (4-6)Authentication (helm registry login), pushing charts (helm push), pulling charts (helm pull)Execute the complete publish-and-consume workflow
Versioning & Integration (7-9)Semantic versioning for charts, installing from OCI URLs, OCI dependencies in Chart.yamlApply production-grade versioning and chart composition

Prerequisites

Before starting this lesson, you should have:

  • ✅ Completed Lessons 1-6 (chart templating, dependencies, hooks, validation)
  • ✅ A working Helm chart (from previous lessons or your own project)
  • ✅ Docker CLI installed (for registry authentication)
  • ✅ Access to an OCI registry (Docker Hub account, AWS ECR, or local registry)
  • ✅ Kubernetes cluster running (Minikube, Kind, or cloud cluster) for installation testing

Time Estimate

  • Reading & Concepts: 25 minutes
  • Hands-On Exercises: 20 minutes
  • Total: ~45 minutes

Concept 1: OCI Basics — Why Helm Adopted OCI for Charts

Before you can push a chart to a registry, understand what OCI is and why Helm moved from Helm repositories (custom format) to OCI.

The Problem: Two Distribution Systems

Before OCI adoption (Helm 2-early Helm 3):

  • Container images → Docker registries (docker.io, gcr.io, etc.)
  • Helm charts → Helm repositories (custom format, separate infrastructure)

Teams maintained two completely different distribution systems with different credentials, different APIs, different versioning semantics.

After OCI adoption (Helm 3.7+):

  • Container images → OCI registries (same)
  • Helm charts → OCI registries (same)

One registry. One authentication model. One API.

What is OCI?

OCI (Open Container Initiative) is a standardized format for packaging and distributing artifacts. Originally designed for container images, OCI is now a general-purpose artifact format that can store anything: images, charts, software bills of materials (SBOMs), vulnerability scan results.

An OCI registry is any system that follows the OCI Distribution Spec:

  • Docker Hub
  • Amazon ECR (Elastic Container Registry)
  • Azure Container Registry
  • Google Artifact Registry
  • Private registries (Harbor, Artifactory)
  • Local registries (for development)

A Helm chart stored in OCI format is just an artifact in these registries, no different from a Docker image.

How Helm Charts Fit into OCI

When you helm package a chart, Helm:

  1. Creates a .tgz archive of your chart directory
  2. Wraps it in OCI metadata (content type: application/vnd.cncf.helm.chart.v1.tar+gzip)
  3. Stores it in the registry with a tag (like myapp-chart:1.2.3)

A Helm chart in a registry looks like this:

docker.io/myusername/myapp-chart:1.2.3

It's identical in structure to a Docker image reference:

docker.io/myusername/myapp-image:1.2.3

Same registry. Same tagging system. Same authentication.


Concept 2: Packaging Charts — helm package

Before pushing to a registry, you must package your chart into a .tgz archive.

Creating a Chart Archive

Start with an existing chart (or the example from Lesson 1):

# Navigate to your chart directory
cd path/to/myapp-chart

# View the chart structure
ls -la
# Output:
# Chart.yaml
# values.yaml
# templates/
# values-dev.yaml
# values-prod.yaml

# Package the chart
helm package .

# Output:
# Successfully packaged chart and saved it to: /Users/you/work/myapp-chart-1.2.3.tgz

Output:

$ cd myapp-chart && helm package .
Successfully packaged chart and saved it to: /Users/username/myapp-chart-1.2.3.tgz

$ ls -lh myapp-chart-1.2.3.tgz
-rw-r--r-- 1 username staff 4.2K Nov 27 10:30 myapp-chart-1.2.3.tgz

What helm package Does

  1. Validates the chart (runs helm lint internally)
  2. Creates a .tgz archive containing:
    • Chart.yaml
    • values.yaml
    • templates/
    • All other chart files
  3. Names the file: {chart-name}-{version}.tgz
  4. Stores it in the current directory (unless -d specifies destination)

Version Matters

The filename comes from Chart.yaml:

---
apiVersion: v2
name: myapp-chart
version: 1.2.3
---

If you change version in Chart.yaml, helm package creates a new file:

# After changing Chart.yaml version to 1.3.0
helm package .
# Output: Successfully packaged chart and saved it to: myapp-chart-1.3.0.tgz

Each version is a separate artifact. This is how semantic versioning works: 1.2.3 represents major.minor.patch. When you fix a bug, bump patch (1.2.4). When you add backwards-compatible features, bump minor (1.3.0). When you make breaking changes, bump major (2.0.0).


Concept 3: OCI URL Format — How to Reference Charts

When you push a chart to an OCI registry, you reference it using oci:// URLs.

The OCI URL Pattern

oci://registry-host/repository/chart-name:version

Breaking this down:

ComponentExampleMeaning
oci://ProtocolTells Helm this is an OCI artifact (not HTTP)
registry-hostdocker.ioRegistry domain
repositorymyusername/chartsPath in registry (like Docker image paths)
chart-namemyapp-chartName of the Helm chart
version1.2.3Semantic version tag

Examples

Public chart on Docker Hub:

oci://docker.io/myusername/agent-chart:1.0.0

Private chart on Amazon ECR:

oci://123456789.dkr.ecr.us-east-1.amazonaws.com/charts/ml-pipeline:2.1.5

Local registry for development:

oci://localhost:5000/my-charts/scheduler:0.1.0

Compare to HTTP URLs

Before OCI, Helm used HTTP repository URLs (still supported):

# OLD way (Helm repository)
https://charts.example.com
helm repo add myrepo https://charts.example.com
helm install myrelease myrepo/myapp-chart --version 1.2.3

# NEW way (OCI)
oci://docker.io/myusername/myapp-chart:1.2.3
helm install myrelease oci://docker.io/myusername/myapp-chart --version 1.2.3

The OCI approach is simpler: no separate repository infrastructure, same registry as your images.


Concept 4: Authenticating with Registries — helm registry login

Before you push or pull charts from a private registry, authenticate using helm registry login.

Logging In to Docker Hub

# Authenticate with Docker Hub
helm registry login docker.io

# Prompt appears:
# Username: your-docker-username
# Password: your-docker-token

# Successfully logged in to docker.io

Output:

$ helm registry login docker.io
Username: myusername
Password: ****
Login Succeeded

$ cat ~/.docker/config.json | grep -A5 "docker.io"
"auths": {
"docker.io": {
"auth": "bXl1c2VybmFtZTpteS10b2tlbg=="
}
}

Using Docker Credentials

If you've already logged in with the Docker CLI:

# You've already run: docker login -u myusername
# Helm reads the same credentials

helm push myapp-chart-1.2.3.tgz oci://docker.io/myusername/
# Uses your existing Docker credentials automatically

Logging In to Private Registries

For Amazon ECR, Azure ACR, or other private registries:

# Amazon ECR
aws ecr get-login-password --region us-east-1 | helm registry login --username AWS --password-stdin 123456789.dkr.ecr.us-east-1.amazonaws.com

# Azure Container Registry
az acr login --name myregistry
helm registry login myregistry.azurecr.io

# Private registry (Artifactory, Harbor, etc.)
helm registry login artifactory.company.com

Output:

$ helm registry login 123456789.dkr.ecr.us-east-1.amazonaws.com
Username: AWS
Password: paste-ECR-token-here
Login Succeeded

Credentials Storage

Helm stores credentials in ~/.docker/config.json (same as Docker CLI). Each registry gets its own credential entry.


Concept 5: Pushing Charts — helm push

Once authenticated, push your packaged chart to an OCI registry using helm push.

Pushing to Docker Hub

# You've already packaged and authenticated
# Chart file: myapp-chart-1.2.3.tgz
# Authenticated with: helm registry login docker.io

# Push to your Docker Hub repository
helm push myapp-chart-1.2.3.tgz oci://docker.io/myusername/

# Output: Pushed: docker.io/myusername/myapp-chart:1.2.3

Output:

$ helm push myapp-chart-1.2.3.tgz oci://docker.io/myusername/
Pushed: docker.io/myusername/myapp-chart:1.2.3

$ # Verify it's in the registry (via Docker Hub web UI or CLI)
$ docker pull docker.io/myusername/myapp-chart:1.2.3
Error: manifest not found
# (Docker image protocol doesn't work; OCI charts aren't container images)

$ # But Helm can pull it
$ helm pull oci://docker.io/myusername/myapp-chart:1.2.3
Downloaded to myapp-chart-1.2.3.tgz

Pushing to Private Registries

# Push to Amazon ECR
helm push agent-chart-2.0.1.tgz oci://123456789.dkr.ecr.us-east-1.amazonaws.com/charts/

# Output: Pushed: 123456789.dkr.ecr.us-east-1.amazonaws.com/charts/agent-chart:2.0.1

# Push to Azure ACR
helm push scheduler-chart-1.5.0.tgz oci://myregistry.azurecr.io/

# Output: Pushed: myregistry.azurecr.io/scheduler-chart:1.5.0

Output:

$ helm push agent-chart-2.0.1.tgz oci://123456789.dkr.ecr.us-east-1.amazonaws.com/charts/
Pushed: 123456789.dkr.ecr.us-east-1.amazonaws.com/charts/agent-chart:2.0.1

# Verify with AWS CLI
$ aws ecr describe-images --registry-id 123456789 --repository-name charts
{
"imageDetails": [
{
"repositoryName": "charts",
"imageTags": ["agent-chart:2.0.1"],
"imageSizeInBytes": 4500,
"pushedAt": 2025-11-27T10:30:00.000Z
}
]
}

Chart Naming Convention

When you push, Helm separates chart name from version:

helm push myapp-chart-1.2.3.tgz oci://docker.io/myusername/
# Pushes to: docker.io/myusername/myapp-chart:1.2.3

# NOT:
# docker.io/myusername/myapp-chart-1.2.3:latest

The version goes into the tag (after the colon), not the image name. This matches Docker convention:

  • docker.io/myusername/myapp-image:1.2.3 (Docker image)
  • docker.io/myusername/myapp-chart:1.2.3 (Helm chart)

Checkpoint: OCI Fundamentals

You've learned the foundation of OCI-based chart distribution.

Quick Reference:

CommandPurposeExample
helm package .Create .tgz archive from chart directoryCreates myapp-chart-1.2.3.tgz
oci://registry/repo/chart:versionOCI URL formatoci://docker.io/username/myapp-chart:1.2.3
Chart versionFrom Chart.yaml version fieldDetermines filename and registry tag

Self-Check Questions:

  1. Why did Helm adopt OCI standards instead of maintaining custom Helm repositories?

    • Answer: To unify distribution with container images—same registry, same authentication, same versioning
  2. What file determines the name of the .tgz archive created by helm package?

    • Answer: Chart.yaml (name and version fields create {name}-{version}.tgz)
  3. How does an OCI chart URL differ from a Docker image URL?

    • Answer: They're structurally identical (oci://registry/repo/name:tag), but the content type is different (charts vs images)

If you can't answer these, review Concepts 1-3 before continuing.


Concept 6: Pulling Charts — helm pull

Download charts from OCI registries using helm pull.

Pulling from Docker Hub

# Pull a specific version
helm pull oci://docker.io/myusername/myapp-chart:1.2.3

# Output: Downloaded to myapp-chart-1.2.3.tgz

# List contents
tar -tzf myapp-chart-1.2.3.tgz | head
# Chart.yaml
# values.yaml
# templates/deployment.yaml
# templates/service.yaml

Output:

$ helm pull oci://docker.io/myusername/myapp-chart:1.2.3
Downloaded to myapp-chart-1.2.3.tgz

$ ls -lh myapp-chart-1.2.3.tgz
-rw-r--r-- 1 username staff 4.2K Nov 27 10:35 myapp-chart-1.2.3.tgz

$ tar -tzf myapp-chart-1.2.3.tgz | head -10
myapp-chart/Chart.yaml
myapp-chart/values.yaml
myapp-chart/templates/
myapp-chart/templates/deployment.yaml
myapp-chart/templates/service.yaml
myapp-chart/templates/_helpers.tpl

Pulling from Private Registries

# Pull from ECR
helm pull oci://123456789.dkr.ecr.us-east-1.amazonaws.com/charts/agent-chart:2.0.1

# Pull from ACR (must be authenticated first)
helm registry login myregistry.azurecr.io
helm pull oci://myregistry.azurecr.io/scheduler-chart:1.5.0

# Output: Downloaded to agent-chart-2.0.1.tgz

Output:

$ helm pull oci://123456789.dkr.ecr.us-east-1.amazonaws.com/charts/agent-chart:2.0.1
Downloaded to agent-chart-2.0.1.tgz

$ tar -tzf agent-chart-2.0.1.tgz | grep -E "Chart|values" | head
agent-chart/Chart.yaml
agent-chart/values.yaml

Extracting and Inspecting

# Extract the chart
tar -xzf myapp-chart-1.2.3.tgz

# Inspect values
cat myapp-chart/values.yaml

# Inspect templates
ls myapp-chart/templates/

Output:

$ tar -xzf myapp-chart-1.2.3.tgz

$ cat myapp-chart/values.yaml
replicaCount: 1
image:
repository: myusername/myapp
tag: "1.0.0"
pullPolicy: IfNotPresent

$ ls -la myapp-chart/templates/
total 24
-rw-r--r-- 6 username staff 284 Nov 27 deployment.yaml
-rw-r--r-- 6 username staff 198 Nov 27 service.yaml
-rw-r--r-- 6 username staff 523 Nov 27 _helpers.tpl

Checkpoint: Registry Operations

You've mastered the publish-and-consume workflow for OCI registries.

Quick Reference:

CommandPurposeAuthentication Required
helm registry login docker.ioAuthenticate with registryYes (username/password or token)
helm push chart.tgz oci://registry/repo/Upload chart to registryYes (must login first)
helm pull oci://registry/repo/chart:versionDownload chart from registryPublic: No, Private: Yes

Self-Check Questions:

  1. Where does Helm store registry credentials after helm registry login?

    • Answer: ~/.docker/config.json (same location as Docker CLI credentials)
  2. What happens if you push a chart version that already exists in the registry?

    • Answer: Registry behavior varies—some overwrite, some reject. Best practice: never reuse versions (bump version instead)
  3. Can you install a chart directly from an OCI URL without pulling first?

    • Answer: Yes, use helm install my-release oci://registry/repo/chart:version

If you can't answer these, review Concepts 4-6 before continuing.


Concept 7: Semantic Versioning for Charts

Helm charts follow semantic versioning (semver): MAJOR.MINOR.PATCH.

Understanding Semver for Charts

ScenarioVersion ChangeRationale
Bug fix (values syntax, template indentation)1.2.31.2.4Patch — backwards compatible, internal improvement
New feature (add optional helper template)1.2.31.3.0Minor — backwards compatible, new functionality
New required field in values1.2.32.0.0Major — breaking change, existing values.yaml won't work
Change chart nameAnyMajor — breaking change, existing deployments won't update

Practical Example: Versioning Your AI Agent Chart

---
# myapp-chart/Chart.yaml
apiVersion: v2
name: agent-chart
version: 1.0.0
description: "AI agent deployment chart"
---

Scenario 1: Fix indentation bug in service template

# No breaking change, existing installations unaffected
version: 1.0.1

Scenario 2: Add optional monitoring sideca (optional feature)

# Backwards compatible, existing values work
version: 1.1.0

Scenario 3: Add required field in values.yaml (for security context)

# Breaking change—existing values.yaml missing the field
version: 2.0.0

# deployments must upgrade with new values or provide the field

Version Tags in Registries

# Your chart versions appear as registry tags
oci://docker.io/myusername/agent-chart:1.0.0
oci://docker.io/myusername/agent-chart:1.0.1
oci://docker.io/myusername/agent-chart:1.1.0
oci://docker.io/myusername/agent-chart:2.0.0

# Helm pulls the exact version requested
helm pull oci://docker.io/myusername/agent-chart:1.1.0
# Gets 1.1.0, not latest

Output:

$ # After pushing multiple versions
$ helm search repo oci://docker.io/myusername/agent-chart
# (search not directly supported for OCI, but you can list tags via registry CLI)

$ # With helm CLI >= 3.7
$ helm pull oci://docker.io/myusername/agent-chart:1.1.0
Downloaded to agent-chart-1.1.0.tgz

$ helm pull oci://docker.io/myusername/agent-chart:2.0.0
Downloaded to agent-chart-2.0.0.tgz

Concept 8: Installing Charts Directly from OCI URLs

Instead of downloading then installing, install directly from OCI URLs.

Installing from OCI

# Download, extract, and install in one command
helm install my-release oci://docker.io/myusername/myapp-chart:1.2.3

# Specify namespace
helm install my-release oci://docker.io/myusername/myapp-chart:1.2.3 \
--namespace production \
--create-namespace

# With custom values
helm install my-release oci://docker.io/myusername/myapp-chart:1.2.3 \
--namespace production \
--values custom-values.yaml

Output:

$ helm install agent oci://docker.io/myusername/agent-chart:1.0.0 \
--namespace default \
--wait

NAME: agent
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
...

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
agent-7d4f8b9c2-xk9vz 1/1 Running 0 5s

Upgrading from OCI URLs

# Upgrade to a new version
helm upgrade my-release oci://docker.io/myusername/myapp-chart:1.3.0

# Rollback if something goes wrong
helm rollback my-release

Output:

$ helm upgrade agent oci://docker.io/myusername/agent-chart:1.1.0

Release "agent" has been upgraded. Happy Helming!
NAME: agent
NAMESPACE: default
STATUS: deployed
REVISION: 2

$ helm history agent
REVISION UPDATED STATUS CHART APP VERSION
1 Wed Nov 27 10:30:00 2025 deployed agent-chart-1.0.0 1.0.0
2 Wed Nov 27 10:35:00 2025 deployed agent-chart-1.1.0 1.1.0

Concept 9: Using OCI URLs in Chart Dependencies

In Lesson 4 (Chart Dependencies), you used HTTP URLs to reference external charts. You can also reference them via OCI.

Declaring OCI Dependencies in Chart.yaml

---
# myapp-chart/Chart.yaml
apiVersion: v2
name: myapp-chart
version: 1.2.3
dependencies:
- name: postgresql
version: "12.x"
repository: "oci://docker.io/bitnamicharts"
condition: postgresql.enabled

- name: redis
version: "17.x"
repository: "oci://123456789.dkr.ecr.us-east-1.amazonaws.com/charts"
condition: redis.enabled
---

Updating Dependencies with OCI URLs

# Helm authenticates and downloads from OCI registry
helm dependency update myapp-chart/

# Output:
# Getting updates for unmanaged Helm repositories...
# ...Successfully got an update from the "oci://docker.io/bitnamicharts" chart repository
# ...Successfully got an update from the "oci://123456789.dkr.ecr.us-east-1.amazonaws.com/charts" chart repository
# Update Complete. ⎈ Happy Helming!

Output:

$ helm dependency update myapp-chart/
Getting updates for unmanaged Helm repositories...
...Successfully got an update from the "oci://docker.io/bitnamicharts" chart repository
...Successfully got an update from the "oci://123456789.dkr.ecr.us-east-1.amazonaws.com/charts" chart repository
Update Complete. ⎈ Happy Helming!

$ ls myapp-chart/charts/
postgresql-12.1.5.tgz
redis-17.3.2.tgz

$ tar -tzf myapp-chart/charts/postgresql-12.1.5.tgz | head
postgresql/Chart.yaml
postgresql/values.yaml
postgresql/templates/

Mixed HTTP and OCI Dependencies

You can mix HTTP (traditional Helm repos) and OCI in the same chart:

---
dependencies:
# HTTP repository (old style)
- name: prometheus
version: "15.x"
repository: "https://prometheus-community.github.io/helm-charts"

# OCI registry (new style)
- name: postgresql
version: "12.x"
repository: "oci://docker.io/bitnamicharts"
---

Output:

$ helm dependency update myapp-chart/
Getting updates for unmanaged Helm repositories...
...Successfully got an update from the "https://prometheus-community.github.io/helm-charts" chart repository
...Successfully got an update from the "oci://docker.io/bitnamicharts" chart repository
Update Complete. ⎈ Happy Helming!

$ ls myapp-chart/charts/
postgresql-12.1.5.tgz
prometheus-15.2.1.tgz

Checkpoint: Versioning & Integration

You've learned production-grade versioning and chart composition strategies.

Quick Reference:

ConceptKey PrincipleExample
Semantic VersioningMAJOR.MINOR.PATCH formatBug fix: 1.2.31.2.4, New feature: 1.2.31.3.0, Breaking change: 1.2.32.0.0
Direct InstallationInstall from OCI URL without pullinghelm install my-release oci://registry/chart:version
OCI DependenciesReference dependencies via oci:// URLsrepository: "oci://docker.io/bitnamicharts" in Chart.yaml

Self-Check Questions:

  1. When should you bump the MAJOR version of a Helm chart?

    • Answer: When making breaking changes (new required fields, changed chart name, incompatible template changes)
  2. Can you install a chart from an OCI registry without downloading it first?

    • Answer: Yes, helm install and helm upgrade accept OCI URLs directly
  3. What's the advantage of using OCI URLs in Chart.yaml dependencies instead of HTTP repository URLs?

    • Answer: Unified authentication, same registry as container images, simpler infrastructure

If you can't answer these, review Concepts 7-9 before continuing.


Common Mistakes

Before starting the exercises, avoid these frequent errors:

Mistake 1: Forgetting to Authenticate Before Push

Wrong:

helm push myapp-chart-1.0.0.tgz oci://docker.io/username/
# Error: failed to authorize: failed to fetch anonymous token

Right:

helm registry login docker.io
# Username: your-username
# Password: your-token
helm push myapp-chart-1.0.0.tgz oci://docker.io/username/

Why: Private registries (and even public pushes) require authentication. Always helm registry login before pushing.


Mistake 2: Mismatched Chart Version and Tag

Wrong:

# Chart.yaml
version: 1.0.0
helm package .
# Creates: myapp-chart-1.0.0.tgz
helm push myapp-chart-1.0.0.tgz oci://docker.io/username/
# Pushed to: oci://docker.io/username/myapp-chart:1.0.0

# Later, you install:
helm install myrelease oci://docker.io/username/myapp-chart:1.1.0
# Error: not found (you never pushed 1.1.0)

Right:

# Update Chart.yaml version BEFORE packaging
sed -i 's/version: 1.0.0/version: 1.1.0/' Chart.yaml
helm package .
helm push myapp-chart-1.1.0.tgz oci://docker.io/username/
# Now 1.1.0 exists in registry

Why: The version in Chart.yaml determines both the .tgz filename and the registry tag. Keep them synchronized.


Mistake 3: Using Docker Pull on Helm Charts

Wrong:

docker pull docker.io/username/myapp-chart:1.0.0
# Error: manifest unknown

Right:

helm pull oci://docker.io/username/myapp-chart:1.0.0
# Downloaded to myapp-chart-1.0.0.tgz

Why: Helm charts are stored in OCI registries but aren't Docker images. Use helm pull, not docker pull.


Mistake 4: Including Trailing Slash Incorrectly in OCI URLs

Wrong:

# Missing trailing slash in helm push
helm push myapp-chart-1.0.0.tgz oci://docker.io/username
# May fail depending on registry

# Including chart name in push URL
helm push myapp-chart-1.0.0.tgz oci://docker.io/username/myapp-chart
# Error: chart name included twice

Right:

# Helm push: repository path WITH trailing slash
helm push myapp-chart-1.0.0.tgz oci://docker.io/username/

# Helm pull/install: full path WITHOUT trailing slash
helm pull oci://docker.io/username/myapp-chart:1.0.0

Why: helm push expects a repository path (directory), while helm pull and helm install expect a full artifact reference (chart name + version).


Mistake 5: Reusing Version Numbers After Pushing

Wrong:

# Push version 1.0.0
helm push myapp-chart-1.0.0.tgz oci://docker.io/username/

# Oops, found a bug, fix it
# Don't change version in Chart.yaml
helm package .
helm push myapp-chart-1.0.0.tgz oci://docker.io/username/
# Overwrites existing 1.0.0 (registry-dependent behavior)

Right:

# Push version 1.0.0
helm push myapp-chart-1.0.0.tgz oci://docker.io/username/

# Found a bug, fix it
# Bump version to 1.0.1 in Chart.yaml
helm package .
helm push myapp-chart-1.0.1.tgz oci://docker.io/username/
# New version, no conflicts

Why: Reusing versions creates ambiguity. Consumers can't tell if they have the buggy or fixed version. Follow semantic versioning: always bump version for changes.


Exercise 1: Package Your Chart

Create a simple chart and package it:

# Create a minimal chart
mkdir -p my-agent-chart/templates
cd my-agent-chart

cat > Chart.yaml <<'EOF'
apiVersion: v2
name: my-agent-chart
version: 1.0.0
description: "Simple AI agent Helm chart"
EOF

cat > values.yaml <<'EOF'
replicaCount: 2
image:
repository: myagent
tag: "1.0.0"
EOF

cat > templates/deployment.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Chart.Name }}
spec:
replicas: {{ .Values.replicaCount }}
template:
spec:
containers:
- name: {{ .Chart.Name }}
image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
EOF

# Package it
helm package .

Output:

$ helm package .
Successfully packaged chart and saved it to: /Users/username/my-agent-chart-1.0.0.tgz

$ ls -lh my-agent-chart-1.0.0.tgz
-rw-r--r-- 1 username staff 1.2K Nov 27 11:00 my-agent-chart-1.0.0.tgz

Exercise 2: Create a Docker Hub Account (Public Registry)

For learning, use a public registry to avoid authentication complexity:

  1. Go to https://hub.docker.com
  2. Click "Sign up"
  3. Create account: username, email, password
  4. Verify email
  5. On your local machine:
    helm registry login docker.io
    # Use your Docker Hub username and password

Output:

$ helm registry login docker.io
Username: your-docker-username
Password:
Login Succeeded

Exercise 3: Push Your Chart to OCI

Using the packaged chart and Docker Hub credentials:

# Push to Docker Hub
helm push my-agent-chart-1.0.0.tgz oci://docker.io/your-docker-username/

# The chart is now in the registry
# Reference: oci://docker.io/your-docker-username/my-agent-chart:1.0.0

Output:

$ helm push my-agent-chart-1.0.0.tgz oci://docker.io/your-docker-username/
Pushed: docker.io/your-docker-username/my-agent-chart:1.0.0

Exercise 4: Pull Your Chart from OCI

Download your published chart:

# Create a new directory
mkdir pull-test
cd pull-test

# Pull the chart
helm pull oci://docker.io/your-docker-username/my-agent-chart:1.0.0

# Extract and inspect
tar -xzf my-agent-chart-1.0.0.tgz
cat my-agent-chart/Chart.yaml

Output:

$ helm pull oci://docker.io/your-docker-username/my-agent-chart:1.0.0
Downloaded to my-agent-chart-1.0.0.tgz

$ tar -xzf my-agent-chart-1.0.0.tgz

$ cat my-agent-chart/Chart.yaml
apiVersion: v2
name: my-agent-chart
version: 1.0.0
description: "Simple AI agent Helm chart"

Exercise 5: Install Directly from OCI URL

Deploy using the OCI chart reference without downloading first:

# Install from OCI URL (requires Minikube/cluster running)
helm install my-agent oci://docker.io/your-docker-username/my-agent-chart:1.0.0 \
--namespace default

# Check deployment
kubectl get deployments
kubectl get pods

Output:

$ helm install my-agent oci://docker.io/your-docker-username/my-agent-chart:1.0.0

NAME: my-agent
NAMESPACE: default
STATUS: deployed
REVISION: 1

$ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
my-agent 2/2 2 2 5s

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
my-agent-5c7d8f9b2-x4k9p 1/1 Running 0 5s
my-agent-5c7d8f9b2-y8m2q 1/1 Running 0 5s

Exercise 6: Update Chart and Push a New Version

Modify your chart and publish version 1.1.0:

# In my-agent-chart directory, update Chart.yaml
sed -i '' 's/version: 1.0.0/version: 1.1.0/' Chart.yaml

# Update values.yaml to use image tag 1.1.0
sed -i '' 's/tag: "1.0.0"/tag: "1.1.0"/' values.yaml

# Package the new version
helm package .

# Push to registry
helm push my-agent-chart-1.1.0.tgz oci://docker.io/your-docker-username/

# Both versions now exist in the registry
# oci://docker.io/your-docker-username/my-agent-chart:1.0.0
# oci://docker.io/your-docker-username/my-agent-chart:1.1.0

Output:

$ helm package .
Successfully packaged chart and saved it to: my-agent-chart-1.1.0.tgz

$ helm push my-agent-chart-1.1.0.tgz oci://docker.io/your-docker-username/
Pushed: docker.io/your-docker-username/my-agent-chart:1.1.0

Note: Local Registry for Private/Development Environments

If you don't have access to Docker Hub or prefer to keep charts private during development, you can run a local OCI registry:

# Run local registry on your machine
docker run -d -p 5000:5000 --name local-registry registry:2

# Push to local registry
helm push my-agent-chart-1.0.0.tgz oci://localhost:5000/

# Pull from local registry
helm pull oci://localhost:5000/my-agent-chart:1.0.0

# Install from local registry
helm install my-agent oci://localhost:5000/my-agent-chart:1.0.0

# Stop when done
docker stop local-registry
docker rm local-registry

Output:

$ docker run -d -p 5000:5000 --name local-registry registry:2
Unable to find image 'registry:2' locally
Pulling from library/registry
...
e83ee3a23efb: Already exists
Digest: sha256:...
Status: Downloaded newer image for registry:2
ef7b2e7b8c8f5d9a

$ helm push my-agent-chart-1.0.0.tgz oci://localhost:5000/
Pushed: localhost:5000/my-agent-chart:1.0.0

$ helm pull oci://localhost:5000/my-agent-chart:1.0.0
Downloaded to my-agent-chart-1.0.0.tgz

Try With AI

Part 1: Initial Request

Ask AI to explain the differences between pushing Helm charts to OCI registries versus traditional Helm repositories:

Prompt: "Explain the key differences between distributing Helm charts via OCI registries (like Docker Hub) versus traditional Helm repositories. Why did Helm move to OCI standards?"

Part 2: Critical Evaluation

Review AI's response. Ask yourself:

  • Does it explain why OCI provides a simpler distribution model?
  • Does it mention shared authentication and versioning?
  • Does it clarify that OCI registries store both images AND charts?

Part 3: Constraint Teaching

If AI missed important details, refine your question:

"Also explain how authentication works—what credentials do I need to push to Docker Hub for both images and Helm charts?"

Part 4: Refinement

Ask AI to validate how you'd push a chart to a private Amazon ECR registry:

"Walk me through the steps to push a Helm chart to a private ECR repository in AWS. What commands do I run, and what credentials do I need?"

Part 5: Final Check

Compare what you learned from AI to the concepts in this lesson:

  • Can you explain why oci:// URLs replace HTTP repository URLs?
  • Can you describe the semantic versioning strategy for charts?
  • Can you walk through the complete workflow: package → authenticate → push → pull → install?
  • Did AI's explanations match the examples in this lesson?