-
Notifications
You must be signed in to change notification settings - Fork 0
Image RaspiVirt Incus
RaspiVirt-Incus is a Raspberry Pi image optimized for running containers and virtual machines using Incus, the modern LXC/LXD fork. This image provides a complete virtualization platform with KVM support, bridged networking, and the Incus web UI.
RaspiVirt-Incus transforms your Raspberry Pi into a powerful virtualization host capable of running:
- System containers (LXC) - Lightweight, fast containers with full OS isolation
- Application containers - OCI-compatible containers
- Virtual machines - KVM-accelerated VMs with full hardware virtualization
- Incus Container Manager: Modern LXC/LXD fork with active development
- Incus Web UI: Built-in web interface for easy management (port 8443)
- KVM Virtualization: Hardware-accelerated virtual machines on ARM64
-
Bridged Networking:
br-wanbridge for direct network access to containers/VMs - Automatic Updates: RaspiOS kernel + Debian packages via APT
- First-Boot Configuration: Automatic partition resize and network setup
-
Classic Network Names: Uses
eth0instead ofenp*for predictable naming
-
Image Name:
rpi-raspivirt-incus.img.xz - Base OS: Debian 13 (Trixie) ARM64
- Kernel: Raspberry Pi OS kernel (with RP1 drivers)
- Image Size: ~1.5 GB (expands on first boot)
- Compressed Size: ~500MB (xz compressed)
From images/raspivirt-incus/config.sh:
OUTPUT_IMAGE="rpi-raspivirt-incus.img"
IMAGE_SIZE="4G"
QEMU_RAM="8G"
QEMU_CPUS="4"
DESCRIPTION="Raspberry Pi image with Incus, KVM virtualization and br-wan bridge"- Debian 13 (Trixie) - Latest Debian stable
-
Raspberry Pi Kernel - Full hardware support (RP1, WiFi, Bluetooth)
-
linux-image-rpi-v8- Raspberry Pi 3/4/5 kernel -
linux-image-rpi-2712- Raspberry Pi 5 optimized kernel -
linux-headers-rpi-v8- Kernel headers for module compilation -
linux-headers-rpi-2712- Pi 5 kernel headers -
raspi-firmware- Bootloader and firmware -
firmware-brcm80211- WiFi/Bluetooth firmware
-
-
Incus - Container and VM manager
-
incus- Core daemon and CLI -
incus-ui-canonical- Official web UI
-
-
KVM/QEMU - Hardware virtualization
-
qemu-system-aarch64- ARM64 system emulator -
qemu-kvm- KVM acceleration support -
qemu-utils- Image utilities -
qemu-efi-aarch64- UEFI firmware for VMs
-
- systemd-networkd - Network management
- netplan.io - Network configuration
- bridge-utils - Bridge utilities
- net-tools - Classic networking tools (ifconfig, route)
- iptables - Firewall and NAT
- curl, wget - Download tools
- sudo - Privilege escalation
- openssh-server - Remote access
- parted - Partition management
- ca-certificates, gnupg - Security and package verification
The image uses a bridged network configuration (br-wan) that allows containers and VMs to appear as physical devices on your network.
network:
version: 2
renderer: networkd
ethernets:
eth0:
dhcp4: false
dhcp6: false
bridges:
br-wan:
interfaces: [eth0]
dhcp4: true
dhcp6: trueInternet
↓
Your Router (DHCP)
↓
┌─────────────────────────────────┐
│ Raspberry Pi │
│ ┌───────────────────────────┐ │
│ │ br-wan (Bridge) │ │ ← Gets IP from router
│ │ ├─ eth0 (Physical NIC) │ │
│ │ ├─ Container 1 │ │ ← Gets IP from router
│ │ ├─ Container 2 │ │ ← Gets IP from router
│ │ └─ VM 1 │ │ ← Gets IP from router
│ └───────────────────────────┘ │
└─────────────────────────────────┘
Advantages:
- Containers/VMs get IPs directly from your router
- No NAT required
- Containers/VMs accessible from your LAN
- Simplified networking
Incus is configured to use the br-wan bridge in passthrough mode:
- Incus network name:
br-wan - Parent device:
br-wan(system bridge) - IPv4/IPv6: Disabled (managed by external DHCP)
- All containers/VMs attach to
br-wanby default
The image uses a two-stage first-boot process to configure the system:
Runs on first boot before network is configured.
Script: /usr/local/bin/rpi-first-boot.sh
Actions:
-
Enable classic network names:
- Adds
net.ifnames=0 biosdevname=0to/boot/firmware/cmdline.txt - Results in
eth0,wlan0instead ofenp*,wlp*
- Adds
-
Disable cloud-init networking:
- Creates
/etc/cloud/cloud.cfg.d/99-disable-network-config.cfg - Prevents cloud-init from managing network (netplan takes over)
- Creates
-
Resize root partition:
- Detects root partition
- Expands to use full SD card/SSD
- Resizes ext4 filesystem
-
Deploy netplan configuration:
- Moves
99-br-wan.yamlto/etc/netplan/ - Generates netplan configuration
- Moves
-
Self-destruct:
- Disables systemd service
- Deletes service file and script
- Reboots system
Runs after reboot when network is available.
Script: /usr/local/bin/services-first-boot.sh
Actions:
-
Wait for internet connectivity:
- Pings 8.8.8.8 and 1.1.1.1
- Timeout: 5 minutes
- Required for Incus initialization
-
Initialize Incus:
- Minimal initialization:
incus admin init --minimal - Configure HTTPS UI:
incus config set core.https_address :8443 - Apply netplan (creates
br-wanbridge) - Create Incus network using
br-wan - Attach
br-wanto default profile
- Minimal initialization:
-
Self-destruct:
- Disables systemd service
- Deletes service file and script
Why Two Stages?
- Stage 1 runs before network to resize partition and configure network
- Stage 2 runs after network to initialize services requiring internet access
- Ensures proper ordering of operations
The image includes a dynamic Message of the Day (MOTD) that displays network information on login.
Script: /usr/local/bin/update-motd-ip.sh
Triggers:
- On boot (
update-motd-ip.service) - On network changes (
update-motd-ip.pathmonitors/etc/netplan/)
Displayed Information:
- System hostname
- IP addresses (IPv4/IPv6)
- Network interfaces
- Incus web UI URL (https://IP:8443)
-
Username:
pi -
Password:
raspberry -
Sudo: Passwordless (
NOPASSWD:ALL) -
Groups:
sudo,kvm,incus,incus-admin
Cloud-Init Configuration (cloudinit/user-data):
users:
- name: pi
sudo: ['ALL=(ALL) NOPASSWD:ALL']
shell: /bin/bash
lock_passwd: false
chpasswd:
list: |
pi:raspberry
expire: false
ssh_pwauth: trueAfter first boot, immediately:
- Change the default password:
passwd pi - Add SSH keys:
ssh-copy-id pi@<raspberry-pi-ip> - Disable password authentication: Edit
/etc/ssh/sshd_config - Configure Incus authentication (see below)
- Get Raspberry Pi IP address:
ip addr show br-wan - Open browser:
https://<raspberry-pi-ip>:8443 - Accept self-signed certificate
- Create admin account on first login
# Check Incus status
incus info
# List containers/VMs
incus list
# Launch a container (gets IP from br-wan/DHCP)
incus launch images:debian/13 mycontainer
# Launch a VM
incus launch images:debian/13 myvm --vm
# Access container console
incus exec mycontainer -- bash
# Stop container
incus stop mycontainer
# Delete container
incus delete mycontainer# Launch Debian container
incus launch images:debian/13 web1
# Check assigned IP (from your router)
incus list
# Container is accessible from your LAN
ping <container-ip>
ssh user@<container-ip># Launch Ubuntu VM with 2GB RAM, 2 CPUs
incus launch images:ubuntu/22.04 vm1 --vm \
-c limits.memory=2GB \
-c limits.cpu=2
# Access VM console
incus console vm1
# Access VM via SSH (after VM gets DHCP IP)
ssh user@<vm-ip>The default profile uses br-wan:
# View default profile
incus profile show default
# Example output:
# devices:
# eth0:
# nictype: bridged
# parent: br-wan
# type: nicAll containers/VMs automatically get network access through br-wan.
# Increased network buffer sizes for better performance
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.ipv4.tcp_rmem = 4096 87380 67108864
net.ipv4.tcp_wmem = 4096 65536 67108864
# Enable IP forwarding for containers/VMs
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1-
Timezone: Europe/Paris (configurable in
setup.sh) -
Locale:
fr_FR.UTF-8(configurable insetup.sh)
The image includes both Debian and RaspiOS repositories:
Debian (/etc/apt/sources.list):
deb http://deb.debian.org/debian trixie main contrib non-free
RaspiOS (/etc/apt/sources.list.d/raspi.sources):
Types: deb
URIs: http://archive.raspberrypi.com/debian/
Suites: trixie
Components: main
Signed-By: /usr/share/keyrings/raspberrypi-archive-keyring.pgp
Incus (/etc/apt/sources.list.d/zabbly-incus-stable.sources):
Types: deb
URIs: https://pkgs.zabbly.com/incus/stable
Suites: trixie
Components: main
Signed-By: /etc/apt/keyrings/zabbly.asc
# Pin RaspiOS packages for kernel/firmware/bootloader
Package: raspberrypi-kernel raspberrypi-bootloader libraspberrypi* firmware-brcm80211
Pin: release o=Raspberry Pi Foundation
Pin-Priority: 1001
# Default Debian packages
Package: *
Pin: release o=Debian
Pin-Priority: 500
This ensures:
- Kernel and firmware come from RaspiOS (hardware compatibility)
- All other packages come from Debian (stability and security)
# Update all packages (Debian + RaspiOS kernel/firmware)
sudo apt update
sudo apt upgrade -y
# Auto-upgrade without prompts
sudo apt full-upgrade -yThe pinning configuration prevents accidental kernel changes while allowing safe updates.
- Run multiple containers for different services
- Host web servers, databases, development environments
- Isolated environments for testing
- Create disposable development containers
- Test across multiple Linux distributions
- Develop and test ARM64 applications
- DNS server (Pi-hole in container)
- VPN server (WireGuard/OpenVPN)
- Home automation (Home Assistant)
- Learn containerization and virtualization
- Experiment with different Linux distributions
- Practice system administration
- Fork this repository
-
Edit configuration (
images/raspivirt-incus/config.sh):- Change image size
- Adjust QEMU resources
-
Customize setup script (
images/raspivirt-incus/setup.sh):- Add packages
- Change timezone/locale
- Install additional software
-
Add custom files (
images/raspivirt-incus/setupfiles/):- Configuration files
- Scripts
- Certificates
-
Modify cloud-init (
images/raspivirt-incus/cloudinit/user-data):- Change default user
- Add SSH keys
- Modify passwords
- Commit and push - GitHub Actions builds automatically
Edit setup.sh around line 20:
apt install -y \
curl \
wget \
sudo \
openssh-server \
vim \
htop \
docker.io # Add DockerEdit cloudinit/user-data:
users:
- name: admin # Change username
sudo: ['ALL=(ALL) NOPASSWD:ALL']
shell: /bin/bash
lock_passwd: false
chpasswd:
list: |
admin:securepassword # Change passwordEdit config.sh:
IMAGE_SIZE="16G" # Increase to 16GBCheck Incus status:
sudo systemctl status incusVerify HTTPS listener:
incus config get core.https_address
# Should show: :8443Check firewall (if enabled):
sudo iptables -L -n | grep 8443Verify br-wan bridge:
ip addr show br-wan
# Should show an IP addressCheck netplan:
sudo netplan statusVerify Incus network:
incus network show br-wanCheck logs:
# Check rpi-first-boot service
journalctl -u rpi-first-boot.service
# Check services-first-boot service
journalctl -u services-first-boot.serviceCommon issues:
- No internet connectivity (services-first-boot requires internet)
- DHCP not available on network
- Network cable not connected
Manual resize:
# Identify root partition
sudo fdisk -l
# Resize partition (example: /dev/mmcblk0p2)
sudo parted /dev/mmcblk0 resizepart 2 100%
# Resize filesystem
sudo resize2fs /dev/mmcblk0p2- Use SSD instead of SD card (USB 3.0 or NVMe via PCIe)
- Enable TRIM for SSDs
- Use high-quality SD cards (A2 rating minimum)
- Prefer containers over VMs (lower overhead)
- Use ZFS storage pool for better performance (optional)
- Limit container resources appropriately
# Check CPU/RAM usage
htop
# Check disk usage
df -h
# Check Incus resource usage
incus info --resources- Home: Project overview
- GitHub Actions: Automated build system
- RaspiVirt-Incus+Docker: This image plus Docker
- Incus Documentation: Official Incus docs
- Debian Documentation: Debian reference
GitHub Actions Workflow: Automatically builds this image on push and daily schedule
Build Process:
- Download Raspberry Pi OS and Debian base images
- Execute
setup.shin QEMU ARM64 - Install RaspiOS kernel and Incus via APT
- Merge boot partition and rootfs
- Compress with PiShrink
Download: Latest Release
Build Logs: GitHub Actions