Image pulling
Configure public, private, pull-through, proxied, and air-gapped image workflows.
This guide explains how to configure image pulling in Clabernetes, including private registries, pull secrets, and pull-through modes.
Overview
Clabernetes launcher pods run containerlab, which in turn runs Docker to manage network device containers. Images can be pulled in several ways:
- Direct pull: Docker in the launcher pulls images directly
- Pull-through: Clabernetes pre-pulls images via the cluster CRI
Pull-Through Modes
Auto (Default)
Clabernetes automatically detects if pull-through is needed:
spec:
imagePull:
pullThroughOverride: autoBehavior:
- Checks if image exists in launcher's Docker
- If not, requests pull via ImageRequest CRD
- Controller creates a pull pod on the same node
- Image is pulled to node's CRI, then available to Docker
Always
Force pull-through for all images:
spec:
imagePull:
pullThroughOverride: alwaysUse cases:
- Private registries requiring cluster credentials
- Ensuring images are cached at CRI level
- Consistent behavior across all topologies
Never
Disable pull-through, use Docker direct pull:
spec:
imagePull:
pullThroughOverride: neverUse cases:
- Public images that Docker can pull directly
- When pull-through isn't working
- Debugging image pull issues
Private Registry Configuration
Using Pull Secrets
Create a Kubernetes secret for your registry:
kubectl create secret docker-registry my-registry-secret \
--docker-server=registry.example.com \
--docker-username=myuser \
--docker-password=mypass \
--docker-email=myemail@example.comReference in your topology:
apiVersion: c9s.run/v1alpha1
kind: Topology
metadata:
name: private-images
spec:
imagePull:
pullThroughOverride: always
pullSecrets:
- my-registry-secret
definition:
containerlab: |
name: private
topology:
nodes:
srl1:
kind: nokia_srlinux
image: registry.example.com/nokia/srlinux:latestHow it works:
- Controller creates ImageRequest for each image
- Pull pod is created with the specified pull secrets
- Image is pulled to node's CRI
- Launcher can then use the image
Using Docker Config
For more complex authentication (multiple registries, credential helpers):
# Create secret from existing docker config
kubectl create secret generic docker-config \
--from-file=config.json=$HOME/.docker/config.jsonReference in topology:
spec:
imagePull:
dockerConfig: docker-config # Secret nameThe secret must contain a config.json key with valid Docker config.
Docker Daemon Configuration
For daemon-level settings (insecure registries, mirrors):
# Create secret with daemon.json
kubectl create secret generic docker-daemon-config \
--from-file=daemon.json=/path/to/daemon.jsonExample daemon.json:
{
"insecure-registries": ["registry.local:5000"],
"registry-mirrors": ["https://mirror.example.com"]
}Reference in topology:
spec:
imagePull:
dockerDaemonConfig: docker-daemon-configInsecure Registries
For registries without valid TLS:
spec:
imagePull:
insecureRegistries:
- registry.local:5000
- 10.0.0.100:5000Note: This is ignored if dockerDaemonConfig is set (configure in daemon.json instead).
HTTP(S) Proxy Support
If your cluster reaches the internet through an HTTP(S) proxy, set the standard proxy env vars on the launcher pods -- either per topology or globally:
spec:
deployment:
extraEnv:
- name: HTTP_PROXY
value: http://proxy.example.com:8080
- name: HTTPS_PROXY
value: http://proxy.example.com:8080
- name: NO_PROXY
value: 10.96.0.0/16,10.244.0.0/16,.svc,.svc.cluster.local,localhost,127.0.0.1Or for all topologies via the Config CRD (or the globalConfig.deployment.extraEnv helm value):
apiVersion: c9s.run/v1alpha1
kind: Config
metadata:
name: clabernetes
spec:
deployment:
extraEnv:
- name: HTTPS_PROXY
value: http://proxy.example.com:8080
# ...How it works:
- The launcher writes the proxy env vars into its Docker daemon config (
proxiessection), so direct Docker pulls go through the proxy. - Pull-through mode also works: the pull pod uses the node CRI (configure your CRI for the proxy
as usual, most distributions handle this already), and the launcher-side
nerdctloperations inherit the env vars. - The in-cluster Kubernetes API service address is automatically appended to
NO_PROXYby the launcher, so it never tries to reach the API through the proxy.
Note: NO_PROXY should contain your cluster's service and pod CIDRs plus cluster-local DNS
suffixes (see the example above -- adjust the CIDRs for your cluster). Proxy env vars are ignored
if dockerDaemonConfig is set (configure proxies in your daemon.json instead).
Global Configuration
Set defaults in the Config CRD:
apiVersion: c9s.run/v1alpha1
kind: Config
metadata:
name: clabernetes
spec:
imagePull:
# Default pull-through mode
pullThroughOverride: auto
# CRI socket for K3s
criSockOverride: /run/k3s/containerd/containerd.sock
# Default docker config for all topologies
dockerConfig: global-docker-config
dockerDaemonConfig: global-daemon-configCRI Socket Override
For non-standard CRI socket locations (e.g., K3s):
spec:
imagePull:
criSockOverride: /run/k3s/containerd/containerd.sockCommon paths:
- Standard containerd:
/run/containerd/containerd.sock - K3s:
/run/k3s/containerd/containerd.sock - Minikube:
/var/run/containerd/containerd.sock
CRI Registry Hosts
Some containerd installations keep registry mirror, TLS, and host configuration outside the
default /etc/containerd/certs.d directory. Set criHostsDir to make that host directory
available to all pull-through launchers:
spec:
imagePull:
pullThroughOverride: always
criHostsDir: /path/to/containerd/hostsThe directory is mounted read-only at both its original path and /etc/containerd/certs.d.
Keeping the original path available allows certificate paths rooted inside that directory tree
to continue working, while the conventional path lets nerdctl use the same registry configuration
as the node runtime. Absolute certificate paths outside criHostsDir are not mounted; keep those
certificates under criHostsDir or make them available to the launcher separately. The configured
path must be an existing directory on every containerd node that can run a pull-through launcher.
It is not mounted when pull-through is disabled or the effective CRI kind is not containerd; a
configured criKindOverride takes precedence over cluster auto-detection.
Complete Examples
Public Registry
apiVersion: c9s.run/v1alpha1
kind: Topology
metadata:
name: public-images
spec:
imagePull:
pullThroughOverride: auto
definition:
containerlab: |
name: public
topology:
nodes:
srl1:
kind: nokia_srlinux
image: ghcr.io/nokia/srlinux:latestPrivate Registry with Pull Secrets
apiVersion: c9s.run/v1alpha1
kind: Topology
metadata:
name: enterprise
spec:
imagePull:
pullThroughOverride: always
pullSecrets:
- enterprise-registry-creds
definition:
containerlab: |
name: enterprise
topology:
nodes:
srl1:
kind: nokia_srlinux
image: registry.corp.example.com/network/srlinux:23.10.1Insecure Local Registry
apiVersion: c9s.run/v1alpha1
kind: Topology
metadata:
name: local-dev
spec:
imagePull:
pullThroughOverride: never
insecureRegistries:
- localhost:5000
definition:
containerlab: |
name: local
topology:
nodes:
srl1:
kind: nokia_srlinux
image: localhost:5000/srlinux:devAir-Gapped Environment
apiVersion: c9s.run/v1alpha1
kind: Topology
metadata:
name: airgapped
spec:
imagePull:
pullThroughOverride: always
pullSecrets:
- internal-registry-secret
dockerConfig: docker-auth-config
definition:
containerlab: |
name: airgapped
topology:
nodes:
srl1:
kind: nokia_srlinux
image: internal-registry.corp:5000/nokia/srlinux:latestTroubleshooting
Image Pull Failures
Check ImageRequest status:
kubectl get imagerequests
kubectl describe imagerequest <name>Check pull pod:
kubectl get pods -l c9s.run/pullerImageHash=<image-hash>
kubectl logs <pull-pod-name>Authentication Issues
Verify secret exists and is correct:
kubectl get secret my-registry-secret -o yamlTest manually:
kubectl run test --rm -it --image=<your-image> \
--overrides='{"spec":{"imagePullSecrets":[{"name":"my-registry-secret"}]}}'CRI Socket Issues
Verify socket path:
# On the node
ls -la /run/containerd/containerd.sockCheck launcher logs:
kubectl logs -l c9s.run/topologyNode=<node> -c clabernetes-launcherPull-Through Not Working
- Check ImageRequest is created and accepted
- Verify pull pod starts and completes
- Check CRI has the image:
crictl images | grep <image> - Verify Docker can see the image in launcher
Best Practices
- Use pull-through for private registries: Leverages Kubernetes secrets properly
- Pre-pull large images: Reduces topology startup time
- Use registry mirrors: For faster pulls in large clusters
- Set appropriate pull policies:
IfNotPresentfor stability,Alwaysfor latest - Secure your secrets: Use RBAC to protect registry credentials
Configuration Priority
- The one explicitly referenced LauncherProfile (or the shared profile generated from
Topology-level
imagePullsettings) - Global Config CRD
imagePullsettings for fields the profile omits - Default behavior (auto pull-through)
LauncherProfiles are attached through Node.spec.launcherProfileRef; there is no label selector
or multi-profile priority chain.