Profile
Back to NewsBack
GitHub Trending 7 min
Reader Mode
carlmontanari/boxen: put your network operating systems in a box!

carlmontanari/boxen: put your network operating systems in a box!

License: MIT</a>


boxen -- put your network operating systems in a box (or if you speak 🇩🇪, fight them! 🤣)!

boxen is a cli tool written in Go that allows you to package your network operating systems neatly into little... boxes (container images) so they are easily portable, and, most importantly, so you can use them with the wonderful containerlab.

Installation

Download the CLI for Linux or macOS, on AMD64 or ARM64, from GitHub releases. The installer detects your OS and architecture and verifies the downloaded archive's SHA-256 checksum:

curl --fail --silent --show-error --location \
  https://raw.githubusercontent.com/carlmontanari/boxen/main/install.sh | sh -

The installation guide also includes a Review and paste tab with the full script for inspection before running it.

Documentation

The documentation site covers installation, profiles, packaging, running labs, and image management. Preview it locally with make docs-serve and build it with make docs-build; uv manages the pinned documentation environment. See documentation and publishing for Cloudflare setup and make docs-publish.

Development

Install Go at the exact version in .github/vars.env, then run make tools to install the pinned formatters and linters into .tools/bin. The installer reads the same version file as CI.

Run make fmt to apply formatting, make fmt-check to check it without edits, make lint for the full local CI lint gate, and make test-race for the CI test gate. make lint installs the pinned tools automatically when needed. The Go module's go directive records the minimum language version; the exact Go toolchain used by CI and the Docker build comes from .github/vars.env. The race tests require a C compiler; on Ubuntu, install gcc and libc6-dev if needed.

Refresh a packaged image runtime and profile

Use rebuild-profile-image to update the Boxen runtime and profile in a packaged image without booting or repackaging its VM disk. Pass the existing local image, a profile path within this repository, and the output image tag:

make rebuild-profile-image \
  SOURCE_IMAGE=ghcr.io/clab-labs/nvidia_cumulusvx:5.16.1 \
  PROFILE_FILE=assets/profiles/nvidia_cumulusvx.yaml \
  TARGET_IMAGE=ghcr.io/clab-labs/nvidia_cumulusvx:5.16.1-rebuilt

The target requires all three variables. It first runs build-image, which compiles Boxen and packages its binary, libscrapli, and the Scrapli definition from this checkout. It then overlays those runtime files and the selected profile onto the local SOURCE_IMAGE. The separate build target creates dist/boxen and is not needed for this workflow. The rebuild preserves the packaged image's resolvedVersion value when the new profile does not define one. The VM disk and operating system layers come from SOURCE_IMAGE; the base image build may download dependencies, but the overlay does not pull the source image. Override BOXEN_IMAGE to tag the base Boxen image differently.

WORK IN PROGRESS. Ask questions in the Containerlab's Discord.

Attach to a VM console

When you want to add support for a new network operating system, you typically want to inspect the VM's boot process and prompts manually before coding them in the profile file. Use boxen build --vm-console to boot the VM and immediately attach your terminal to the VM serial console.

This mode still uses the normal build inputs, such as --diskImage/--disk and --profile/--prof, but stops after the builder container has prepared and started the VM.

boxen build --disk /path/to/disk.qcow2 --profile /path/to/profile.yaml --vm-console

Since the telnet client is used to attach to the VM console, to exit it, type Ctrl+] followed by q.

Transparent Management

By default, Boxen connects the VM management interface with QEMU user networking. In that mode, the VM receives the fixed management address 10.0.0.15/24, uses 10.0.0.2 as its gateway, and the ports a user wants to forward to the VM are driven by the QEMU hostfwd rules defined in virtualMachine.natPorts. This operational mode, however, has several shortcomings:

  • The management IP address configured in the Network Operating System config is static and different from the one assigned to the container management interface by containerlab. This makes every NOS see the same management IP and makes external management systems confused when the nodes report their IP during the onboarding/call-home process.
  • The exposed ports are a fixed set of ports provided by the user and are only forwarded via IPv4 address family due to QEMU limitations. This makes it impossible to forward ports via IPv6 easily.
To solve for these limitations, Boxen offers transparent management mode that can be enabled in the profile by setting:
virtualMachine:
  managementPassthrough: true

When transparent management is enabled at runtime, Boxen connects the VM management NIC to tap0 and redirects traffic between container eth0 and tap0 with tc. The network OS can then use the same management IP address that containerlab assigned to the container management interface. In this mode, QEMU management hostfwd rules are simply not created.

CLAB_MGMT_PASSTHROUGH environment variable overrides the profile setting at runtime. Note, that this env var must be set in the container's shell, not on your host.

  • CLAB_MGMT_PASSTHROUGH=true enables transparent management.
  • CLAB_MGMT_PASSTHROUGH=false forces legacy host-forwarded management.
  • If unset, virtualMachine.managementPassthrough controls the behavior.
Packaging and boxen build --vm-console always use the legacy QEMU user-network management path because containerlab is not providing the transparent management datapath during image build.

Process Types

Write

Write content uses Go template variables. Templates work in the following fields: write.content, write.contentFromFile, and write.contentFromStartupConfig.

run:
  process:
    - type: write
      write:
        content: |
          nv set interface eth0 ipv4 address {{ .mgmtIPv4 }}
          nv set interface eth0 ipv4 gateway {{ .mgmtIPv4Gateway }}

The following values are available:

| Template value | Example value | Description | | -------------------------- | --------------------------- | -------------------------------------------------- | | {{ .disk }} | disk.qcow2 | Disk filename used inside the agent container. | | {{ .version }} | 5.15.0 | Version matched from the profile versionPattern. | | {{ .extraFiles }} | [license.lic startup.cfg] | List of extra file basenames. | | {{ .username }} | admin | Containerlab-provided username during run. | | {{ .password }} | admin | Containerlab-provided password during run. | | {{ .hostname }} | leaf1 | Containerlab node hostname during run. | | {{ .mgmtDHCP }} | false | Whether management config should use DHCP. | | {{ .mgmtIPv4 }} | 172.20.20.10/24 | IPv4 management address in CIDR notation. | | {{ .mgmtIPv4Address }} | 172.20.20.10 | IPv4 management address without prefix length. | | {{ .mgmtIPv4PrefixLen }} | 24 | IPv4 management prefix length. | | {{ .mgmtIPv4Network }} | 172.20.20.0/24 | IPv4 management network in CIDR notation. | | {{ .mgmtIPv4Gateway }} | 172.20.20.1 | IPv4 management default gateway. | | {{ .mgmtIPv6 }} | 2001:db8:20::10/64 | IPv6 management address in CIDR notation. | | {{ .mgmtIPv6Address }} | 2001:db8:20::10 | IPv6 management address without prefix length. | | {{ .mgmtIPv6PrefixLen }} | 64 | IPv6 management prefix length. | | {{ .mgmtIPv6Network }} | 2001:db8:20::/64 | IPv6 management network in CIDR notation. | | {{ .mgmtIPv6Gateway }} | 2001:db8:20::1 | IPv6 management default gateway. |

extraFiles is a list, so index it to reference a single file by position -- for example {{ index .extraFiles 0 }} resolves to license.lic.

In packaging and legacy host-forwarded management mode, management template values resolve to the QEMU user-network defaults (10.0.0.15/24 and 10.0.0.2). In transparent management runtime mode, Boxen reads the values from container eth0 and the container default routes. When CLAB_MGMT_DHCP=true, {{ .mgmtDHCP }} is true and address-specific management values are unavailable, so templates should branch into the NOS-specific DHCP syntax:

content: |
  {{ if .mgmtDHCP }}
  nv set interface eth0 ipv4 address dhcp
  {{ else }}
  nv set interface eth0 ipv4 address {{ .mgmtIPv4 }}
  nv set interface eth0 ipv4 gateway {{ .mgmtIPv4Gateway }}
  {{ end }}

IPv6 values can be empty when the container management interface has no global IPv6 address or default route, so profile commands that use IPv6 should guard them when needed:

content: |
  if [ -n "{{ .mgmtIPv6 }}" ]; then nv set interface eth0 ipv6 address {{ .mgmtIPv6 }}; fi
Chat with me