Skip to main content
Version: v0.34 Stable

Deploy vind in an air-gapped environment

Modify the following with your specific values to replace on the whole page and generate copyable commands:

vind makes five distinct network calls during cluster creation and operation. In an air-gapped environment where Docker containers cannot reach public registries or GitHub, each one must be handled separately.

How vind uses the network​

StepWhenWhat vind contactsConfigurable?
1Before vcluster createPulls alpine on the Docker host for pre-creation checksNo — pre-pull required
2Inside the vm-container at bootstrapDownloads install-standalone.sh from GitHubNo — custom image or bridge proxy required
3During vCluster startupPulls internal vCluster images (k3s, CoreDNS, syncer)Yes — controlPlane.advanced.defaultImageRegistry
4When workloads runPulls workload container imagesYes — containerd registry mirror using a volume mount
5For StatefulSet podsPulls alpine for the rewriteHosts init containerYes — sync.toHost.pods.rewriteHosts.initContainer.image

Steps 1 and 2 have no native config override. The sections below cover each step with its workaround.

Prerequisites​

  • Docker installed and running with access to your internal registry
  • vCluster CLI installed — see Deployment basics
  • install-standalone.sh and the vcluster-linux-<arch>-standalone binary downloaded from the vCluster releases page on an internet-connected machine
  • Required images mirrored to your internal registry (images.txt is listed under vCluster release assets)

Step 1: Pre-pull Alpine on the Docker host​

Before vcluster create runs, vind pulls alpine directly on the host for pre-creation checks including port availability, containerd socket detection, and network reachability. There is no config flag to override this image name.

Pull Alpine from your internal registry and retag it so vind finds it under the expected name:

docker pull your-registry.internal/library/alpine:latest
docker tag your-registry.internal/library/alpine:latest alpine:latest

vind's registry proxy (enabled by default) then serves this image from the host Docker cache for all subsequent pulls inside the cluster, so the network is not contacted again.

Step 2: Provide the vm-container bootstrap assets​

After the vm-container starts, the vCluster binary inside it downloads install-standalone.sh from GitHub. This URL is hardcoded in the CLI binary. Choose one of the following workarounds.

  1. On an internet-connected machine, create an empty directory and add a plain text file named Dockerfile (no extension) with the following content. Replace amd64 with arm64 if building on Apple Silicon.

    FROM ghcr.io/loft-sh/vm-container:latest
    RUN mkdir -p /vcluster-install && \
    curl -sfL https://github.com/loft-sh/vcluster/releases/download/v0.34.0/install-standalone.sh \
    -o /vcluster-install/install-standalone.sh && \
    curl -sfL https://github.com/loft-sh/vcluster/releases/download/v0.34.0/vcluster-linux-amd64-standalone \
    -o /vcluster-install/vcluster && \
    chmod +x /vcluster-install/vcluster
  2. From that same directory, build, and push the image to your registry:

    docker build -t your-registry.internal/vind:v0.34.0-airgapped .
    docker push your-registry.internal/vind:v0.34.0-airgapped
  3. Create a vcluster.yaml file and add the following. You will add more settings to this file in Steps 3–5, then pass it to vcluster create at the end.

    experimental:
    docker:
    image: your-registry.internal/vind:v0.34.0-airgapped

Option B: Serve the assets from the Docker bridge network​

If you cannot build a custom image, run a file server on the Docker host that is reachable from inside the vind containers.

  1. Find the Docker bridge gateway IP:

    docker network inspect bridge | grep Gateway

    Note the IP address — this is usually 172.17.0.1. You will use it in Step 3.

  2. Run the following commands to create a directory structure that mirrors the GitHub URL path, copy the assets into it, and start a local file server:

    mkdir -p /tmp/vind-mirror/loft-sh/vcluster/releases/download/v0.34.0
    cp install-standalone.sh vcluster-linux-amd64-standalone \
    /tmp/vind-mirror/loft-sh/vcluster/releases/download/v0.34.0/
    cd /tmp/vind-mirror && python3 -m http.server 9999 --bind 0.0.0.0
  3. Create a vcluster.yaml file and add the following, substituting the gateway IP from Step 1. You will add more settings to this file in Steps 3–5, then pass it to vcluster create at the end.

    Modify the following with your specific values to generate a copyable command:
    experimental:
    docker:
    env:
    - HTTPS_PROXY=http://172.17.0.1:9999
    warning

    The proxy must listen on the Docker bridge interface, not loopback (127.0.0.1). A proxy bound to loopback on the host is not reachable from inside the vm-container.

Step 3: Redirect internal vCluster images​

controlPlane.advanced.defaultImageRegistry prepends a registry prefix to all images vCluster deploys internally, including k3s, CoreDNS, syncer, etcd, and backing store images. It does not affect workload images.

Mirror all images listed in images.txt from the vCluster release assets into your registry at the same repository path, then add the following to your vcluster.yaml:

controlPlane:
advanced:
defaultImageRegistry: your-registry.internal/vcluster/
note

Steps 4 and 5 are not required for a basic air-gapped cluster. Complete the remaining steps only if your environment needs them.

Step 4: Route workload image pulls through a registry mirror​

Complete this step if your workloads pull images from public registries like docker.io or ghcr.io. Without it, workload containers fail to start in an air-gapped environment.

Workload images are pulled by the k3s containerd runtime inside the cluster. Configure containerd mirrors by mounting a registries.yaml file into the vind container.

  1. Create a registries.yaml file on the host that redirects pulls for docker.io and ghcr.io to your internal registry:

    mirrors:
    docker.io:
    endpoint:
    - "https://your-registry.internal"
    ghcr.io:
    endpoint:
    - "https://your-registry.internal"
  2. If your registry requires authentication, add a configs section to the registries.yaml file:

    Modify the following with your specific values to generate a copyable command:
    configs:
    "your-registry.internal":
    auth:
    username: username
    password: password
  3. Add a volumes entry to the docker block in your vcluster.yaml. The image key was added in Step 2:

    Modify the following with your specific values to generate a copyable command:
    experimental:
    docker:
    image: ... # from Step 2
    volumes:
    - "/path/to/registries.yaml:/etc/rancher/k3s/registries.yaml"

Step 5: Override the rewriteHosts init container image​

Complete this step if you are running StatefulSets inside the cluster. vCluster pulls mirror.gcr.io/library/alpine:3.20 to rewrite FQDNs for StatefulSet pods. Without this override, that pull will fail in an air-gapped environment.

Add the following to your vcluster.yaml:

sync:
toHost:
pods:
rewriteHosts:
initContainer:
image:
registry: your-registry.internal
repository: library/alpine
tag: "3.20"

The image field takes registry, repository, and tag sub-fields, not a plain string. This format was introduced in v0.27.0.

Step 6: Create the cluster​

After preparing the host (Steps 1 and 2), switch to the Docker driver and create the cluster:

vcluster use driver docker
vcluster create my-cluster --values vcluster.yaml

For next steps, see the Docker (vind) Quick Start to verify your cluster is working and explore pause/resume, LoadBalancer services, and worker nodes.

Troubleshoot​

Cluster creation times out due to unreachable kubectl context​

fatal Get "https://<ip>:6443/api/v1/namespaces/vcluster-my-cluster": dial tcp <ip>:6443: i/o timeout

The vCluster CLI checks the active kubectl context at startup. If that context points to an unreachable cluster (for example, a stopped Multipass VM), the command times out before Docker driver mode is entered.

Check your current context and switch to one that is reachable:

kubectl config get-contexts
kubectl config use-context <your-context>

If the correct context is not listed, your shell may have a KUBECONFIG environment variable pointing to a separate kubeconfig file. Unset it to fall back to ~/.kube/config:

unset KUBECONFIG
kubectl config get-contexts
kubectl config use-context <your-context>

vCluster Pro features error when not logged into Platform​

fatal you have vCluster pro features enabled, but seems like you are not logged in ... Please make sure to log into vCluster Platform to use vCluster pro features or run this command with --add=false

This occurs when the CLI has a vCluster Platform connection configured but you are not actively logged in. Pass --add=false to create the cluster without registering it with Platform:

vcluster create my-cluster --values vcluster.yaml --add=false

Permission denied creating staging directory​

fatal create staging directory: mkdir /Users/<you>/.vcluster/docker/vcluster/<version>.downloading: permission denied

A previous sudo vcluster create run left files in ~/.vcluster/docker/ owned by root. Fix the ownership and retry:

sudo chown -R $(whoami) ~/.vcluster/
vcluster create my-cluster --values vcluster.yaml

Example complete vcluster.yaml​

The following combines all configurable layers. Replace registry, version, and path values for your environment.

Modify the following with your specific values to generate a copyable command:
controlPlane:
advanced:
defaultImageRegistry: your-registry.internal/vcluster/

experimental:
docker:
image: your-registry.internal/vind:v0.34.0-airgapped
volumes:
- "/path/to/registries.yaml:/etc/rancher/k3s/registries.yaml"

sync:
toHost:
pods:
rewriteHosts:
initContainer:
image:
registry: your-registry.internal
repository: library/alpine
tag: "3.20"