Skaffold
Helm Charts
Local Image Repository
Kubernetes
DevOps

How Do I Get Skaffold And Helm Charts To Work With A Local Image Repository?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

To make Skaffold, Helm, and a local image repository work together, you need one consistent image flow from build to chart values to cluster pullability. Most failures come from one of two mismatches: Skaffold pushes an image name that Helm does not deploy, or Kubernetes cannot reach the registry address that worked from your laptop.

Start With a Registry the Cluster Can Reach

A local registry on your machine is only useful if the Kubernetes nodes can pull from it. The tricky part is that localhost:5000 from your host is not always localhost:5000 from inside the cluster.

A simple local registry container looks like this:

bash
docker run -d -p 5000:5000 --restart=always --name registry registry:2

That creates a registry at localhost:5000 from the host perspective. Whether the cluster can use that address depends on the cluster type:

  • with Docker Desktop Kubernetes, the host and cluster integration is usually straightforward
  • with Kind, you often need a registry alias wired into the cluster
  • with Minikube, the node may need a host IP or an insecure-registry configuration

The main rule is that the image reference deployed into Kubernetes must point to a registry address resolvable from the cluster, not just from your terminal.

Make Skaffold Build and Push to That Registry

Skaffold needs to tag images with the same repository path that Helm will later deploy. A common pattern is to set a default repository.

yaml
1apiVersion: skaffold/v4beta11
2kind: Config
3build:
4  local:
5    push: true
6artifacts:
7  - image: myapp
8deploy:
9  helm:
10    releases:
11      - name: myapp
12        chartPath: charts/myapp
13        setValueTemplates:
14          image.repository: "{{.IMAGE_REPO_myapp}}"
15          image.tag: "{{.IMAGE_TAG_myapp}}"
16defaultRepo: localhost:5000

With that setup, Skaffold builds myapp, rewrites it to something like localhost:5000/myapp, pushes it, and passes the resolved repository and tag into Helm.

The setValueTemplates section is the critical bridge. Without it, Helm may still deploy a hard-coded image name from values.yaml and completely ignore what Skaffold just built.

Make the Helm Chart Accept Repository and Tag Values

Your Helm chart must expose image settings cleanly. A typical values.yaml looks like this:

yaml
1image:
2  repository: localhost:5000/myapp
3  tag: latest
4  pullPolicy: IfNotPresent

Then in the deployment template:

yaml
1spec:
2  containers:
3    - name: myapp
4      image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
5      imagePullPolicy: {{ .Values.image.pullPolicy }}

This keeps the chart reusable and lets Skaffold inject the current image reference at deploy time.

Match Pull Policy to the Development Workflow

For local registry setups, imagePullPolicy matters. If you are reusing tags such as latest, Kubernetes may keep using an older image unless the policy forces another pull or the tag changes.

In development, the safest options are:

  • unique tags generated by Skaffold
  • or Always as the pull policy when reusing tags

Unique tags are generally cleaner because they avoid ambiguity.

Diagnose the Real Failure Point

When it does not work, isolate the stage that is broken:

  1. Did Skaffold build the image
  2. Did it push to the registry you expected
  3. Did Helm deploy that exact repository and tag
  4. Can the cluster pull from that registry address

Useful checks include:

bash
1skaffold build
2skaffold deploy --dry-run
3kubectl describe pod myapp-12345
4kubectl get pods

If the pod shows ImagePullBackOff, the problem is usually registry reachability or naming. If the pod runs but uses the wrong image, the problem is usually the Helm values wiring.

Do Not Assume localhost Means the Same Thing Everywhere

This is the most common mistake in local registry workflows. localhost:5000/myapp may work fine for docker push on your host, but a Kubernetes node may interpret localhost as itself rather than your machine. In that case, the node looks for a registry on its own loopback interface and fails.

That is why cluster-specific registry setup matters so much. The registry address in the deployed image must be valid from the node's point of view.

Common Pitfalls

  • Building and pushing with Skaffold is not enough if the Helm chart still deploys a hard-coded image reference from values.yaml.
  • Using localhost:5000 blindly often fails because the Kubernetes node may not resolve localhost to your host machine.
  • Reusing static tags without a matching pull policy can make Kubernetes run an old image even after a successful push.
  • Treating registry setup as identical across Kind, Minikube, and Docker Desktop leads to confusing cluster-specific pull failures.
  • Debugging Helm templates before checking whether the cluster can reach the registry wastes time when the real problem is network visibility.

Summary

  • Skaffold, Helm, and a local registry only work cleanly when all three agree on the same image repository and tag.
  • Use setValueTemplates so Helm deploys the image Skaffold actually built.
  • Make sure the registry address is reachable from the Kubernetes nodes, not just from your workstation.
  • Choose a pull policy and tagging strategy that avoids stale images.
  • Diagnose failures stage by stage: build, push, template injection, and cluster pull.

Course illustration
Course illustration

All Rights Reserved.