c9s
Guides

File mounting

Mount configuration, licenses, and other files into network nodes.

This guide explains how to mount external files into Clabernetes topology nodes using ConfigMaps, Secrets, and URLs.

Overview

Clabernetes supports three sources for placing files into device pods:

  1. ConfigMaps: Mount files from Kubernetes ConfigMaps
  2. Secrets: Mount sensitive files from Kubernetes Secrets
  3. URLs: Download files from HTTP/HTTPS endpoints

Every declared file is a payload of the node's device plan: its content digest is recorded in the accepted plan, and the preparation init container stages it into plan-scoped volumes and verifies every path, mode, and digest before the device containers start. Changing a payload's content therefore produces a new plan and recreates the Pod. Arbitrary host binds are rejected before a workload is created.

Mounting Files from ConfigMaps

Creating the ConfigMap

First, create a ConfigMap with your file content:

apiVersion: v1
kind: ConfigMap
metadata:
  name: srl-license
  namespace: default
data:
  license.key: |
    # Your license content here
    AAAAB3NzaC1yc2EAAA...

Or create from a file:

kubectl create configmap srl-license --from-file=license.key=/path/to/license.key

Mounting in Topology

Reference the ConfigMap in your topology:

apiVersion: c9s.run/v1alpha1
kind: Topology
metadata:
  name: with-license
spec:
  deployment:
    filesFromConfigMap:
      srl1:  # Node name
        - filePath: /opt/srlinux/etc/license.key
          configMapName: srl-license
          configMapPath: license.key
          mode: read
  definition:
    containerlab: |
      name: licensed
      topology:
        nodes:
          srl1:
            kind: nokia_srlinux
            image: ghcr.io/nokia/srlinux:latest

Topology remains a supported auxiliary input. The compiler moves each map entry onto the corresponding generated Node; it does not create a one-off NodeProfile just to carry payload.

Mounting on a Direct Node

For directly authored primitive resources, payload attachments live in the Node spec:

apiVersion: c9s.run/v1alpha1
kind: Node
metadata:
  name: srl1
spec:
  kind: nokia_srlinux
  image: ghcr.io/nokia/srlinux:latest
  filesFromConfigMap:
    - filePath: /opt/srlinux/etc/license.key
      configMapName: srl-license
      configMapPath: license.key
      mode: read
  filesFromURL:
    - filePath: /tmp/bootstrap.json
      url: https://example.com/bootstrap/srl1.json
      digest: sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08

NodeProfile contains deployment policy only; it does not own per-node files.

FileFromConfigMap Fields

FieldRequiredDescription
filePathYesDestination path inside the pod
configMapNameYesName of the ConfigMap
configMapPathNoSpecific key in ConfigMap (mounts entire CM if omitted)
modeNoread (0o444) or execute (0o555), default: read

Multiple Files

Mount multiple files to the same node:

filesFromConfigMap:
  srl1:
    - filePath: /opt/srlinux/etc/license.key
      configMapName: srl-license
      configMapPath: license.key
    - filePath: /tmp/startup-config.json
      configMapName: srl-configs
      configMapPath: srl1-config.json
    - filePath: /tmp/custom-script.sh
      configMapName: scripts
      configMapPath: init.sh
      mode: execute  # Make script executable

Files for Multiple Nodes

filesFromConfigMap:
  srl1:
    - filePath: /opt/srlinux/etc/license.key
      configMapName: srl-license
      configMapPath: license.key
  srl2:
    - filePath: /opt/srlinux/etc/license.key
      configMapName: srl-license
      configMapPath: license.key
  srl3:
    - filePath: /opt/srlinux/etc/license.key
      configMapName: srl-license
      configMapPath: license.key

Mounting Files from Secrets

Sensitive payloads (credentials, private keys, licenses under NDA) belong in Secrets rather than ConfigMaps. Secret-backed payloads are marked sensitive: only their digest reaches the plan, and the bytes are projected straight into the preparation container.

apiVersion: c9s.run/v1alpha1
kind: Node
metadata:
  name: srl1
spec:
  kind: nokia_srlinux
  image: ghcr.io/nokia/srlinux:latest
  filesFromSecret:
    - filePath: /opt/srlinux/etc/license.key
      secretName: srl-license
      secretPath: license.key

In a Topology, the equivalent map is spec.deployment.filesFromSecret keyed by node name.

FileFromSecret Fields

FieldRequiredDescription
filePathYesDestination path, or destination directory when secretPath is omitted
secretNameYesName of the same-namespace Secret
secretPathNoSecret data key (all keys are projected beneath filePath if omitted)
modeNoread (0o444) or execute (0o555), default: read

Mounting Files from URLs

Basic URL Mount

spec:
  deployment:
    filesFromURL:
      srl1:
        - filePath: /tmp/config.json
          url: https://raw.githubusercontent.com/example/configs/main/srl1.json
          digest: sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08

FileFromURL Fields

FieldRequiredDescription
filePathYesDestination path inside the pod
urlYesURL to download the file from
digestYessha256:<64 hex> digest of the file bytes; a download that differs fails

The digest pins the payload: a mutable URL cannot change a device's content without a matching change to the accepted plan. Compute it with sha256sum <file>.

URL Requirements

  • Must be a direct file download (not HTML page)
  • GitHub: Use "raw" URLs
  • Must be an absolute HTTP(S) URL without embedded credentials
  • Must be a publicly resolvable endpoint: the fetch runs from the controller (or the planning worker when the reusable pool is disabled) and again from the preparation init container, and hosts that resolve to private, loopback, or otherwise non-public addresses are rejected
  • Downloads are capped at 64 MB
  • The reusable planner pool also limits all transferred payloads together to 64 MiB per request

Good URLs:

https://raw.githubusercontent.com/user/repo/main/config.json
https://files.example.com/configs/router1.cfg

Bad URLs:

https://github.com/user/repo/blob/main/config.json  # HTML page
https://drive.google.com/file/d/xxx               # Requires auth

Authentication

URL payloads are fetched anonymously. For content behind authentication, load it into a ConfigMap or Secret and reference that instead. Registry credentials belong in Kubernetes imagePull.pullSecrets, not in file mounting.

Containerlab file fields

Containerlab node fields that name a file on the local host are satisfied by payloads instead. A path in the definition that equals the filePath of a filesFromConfigMap, filesFromSecret, or filesFromURL entry of the same Node is served from that payload:

  • startup-config inline text (a value containing newlines) is embedded in the plan and needs no payload. A startup-config path must match a payload filePath.
  • license works the same way: mount the license at the path the field names, as in the Nokia SR-SIM guide.
  • binds entries (source:target with an optional ro) mount a payload into the device at target. The source must be a payload filePath of that Node, or a directory that contains one. Anything else is rejected because there is no host to bind from.
  • env-files are rejected. Put the variables in env.

Destination paths the Pod owns are rejected before anything is created: /etc/hosts, /etc/hostname, /etc/resolv.conf, /dev/termination-log, and everything under /var/lib/clabernetes and /var/run/clabernetes.

Common Use Cases

License Files

# ConfigMap with license
apiVersion: v1
kind: ConfigMap
metadata:
  name: nokia-licenses
data:
  srl-license.key: |
    <license-content>
  sros-license.txt: |
    <license-content>
---
apiVersion: c9s.run/v1alpha1
kind: Topology
spec:
  deployment:
    filesFromConfigMap:
      srl1:
        - filePath: /opt/srlinux/etc/license.key
          configMapName: nokia-licenses
          configMapPath: srl-license.key
      sros1:
        - filePath: /tftpboot/license.txt
          configMapName: nokia-licenses
          configMapPath: sros-license.txt

Startup Configurations

apiVersion: v1
kind: ConfigMap
metadata:
  name: startup-configs
data:
  srl1.json: |
    {
      "system": {
        "name": {"host-name": "srl1-lab"}
      }
    }
  srl2.json: |
    {
      "system": {
        "name": {"host-name": "srl2-lab"}
      }
    }
---
spec:
  deployment:
    filesFromConfigMap:
      srl1:
        - filePath: /etc/opt/srlinux/config.json
          configMapName: startup-configs
          configMapPath: srl1.json
      srl2:
        - filePath: /etc/opt/srlinux/config.json
          configMapName: startup-configs
          configMapPath: srl2.json

Inline Startup Configurations

In a Topology definition, startup-config can be specified in two ways:

File path reference (points to external file):

nodes:
  srl1:
    kind: nokia_srlinux
    startup-config: configs/srl1.cfg

Inline configuration (embedded in YAML):

nodes:
  srl1:
    kind: nokia_srlinux
    startup-config: |
      set / interface ethernet-1/1 admin-state enable
      set / interface ethernet-1/1 subinterface 0 ipv4 address 10.0.0.1/24
      set / network-instance default interface ethernet-1/1.0

Inline configurations containing newlines are embedded in the device plan and need no payload. For a file path reference, provide a ConfigMap, Secret, or URL payload whose filePath matches the startup-config path; the controller cannot read files from your local filesystem.

TLS Certificates

apiVersion: v1
kind: ConfigMap
metadata:
  name: lab-certs
data:
  ca.crt: |
    -----BEGIN CERTIFICATE-----
    ...
    -----END CERTIFICATE-----
  server.crt: |
    -----BEGIN CERTIFICATE-----
    ...
    -----END CERTIFICATE-----
  server.key: |
    -----BEGIN PRIVATE KEY-----
    ...
    -----END PRIVATE KEY-----
---
spec:
  deployment:
    filesFromConfigMap:
      srl1:
        - filePath: /etc/ssl/certs/ca.crt
          configMapName: lab-certs
          configMapPath: ca.crt
        - filePath: /etc/ssl/certs/server.crt
          configMapName: lab-certs
          configMapPath: server.crt
        - filePath: /etc/ssl/private/server.key
          configMapName: lab-certs
          configMapPath: server.key

Custom Scripts

apiVersion: v1
kind: ConfigMap
metadata:
  name: init-scripts
data:
  setup.sh: |
    #!/bin/bash
    echo "Running initialization..."
    # Your setup commands
---
spec:
  deployment:
    filesFromConfigMap:
      srl1:
        - filePath: /tmp/setup.sh
          configMapName: init-scripts
          configMapPath: setup.sh
          mode: execute  # 0o555 permissions

ConfigMap vs URL

AspectConfigMapURL
Size limit1 MB64 MB
UpdatesNew content = new plan, Pod recreatedChange file and digest together
SecurityIn-cluster (use Secrets for sensitive bytes)Public endpoint required
VersioningVia K8sVia URL versioning
Best forSmall configsLarge files, external sources

Troubleshooting

File Not Appearing

Check ConfigMap exists:

kubectl get configmap <name>

Check pod events:

kubectl describe pod <pod-name>

Permission Issues

Ensure correct mode:

  • Scripts: mode: execute
  • Config files: mode: read (default)

ConfigMap Size Limit

If a file exceeds 1MB, mount it from a URL instead or split it into multiple ConfigMaps.

URL Download Failures

A fetch or digest failure during planning is reported on the Node before any workload exists; a failure during staging shows up in the preparation init container:

kubectl describe nodes.c9s.run <node>
kubectl logs deploy/<node> -c planner

Verify URL accessibility:

kubectl run curl-test --rm -it --image=curlimages/curl -- curl -I <url>

On this page