A lab server is an essential resource for learning and experimentation. It can be used for anything from spinning up a simple virtual networking lab to deploying a complex multi-cluster Kubernetes topology using Vagrant .

This post provides an opinionated step-by-step guide for installing and configuring a headless bare-metal lab server using Arch Linux, which can be completed in under an hour.

After completing this guide, your lab server will be ready for use with the following projects:

Why select Arch for the Linux distribution? Arch Linux is relatively stable and has a minimal footprint in terms of computing resource usage. It also has the significant upside of streamlined access to the latest software.

Base

  1. Disable Secure Boot in the BIOS settings.

  2. Start the computer with a bootable USB flash drive provisioned with the Arch Linux ISO.

Boot Arch Linux ISO
  1. Run archinstall at the prompt.
archinstall
  1. Set the configuration.

For instance, I utilize the following configuration based on my specific requirements:

Option Value(s)
Archinstall language English (100%)
Locales


Keyboard layout: us
Locale language: en_US.UTF-8
Locale encoding: UTF-8
Mirrors and repositories United States
Disk configuration


Use a best-effort default partition layout
Filesystem: xfs
Would you like to create a separate partition for /home?: No
Swap Swap on zram: Enabled
Bootloader Systemd-boot
Unified kernel images Disabled
Hostname arch
Authentication





Root password: *****
User account:
  username: marc
  password: *****
  sudo: True
  groups: []
Profile Profiles: Minimal
Applications
Kernels linux
Network configuration





Manual configuration:
  iface: enp191s0
  ip: 192.168.137.52/24
  dhcp: False
  gateway: 192.168.137.1
  dns: 192.168.137.1
Additional packages


less
openssh
vim
Timezone US/Pacific
Automatic time sync (NTP) NTP: Enabled
  1. Select Install.

  2. Remove the bootable USB flash drive and select Reboot system after installation is complete.

  3. Log in with the user account.

  4. Verify essential packages are installed.

pacman -Qqe

output:

base
base-devel
efibootmgr
intel-ucode
less
linux
linux-firmware
openssh
vim
xfsprogs
zram-generator

amd-ucode for computers with an AMD CPU.

  1. Enable and start the OpenSSH daemon.
sudo systemctl enable --now sshd
  1. Verify the OpenSSH server process (sshd) is running and listening on port 22.
sudo ss -ntlp sport = :22
  1. (Optional) Connect remotely for the remaining steps.

For my example, I establish a connection to the 192.168.137.52 IP address with the user account marc as defined in the archinstall step.

ssh [email protected]
  1. Install the zsh package.
sudo pacman -S zsh
  1. Run Z shell (Zsh).
zsh
  1. Create an empty Zsh configuration file.
This is the Z Shell configuration function for new users,
zsh-newuser-install.
You are seeing this message because you have no zsh startup files
(the files .zshenv, .zprofile, .zshrc, .zlogin in the directory
~).  This function can help you with a few settings that should
make your use of the shell easier.

You can:

(q)  Quit and do nothing.  The function will be run again next time.

(0)  Exit, creating the file ~/.zshrc containing just a comment.
     That will prevent this function being run again.

(1)  Continue to the main menu.

--- Type one of the keys in parentheses ---

Enter 0.

  1. Exit out of Zsh back to the default Bash shell.
exit
  1. Set the default shell to Zsh.
chsh -s /usr/bin/zsh
  1. Log out and back in for the shell change to take effect.

  2. Install and configure Zsh for Humans .

sh -c "$(curl -fsSL https://raw.githubusercontent.com/romkatv/zsh4humans/v5/install)"

Answer each question to customize Zsh to your liking.

  1. Install Python (with tooling) and Go.
sudo pacman -S python uv go
  1. Set optimized compiler options for Makepkg .
sudo vim /etc/makepkg.conf

Modify the following compiler flags:

  • march=x86-64march=native
  • Remove mtune=generic
  • O2O3
#########################################################################
# ARCHITECTURE, COMPILE FLAGS
#########################################################################
#
CARCH="x86_64"
CHOST="x86_64-pc-linux-gnu"

#-- Compiler and Linker Flags
#CPPFLAGS=""
CFLAGS="-march=native -O3 -pipe -fno-plt -fexceptions \
        -Wp,-D_FORTIFY_SOURCE=3 -Wformat -Werror=format-security \
        -fstack-clash-protection -fcf-protection \
        -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer"
CXXFLAGS="$CFLAGS -Wp,-D_GLIBCXX_ASSERTIONS"
LDFLAGS="-Wl,-O1 -Wl,--sort-common -Wl,--as-needed -Wl,-z,relro -Wl,-z,now \
         -Wl,-z,pack-relative-relocs"
LTOFLAGS="-flto=auto"
#-- Make Flags: change this for DistCC/SMP systems
#MAKEFLAGS="-j2"
#-- Debugging flags
DEBUG_CFLAGS="-g"
DEBUG_CXXFLAGS="$DEBUG_CFLAGS"
  1. Install an AUR helper .

Create the src directory and change to it.

mkdir -p ~/src && cd $_

I prefer yay , so I install it with the following chain of commands:

sudo pacman -S --needed git base-devel && git clone https://aur.archlinux.org/yay.git && cd yay && makepkg -sci

QEMU/KVM

  1. Install the qemu-base package.
yay -S qemu-base
  1. Enable Kernel Same-page Merging (KSM ).

Set for runtime.

echo 1 | sudo tee /sys/kernel/mm/ksm/run

Make the configuration persistent.

printf 'w /sys/kernel/mm/ksm/run - - - - 1\n' | sudo tee /etc/tmpfiles.d/ksm.conf

Example: Cisco Catalyst 8000V devices running on a host with KSM enabled.

KSM with Cisco Catalyst 8000V
  1. Modify the KVM halt polling interval .

This setting reduces idle CPU usage for specific virtual Cisco devices (e.g., Catalyst 8000V and CSR 1000v).

Get the default value.

cat /sys/module/kvm/parameters/halt_poll_ns

output:

200000

Set for runtime.

echo 0 | sudo tee /sys/module/kvm/parameters/halt_poll_ns

Make the configuration persistent.

printf 'options kvm halt_poll_ns=0\n' | sudo tee /etc/modprobe.d/qemu-kvm.conf
  1. Verify KVM nested virtualization is enabled.

For computers with an Intel CPU:

cat /sys/module/kvm_intel/parameters/nested

output:

Y

For computers with an AMD CPU:

cat /sys/module/kvm_amd/parameters/nested

output:

1

Libvirt

  1. Install packages.
yay -S bridge-utils dmidecode dnsmasq iptables-nft libvirt openbsd-netcat

Enter y when prompted whether to remove iptables.

  1. Add the LIBVIRT_DEFAULT_URI shell variable to the Zsh configuration file.
vim ~/.zshrc
61
62
63
# Export environment variables.
export GPG_TTY=$TTY
export LIBVIRT_DEFAULT_URI=qemu:///system
  1. Set the Libvirt firewall backend to iptables.
sudo vim /etc/libvirt/network.conf
# Master configuration file for the network driver.
# All settings described here are optional - if omitted, sensible
# defaults are used.

# firewall_backend:
#
#   determines which subsystem to use to setup firewall packet
#   filtering rules for virtual networks.
#
#   Supported settings:
#
#     iptables - use iptables commands to construct the firewall
#     nftables - use nft commands to construct the firewall
#
#   If firewall_backend isn't configured, libvirt will choose the
#   first available backend from the following list:
#
#     [nftables, iptables]
#
#   If no backend is available on the host, then the network driver
#   will fail to start, and an error will be logged.
#
#   (NB: switching from one backend to another while there are active
#   virtual networks *is* supported. The change will take place the
#   next time that libvirtd/virtnetworkd is restarted - all existing
#   virtual networks will have their old firewalls removed, and then
#   reloaded using the new backend.)
#
firewall_backend = "iptables"
  1. Use the systemd DNS stub file for name resolution.
sudo ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
  1. Enable Libvirt name resolution.
sudo vim /etc/nsswitch.conf

Add libvirt and libvirt_guest to the beginning of the query order for the hosts database.

# Name Service Switch configuration file.
# See nsswitch.conf(5) for details.

passwd: files systemd
group: files [SUCCESS=merge] systemd
shadow: files systemd
gshadow: files systemd

publickey: files

hosts: libvirt libvirt_guest mymachines resolve [!UNAVAIL=return] files myhostname dns
networks: files

protocols: files
services: files
ethers: files
rpc: files

netgroup: files
  1. Add yourself to the libvirt group.
sudo gpasswd -a $USER libvirt
  1. Log out and back in for the new group membership to take effect.

  2. Verify the group membership.

id -Gn | cut -d' ' -f2-

output:

libvirt wheel
  1. Enable and start the libvirtd service.
sudo systemctl enable --now libvirtd.service
  1. Validate the QEMU/KVM stack.
virt-host-validate qemu

Vagrant

  1. Install the vagrant package.
yay -S vagrant
  1. Install vagrant-libvirt plugin on Arch Linux .

  2. Verify the Vagrant installation.

vagrant --version && vagrant plugin list

Containerlab

  1. Install Docker (with tooling) and the containerlab package.
yay -S docker docker-compose docker-buildx containerlab
  1. Add yourself to the docker group.
sudo gpasswd -a $USER docker
  1. Log out and back in for the new group membership to take effect.

  2. Verify the group membership.

id -Gn | cut -d' ' -f2-

output:

docker libvirt wheel
  1. Enable and start the docker service.
sudo systemctl enable --now docker.service
  1. Verify the status of Docker.
docker info
  1. (Optional) Test the installation with hello-world.
docker run --rm hello-world
  1. Create the clab alias.
vim ~/.zshrc

Add the alias.

93
94
95
# Define aliases.
alias tree='tree -a -I .git'
alias clab='containerlab'
  1. Enable the setuid flag for the containerlab binary file.
sudo chmod u+s $(which containerlab)
  1. Create the clab_admins system group.
sudo groupadd -r clab_admins
  1. Add yourself to the clab_admins group.
sudo gpasswd -a $USER clab_admins
  1. Log out and back in for the new group membership to take effect.

  2. Verify the group membership.

id -Gn | cut -d' ' -f2-

output:

clab_admins docker libvirt wheel
  1. Verify the clab alias.
clab version

Netlab

  1. Install the sshpass package.
yay -S sshpass
  1. Disable the libvirt probe .
printf 'providers.libvirt.probe: []\n' > ~/.netlab.yml

Bonus

  1. Disable the password prompt for sudo.
sudo sed -i 's/ALL=(ALL)/& NOPASSWD:/' /etc/sudoers.d/00_${USER}
  1. Verify the modification.
sudo -ll

output:

Matching Defaults entries for marc on arch:
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/bin

Runas and Command-specific defaults for marc:
    Defaults!/usr/bin/visudo env_keep+="SUDO_EDITOR EDITOR VISUAL"

User marc may run the following commands on arch:

Sudoers entry: /etc/sudoers.d/00_marc
    RunAsUsers: ALL
    Options: !authenticate
    Commands:
        ALL
  1. Install the tmux package.
yay -S tmux
  1. Set a basic tmux configuration.
printf 'set -g mouse on\nset-option -ga terminal-overrides ",xterm-256color:Tc"\nset-option -g default-terminal "screen-256color"\n' > $HOME/.tmux.conf