Deploy vind in an air-gapped environment
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​
| Step | When | What vind contacts | Configurable? |
|---|---|---|---|
| 1 | Before vcluster create | Pulls alpine on the Docker host for pre-creation checks | No — pre-pull required |
| 2 | Inside the vm-container at bootstrap | Downloads install-standalone.sh from GitHub | No — custom image or bridge proxy required |
| 3 | During vCluster startup | Pulls internal vCluster images (k3s, CoreDNS, syncer) | Yes — controlPlane.advanced.defaultImageRegistry |
| 4 | When workloads run | Pulls workload container images | Yes — containerd registry mirror using a volume mount |
| 5 | For StatefulSet pods | Pulls alpine for the rewriteHosts init container | Yes — 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.shand thevcluster-linux-<arch>-standalonebinary downloaded from the vCluster releases page on an internet-connected machine- Required images mirrored to your internal registry (
images.txtis 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.
Option A: Build a custom vm-container image (recommended)​
On an internet-connected machine, create an empty directory and add a plain text file named
Dockerfile(no extension) with the following content. Replaceamd64witharm64if building on Apple Silicon.FROM ghcr.io/loft-sh/vm-container:latestRUN 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/vclusterFrom 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-airgappedCreate a
vcluster.yamlfile and add the following. You will add more settings to this file in Steps 3–5, then pass it tovcluster createat 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.
Find the Docker bridge gateway IP:
docker network inspect bridge | grep GatewayNote the IP address — this is usually
172.17.0.1. You will use it in Step 3.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.0cp 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.0Create a
vcluster.yamlfile 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 tovcluster createat the end.Modify the following with your specific values to generate a copyable command:experimental:docker:env:- HTTPS_PROXY=http://172.17.0.1:9999warningThe 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/
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.
Create a
registries.yamlfile on the host that redirects pulls fordocker.ioandghcr.ioto your internal registry:mirrors:docker.io:endpoint:- "https://your-registry.internal"ghcr.io:endpoint:- "https://your-registry.internal"If your registry requires authentication, add a
configssection to theregistries.yamlfile:Modify the following with your specific values to generate a copyable command:configs:"your-registry.internal":auth:username: usernamepassword: passwordAdd a
volumesentry to thedockerblock in yourvcluster.yaml. Theimagekey was added in Step 2:Modify the following with your specific values to generate a copyable command:experimental:docker:image: ... # from Step 2volumes:- "/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.
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"