Ajeet Raina Docker Captain, ARM Innovator & Docker Bangalore Community Leader.

How to Build & Push Helm Chart to Docker Hub flawlessly

4 min read

Helm is a package manager for Kubernetes. It is an open-source container orchestration system. It helps you manage Kubernetes applications by providing an easy way to define, install, and upgrade complex Kubernetes applications.

Using Helm, you can package your application into a “chart,” which is a bundle of files that describes your application. You can then use Helm to install and manage your application on a Kubernetes cluster. Helm makes it easy to automate the installation and management of complex applications, and it provides a number of features that make it a powerful tool for managing Kubernetes applications.

Some of the benefits of using Helm include:

  • Simplifies the process of installing and managing complex Kubernetes applications.
  • Makes it easy to automate the deployment and management of applications.
  • Allows you to version control your application configurations.
  • Provides a way to share applications with others through public or private chart repositories.
  • Makes it easy to roll back to a previous version of an application if necessary.

Overall, Helm is a useful tool for managing and deploying applications on Kubernetes, and it can help you streamline the process of managing complex applications on a Kubernetes cluster.

Why Docker is supporting Helm Chart?

Docker Hub is the popular hosted repository service provided by Docker for finding and sharing container images with your team. It is a repository of container images used to store and distribute container images — or artifacts usable by container runtimes. This became a limitation of our platform since container image distribution is just the starting point of the application delivery process.

Modern apps today support numerous artifacts such as WebAssembly modules, OPA Bundles, Helm charts, SBOMs and custom artifacts. Hence, it became essential for the Docker Hub team to accommodate and support all these artifacts so that developers can share these with clients that need them since they add immense value to their projects. Last Oct 2022, Docker announced that Docker Hub can now help you distribute any type of application artifacts! You can now keep everything in one place without having to leverage multiple registries.

In this blog, you will see how Docker Hub can store Helm charts flawlessly.

1. Create a Helm chart

The best way to get started with a new chart is to use the helm create command to scaffold out an example we can build on. Use this command to create a new chart named collabnix in a new directory:

helm create collabnix

2. Viewing the Chart

Helm will create a new directory in your project called collabnix with the structure shown below. Let’s navigate our new chart to find out how it works.

demo % tree             
.
├── Chart.yaml
├── charts
├── templates
│   ├── NOTES.txt
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   ├── hpa.yaml
│   ├── ingress.yaml
│   ├── service.yaml
│   ├── serviceaccount.yaml
│   └── tests
│       └── test-connection.yaml
└── values.yaml

3 directories, 10 files

The most important piece of the file structure is the templates/ directory. This is where Helm finds the YAML definitions for your ServicesDeployments and other Kubernetes objects. If you already have definitions for your application, all you need to do is replace the generated YAML files with your own. What you end up with is a working chart that can be deployed using the helm install command.

Open the service.yaml file to see what this looks like:

apiVersion: v1
kind: Service
metadata:
  name: {{ include "collabnix.fullname" . }}
  labels:
    {{- include "collabnix.labels" . | nindent 4 }}
spec:
  type: {{ .Values.service.type }}
  ports:
    - port: {{ .Values.service.port }}
      targetPort: http
      protocol: TCP
      name: http
  selector:
    {{- include "collabnix.selectorLabels" . | nindent 4 }}                                                        

This is a basic Service definition using templating. When deploying the chart, Helm will generate a definition that will look a lot more like a valid Service. We can do a dry-run of a helm install and enable debug to inspect the generated definitions.

Please ensure that you run the below command outside the Helm directory.

helm install collabnix --dry-run --debug ./collabnix
install.go:192: [debug] Original chart version: ""
install.go:209: [debug] CHART PATH: /Users/ajeetraina/dec/collabnix

NAME: collabnix
LAST DEPLOYED: Thu Dec  8 11:02:54 2022
NAMESPACE: default
STATUS: pending-install
REVISION: 1
USER-SUPPLIED VALUES:
{}

COMPUTED VALUES:
affinity: {}
autoscaling:
  enabled: false
  maxReplicas: 100
  minReplicas: 1
  targetCPUUtilizationPercentage: 80
fullnameOverride: ""
image:
  pullPolicy: IfNotPresent
  repository: nginx
  tag: ""
imagePullSecrets: []
ingress:
  annotations: {}
  className: ""
  enabled: false
  hosts:
  - host: chart-example.local
    paths:
    - path: /
      pathType: ImplementationSpecific
  tls: []
nameOverride: ""
nodeSelector: {}
podAnnotations: {}
podSecurityContext: {}
replicaCount: 1
resources: {}
securityContext: {}
service:
  port: 80
  type: ClusterIP
serviceAccount:
  annotations: {}
  create: true
  name: ""
tolerations: []

HOOKS:
---
# Source: collabnix/templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
  name: "collabnix-test-connection"
  labels:
    helm.sh/chart: collabnix-0.1.0
    app.kubernetes.io/name: collabnix
    app.kubernetes.io/instance: collabnix
    app.kubernetes.io/version: "1.16.0"
    app.kubernetes.io/managed-by: Helm
  annotations:
    "helm.sh/hook": test
spec:
  containers:
    - name: wget
      image: busybox
      command: ['wget']
      args: ['collabnix:80']
  restartPolicy: Never
MANIFEST:
---
# Source: collabnix/templates/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: collabnix
  labels:
    helm.sh/chart: collabnix-0.1.0
    app.kubernetes.io/name: collabnix
    app.kubernetes.io/instance: collabnix
    app.kubernetes.io/version: "1.16.0"
    app.kubernetes.io/managed-by: Helm
---
# Source: collabnix/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: collabnix
  labels:
    helm.sh/chart: collabnix-0.1.0
    app.kubernetes.io/name: collabnix
    app.kubernetes.io/instance: collabnix
    app.kubernetes.io/version: "1.16.0"
    app.kubernetes.io/managed-by: Helm
spec:
  type: ClusterIP
  ports:
    - port: 80
      targetPort: http
      protocol: TCP
      name: http
  selector:
    app.kubernetes.io/name: collabnix
    app.kubernetes.io/instance: collabnix
---
# Source: collabnix/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: collabnix
  labels:
    helm.sh/chart: collabnix-0.1.0
    app.kubernetes.io/name: collabnix
    app.kubernetes.io/instance: collabnix
    app.kubernetes.io/version: "1.16.0"
    app.kubernetes.io/managed-by: Helm
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: collabnix
      app.kubernetes.io/instance: collabnix
  template:
    metadata:
      labels:
        app.kubernetes.io/name: collabnix
        app.kubernetes.io/instance: collabnix
    spec:
      serviceAccountName: collabnix
      securityContext:
        {}
      containers:
        - name: collabnix
          securityContext:
            {}
          image: "nginx:1.16.0"
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /
              port: http
          readinessProbe:
            httpGet:
              path: /
              port: http
          resources:
            {}

NOTES:
1. Get the application URL by running these commands:
  export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=collabnix,app.kubernetes.io/instance=collabnix" -o jsonpath="{.items[0].metadata.name}")
  export CONTAINER_PORT=$(kubectl get pod --namespace default $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
  echo "Visit http://127.0.0.1:8080 to use your application"
  kubectl --namespace default port-forward $POD_NAME 8080:$CONTAINER_PORT

3. Changing the default value for service.internalPort

If you execute another dry-run, you should find that the targetPort in the Service and the containerPort in the Deployment changes.

helm install collabnix --dry-run --debug ./collabnix --set service.internalPort=8080

4. Exposing it externally

By default, the chart will create a ClusterIP-type Service, so NGINX will only be exposed internally in the cluster. To access it externally, we’ll use the NodePort type instead. We can also set the name of the Helm release so we can easily refer back to it. Let’s go ahead and deploy our NGINX chart using the helm install command:

helm install example ./collabnix --set service.type=NodePort

3. Packaging the Helm Chart

Once we’re done editing, we’ll need to package the Helm chart as an OCI image:

helm package collabnix
Successfully packaged chart and saved it to: /Users/ajeetraina/dec/collabnix-0.1.0.tgz

5. Login to Docker Hub

docker login
Authenticating with existing credentials...
Login Succeeded

Logging in with your password grants your terminal complete access to your account. 
For better security, log in with a limited-privilege personal access token. Learn more at https://docs.docker.com/go/access-tokens/

6. Pushing it to Docker Hub

helm push collabnix-0.1.0.tgz  oci://registry-1.docker.io/ajeetraina
Error: server message: insufficient_scope: authorization failed

You might encounter the error message.

Fix: We recommend creating a Personal Access Token (PAT) for this.

Image3

You can export your PAT via an environment variable, and login, as follows:

echo $REG_PAT | helm registry login registry-1.docker.io -u ajeetraina --password-stdin 
Login Succeeded

Conclusion

Storing your Helm Charts in Docker Hub enables improved collaboration through Docker Hub’s standard sharing capabilities. Developers can now build Helm chart locally by using the search capabilities of Docker Desktop and then push it flawlessly to the Docker Hub.

References

Please follow and like us:

Have Queries? Join https://launchpass.com/collabnix

Ajeet Raina Docker Captain, ARM Innovator & Docker Bangalore Community Leader.