Install Meshery on Kubernetes in an Air-Gapped Environment

An air-gapped cluster cannot pull images or charts from the internet. Installing Meshery there takes three steps, in order:

  1. Collect every container image the installation needs, on a machine with internet access.
  2. Move the images and the Helm chart into the air-gapped environment, either by mirroring the images to a private registry the cluster can reach or by side-loading them onto the cluster nodes.
  3. Install from the local chart with a values file that points the chart at your local copies.

This guide covers installation on Kubernetes with Helm. It assumes you have already chosen the Meshery release and Meshery Operator release to deploy, and that you pin both. Do not install a moving tag such as stable-latest in an air-gapped environment: record the exact tags (or digests) you mirrored, so the installation is reproducible.

Images Required

The images fall into two groups. Images deployed directly by the Helm charts can be repointed in values.yaml. Images deployed by Meshery Operator at runtime cannot (see Operator-Managed Images).

Deployed by the Helm Charts

ImageComponentNotes
meshery/mesheryMeshery ServerChart default tag is stable-latest; pin the release tag you mirrored.
meshery/meshery-operator:1.0.6Meshery OperatorOnly when the operator is enabled (meshery-operator.enabled). Tag is the operator release.
alpine/k8s:1.35.6Operator CRD update jobRuns on install and upgrade when meshery-operator.crds.updateJob.enabled is true (the default). Easy to miss; a missing copy fails the job, not the operator pod.
meshery/meshery-istio:stable-latestMeshery Istio adapterOnly when the adapter is enabled. The adapter charts carry the tag inside their image reference; pin the release tag you mirrored.
meshery/meshery-linkerd:stable-latestMeshery Linkerd adapterOnly when enabled.
meshery/meshery-consul:stable-latestMeshery Consul adapterOnly when enabled.
meshery/meshery-kuma:stable-latestMeshery Kuma adapterOnly when enabled.
meshery/meshery-nsm:stable-latestMeshery NSM adapterOnly when enabled.
meshery/meshery-traefik-mesh:stable-latestMeshery Traefik Mesh adapterOnly when enabled.
meshery/meshery-cilium:stable-latestMeshery Cilium adapterOnly when enabled.

Adapters are disabled by default, and so is Meshery Operator. Mirror only the optional images for the components you intend to enable.

Operator-Managed Images

When Meshery Operator runs, it creates the MeshSync and Broker workloads itself. Their images are chosen by the operator, not by the Helm charts:

ImageComponent
meshery/meshsync:1.0.3MeshSync (tag is the operator’s pinned default; see below)
nats:2.14.2-alpineBroker (NATS server)
natsio/nats-server-config-reloader:0.23.0Broker (NATS configuration reloader sidecar)

Move the Images

Option A: Mirror to a Private Registry

On a machine with internet access, pull each image, retag it for your registry, and push it:

	
registry=registry.example.local # If your registry requires authentication, log the Docker client in before pushing: docker login "$registry" # Add a meshery/meshery-<adapter>:<release-tag> entry here for each adapter you enable. The example values file below enables meshery-istio, so it is included here. for image in meshery/meshery:<release-tag> meshery/meshery-operator:1.0.6 alpine/k8s:1.35.6 meshery/meshery-istio:<release-tag> meshery/meshsync:1.0.3 natsio/nats-server-config-reloader:0.23.0; do docker pull "$image" docker tag "$image" "$registry/$image" docker push "$registry/$image" done # NATS is a bare Docker Hub image, so its canonical repository is library/nats. # A docker.io mirror looks up library/nats, not nats, so mirror it under that path: docker pull nats:2.14.2-alpine docker tag nats:2.14.2-alpine "$registry/library/nats:2.14.2-alpine" docker push "$registry/library/nats:2.14.2-alpine"

If your registry requires authentication, create the meshery namespace first, then create the pull secret in it before installing, and name it in your values file (below). The namespace must exist before the secret is created; helm install --create-namespace below runs later, so it does not help here.

	
kubectl create namespace meshery --dry-run=client -o yaml | kubectl apply -f -
	
kubectl create secret docker-registry private-registry --namespace meshery --docker-server=registry.example.local --docker-username=<username> --docker-password=<password>

Option B: Side-Load the Images onto Each Node

If you have no registry, archive the images, transfer the archive, and load it on every node:

	
for image in meshery/meshery:<release-tag> meshery/meshery-operator:1.0.6 alpine/k8s:1.35.6 meshery/meshery-istio:<release-tag> meshery/meshsync:1.0.3 nats:2.14.2-alpine natsio/nats-server-config-reloader:0.23.0; do docker pull "$image" done docker save -o meshery-images.tar meshery/meshery:<release-tag> meshery/meshery-operator:1.0.6 alpine/k8s:1.35.6 meshery/meshery-istio:<release-tag> meshery/meshsync:1.0.3 nats:2.14.2-alpine natsio/nats-server-config-reloader:0.23.0

On each node, import the archive into the container runtime. For containerd (the runtime most Kubernetes distributions use today):

	
sudo ctr --namespace k8s.io images import meshery-images.tar

Images loaded this way keep their original names. That matters for the operator-managed images, which the operator requests by their original names (see below).

Get the Helm Chart

The usual helm repo add fetches the chart index from the internet, so transfer the chart itself instead. On the connected machine:

	
helm repo add meshery https://meshery.io/charts/ helm repo update helm pull meshery/meshery --version <chart-version>

Transfer the resulting meshery-<chart-version>.tgz into the air-gapped environment. Installing from the packaged chart (or from a checkout of the Meshery repository) needs no repository access.

Point values.yaml at Your Registry

Create air-gapped-values.yaml. Two defaults must change for every component: the image reference must name your registry, and the pull policy must be IfNotPresent. The chart defaults for Meshery Server and the adapters use the moving stable-latest tag with pull policy Always, which an offline cluster can neither resolve nor rely on.

image:
  repository: registry.example.local/meshery/meshery
  tag: "<release-tag>"
  pullPolicy: IfNotPresent
imagePullSecrets:
  - name: private-registry

meshery-operator:
  enabled: true
  image:
    repository: registry.example.local/meshery/meshery-operator
    # tag remains the operator release, 1.0.6 in this example
    pullPolicy: IfNotPresent
  imagePullSecrets:
    - name: private-registry
  crds:
    updateJob:
      image:
        repository: registry.example.local/alpine/k8s

# Adapters, for each one you enable. Note that an adapter's image
# reference carries its tag, because that is how the adapter
# subcharts define it.
meshery-istio:
  enabled: true
  image:
    repository: registry.example.local/meshery/meshery-istio:<release-tag>
    pullPolicy: IfNotPresent
  imagePullSecrets:
    - name: private-registry

Notes on this file:

  • The operator has its own image, imagePullSecrets, and CRD job image settings, nested under meshery-operator. There is no single global registry setting shared by the charts, so each component is repointed separately.
  • Each adapter subchart has its own imagePullSecrets (default empty), so the top-level secret does not cover an enabled adapter. Set it under that adapter, as shown for meshery-istio above.
  • meshery-operator.imagePullSecrets applies only to the operator Deployment. The CRD update Job (crds.updateJob.image) has no chart-level pull-secret setting, so on an authenticated registry either side-load the alpine/k8s image onto every node or configure node-runtime credentials for the registry; otherwise the Job fails with ImagePullBackOff and CRD updates do not apply.
  • If you side-loaded the images instead of mirroring them, keep the original image references, omit imagePullSecrets, and set only the pinned tags and pullPolicy: IfNotPresent.

Operator-Managed Images

MeshSync and Broker images cannot be repointed in values.yaml or in their custom resources. Meshery Operator names the repositories (meshery/meshsync and nats) in its own code; the MeshSync and Broker resources accept only a version (spec.version), which selects the tag, and the NATS configuration reloader image is fixed in the operator’s embedded Broker manifests. You have two ways to serve these images offline:

  1. Configure a registry mirror on every node. Point your container runtime’s mirror for Docker Hub at your private registry, and mirror the three operator-managed images under their original Docker Hub names: meshery/meshsync, library/nats (the bare nats reference), and natsio/nats-server-config-reloader. The operator’s requests then resolve to your registry without any Meshery configuration change.

    For containerd, use the hosts directory layout, not the deprecated registry.mirrors field in config.toml. Set config_path = "/etc/containerd/certs.d" in containerd’s registry configuration, then add /etc/containerd/certs.d/docker.io/hosts.toml:

    server = "https://docker.io"
    
    [host."https://registry.example.local"]
      capabilities = ["pull", "resolve"]
    

    Helm imagePullSecrets cover chart-created Pods only, not the MeshSync and Broker Pods the operator creates, so an authenticated private registry needs its credentials configured at the node level for this mirror as well.

  2. Side-load the three images onto every node under their original names, as in Option B above. The operator pulls pinned tags with IfNotPresent, so pre-loaded images are used without contacting a registry.

Install

	
helm install meshery ./meshery-<chart-version>.tgz --namespace meshery --create-namespace -f air-gapped-values.yaml kubectl get pods --namespace meshery -w

A pod stuck in ImagePullBackOff names an image that was not mirrored or loaded, or a reference your values file did not repoint. Check the exact image it wants:

	
kubectl describe pod --namespace meshery <pod-name>

Providers in an Air-Gapped Environment

Use the Local Provider. The chart’s default env.PROVIDER_BASE_URLS lists Remote Providers hosted outside your environment; signing in through a Remote Provider, and retrieving extensions and catalog content from one, requires internet access and will not work air-gapped. Meshery Server, Meshery Operator, MeshSync, and the adapters run entirely inside the cluster once their images are present.

Managing a service mesh through an adapter additionally requires that mesh’s own images, which are outside the list above. Mirror those from the mesh project’s own air-gapped instructions before installing the mesh.