step1nsdbx a.k.a. setting up and launching a jailed Firecracker microVM on a linux server as a lightweight and secured sandbox for production.
Note: This blog post assumes you are running Ubuntu 24.04 LTS or later on an x86_64 system with hardware virtualization support (Intel VT-x or AMD-V).
[[ $(egrep -c '(vmx|svm)' /proc/cpuinfo) -gt 0 ]] && echo "OK" || echo "NOK"
sudo modprobe kvm_intel
Or for AMD:
sudo modprobe kvm_amd
kvm group for access to /dev/kvm:
POLP="step1nsdbx"
sudo adduser --system --group "$POLP" --shell /bin/false --home /var/lib/"$POLP"
sudo usermod -aG kvm "$POLP"
POLP="step1nsdbx"
sudo -u "$POLP" bash -c 'kvm-ok'
Install if needed: sudo apt install cpu-checker.
sudo apt update && sudo apt upgrade -y
sudo apt autoremove -y
sudo apt install -y curl jq
[!TIP]
Review also firecracker team’s prod setup guidelines.
mkdir "${POLP}-build-dir"
cd "${POLP}-build-dir"
ARCH="$(uname -m)"
release_url="https://github.com/firecracker-microvm/firecracker/releases"
latest_version=$(basename $(curl -fsSLI -o /dev/null -w %{url_effective} ${release_url}/latest))
curl -LO https://github.com/firecracker-microvm/firecracker/releases/download/$latest_version/firecracker-$latest_version-$ARCH.tgz
tar -xzf firecracker-$latest_version-$ARCH.tgz
sudo mv release-$latest_version-$ARCH/firecracker-$latest_version-$ARCH /usr/local/bin/firecracker
sudo mv release-$latest_version-$ARCH/jailer-$latest_version-$ARCH /usr/local/bin/sdbx-jailer
# cleaning
rm -r release-$latest_version-$ARCH firecracker-$latest_version-$ARCH.tgz
Firecracker requires a Linux kernel image and a root filesystem (rootfs) for the guest VM.
CI_VERSION=${latest_version%.*}
latest_kernel_key=$(curl "http://spec.ccfc.min.s3.amazonaws.com/?prefix=firecracker-ci/$CI_VERSION/$ARCH/vmlinux-&list-type=2" \
| grep -oP "(?<=<Key>)(firecracker-ci/$CI_VERSION/$ARCH/vmlinux-[0-9]+\.[0-9]+\.[0-9]{1,3})(?=</Key>)" \
| sort -V | tail -1)
curl -fsSL -o sdbx_kernel.bin "https://s3.amazonaws.com/spec.ccfc.min/$latest_kernel_key"
# Guest VM internet access
TAP_DEV="tap0"
TAP_NET="172.16.42"
MASK_SHORT="/30"
## Create the tap device
sudo ip tuntap add "$TAP_DEV" mode tap
## Assign it the tap IP and start up the device
sudo ip addr add "${TAP_NET}.1${MASK_SHORT}" dev "$TAP_DEV"
sudo ip link set "$TAP_DEV" up
# Guest VM access via network
BR_IF="br0"
BR_NET="172.16.243"
LOCAL_MASK="/29"
## Create the bridge interface
sudo ip link add name "$BR_IF" type bridge
## Add the above tap device to the bridge
sudo ip link set dev "$TAP_DEV" master "$BR_IF"
## Give the bridge an IP address in its subnet pool
sudo ip address add "${BR_NET}.1${LOCAL_MASK}" dev "$BR_IF"
sudo ip link set "$BR_IF" up
# Enable IPv4 forwarding and NAT so the guest can reach the internet
echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward
# Host firewall rule
HOST_IFACE=$(ip -j route list default |jq -r '.[0].dev')
##
sudo nft add table firecracker
sudo nft 'add chain firecracker postrouting { type nat hook postrouting priority srcnat; policy accept; }'
sudo nft 'add chain firecracker filter { type filter hook forward priority filter; policy accept; }'
## Guest IP masquerade
sudo nft add rule firecracker postrouting ip saddr "${TAP_NET}.2" oifname "$HOST_IFACE" counter masquerade
## Accecpt pkt from tap and redirect to host main net interface
sudo nft add rule firecracker filter iifname "$TAP_DEV" oifname "$HOST_IFACE" accept
## Bridge FW conf: allow traffic to be routed to the guest
sudo nft add rule firecracker postrouting oifname "$BR_IF" counter masquerade
sudo apt install -y debootstrap
mkdir mnt rootfs
sudo debootstrap --variant=minbase --include=apt,sudo,netplan.io,vim,openssh-server,ufw plucky rootfs https://archive.ubuntu.com/ubuntu/
dd if=/dev/zero of=sdbx_rootfs.ext4 bs=1M count=1024
mkfs.ext4 sdbx_rootfs.ext4
sudo mount -o loop sdbx_rootfs.ext4 mnt
ssh-keygen -t ed25519 -f sdbx_sk -N ""
sudo mkdir -p rootfs/root/.ssh
sudo cp -v sdbx_sk.pub rootfs/root/.ssh/authorized_keys
sudo bash -c 'echo "hardened-sdbx" > rootfs/etc/hostname'
sudo cp -a rootfs/* mnt/
sudo chroot mnt
apt update && apt upgrade -y && apt modernize-sources -yapt install --no-install-recommends -y adduser apparmorsystemctl disable --now avahi-daemon cups systemd-resolvedufw --force enable && ufw default deny incoming && ufw allow from 172.16.42.1 (allow host IP)adduser --disabled-password zoba && usermod -aG sudo zoba && echo "zoba:s7r0ngp4ssw0rd" | chpasswdecho "nameserver 8.8.8.8" >> /etc/resolv.conf/etc/sysctl.conf and add:
kernel.kptr_restrict=2
kernel.dmesg_restrict=1
net.ipv4.conf.all.rp_filter=1
net.ipv4.conf.default.rp_filter=1
net.ipv4.tcp_syncookies=1
Apply: sysctl -p
systemctl enable ssh && ufw allow 22/tcpsystemctl enable apparmor && aa-enforce /etc/apparmor.d/*passwd -d rootapt autoremove --purge -y && rm -rf /var/cache/apt/*exitsudo umount mnt
chmod 444 sdbx_rootfs.ext4
Using the Jailer to launch Firecracker in a secured environment applies chroot, cgroups, seccomp, and runs as non-root.
SDBXID="sdbx-$(uuidgen -r)"
sudo mkdir -p "/srv/jailer/firecracker/$SDBXID/root" "/sys/fs/cgroup/firecracker/$SDBXID"
sudo cp sdbx_kernel.bin sdbx_rootfs.ext4 "/srv/jailer/firecracker/$SDBXID/root/"
sudo chown -R "$POLP":"$POLP" "/srv/jailer/firecracker/$SDBXID/root" "/sys/fs/cgroup/firecracker/$SDBXID"
# cleaning
rm sdbx_kernel.bin sdbx_rootfs.ext4 sdbx_sk.pub
rmdir mnt
sudo rm -rf rootfs
sudo -u "$POLP" bash -c "cat -> /srv/jailer/firecracker/$SDBXID/root/sdbx_config.json"
{
"boot-source": {
"kernel_image_path": "sdbx_kernel.bin",
"boot_args": "8250.nr_uarts=0 reboot=k panic=1 pci=off ip=172.16.42.2::172.16.42.1:255.255.255.252::eth0:on",
"initrd_path": null
},
"drives": [
{
"drive_id": "rootfs",
"partuuid": null,
"is_root_device": true,
"is_read_only": true,
"cache_type": "Writeback",
"path_on_host": "sdbx_rootfs.ext4",
"io_engine": "Sync",
"rate_limiter": {
"bandwidth": {
"size": 100000,
"one_time_burst": 4096,
"refill_time": 150
},
"ops": {
"size": 10,
"refill_time": 250
}
},
"socket": null
}
],
"machine-config": {
"vcpu_count": 1,
"mem_size_mib": 1024,
"smt": false,
"track_dirty_pages": false,
"huge_pages": "None"
},
"network-interfaces": [
{
"iface_id": "eth0",
"guest_mac": "00:16:3E:42:DE:AD",
"host_dev_name": "tap0",
"rx_rate_limiter": {
"bandwidth": {
"size": 1024,
"one_time_burst": 1048576,
"refill_time": 1000
}
},
"tx_rate_limiter": {
"bandwidth": {
"size": 1024,
"one_time_burst": 1048576,
"refill_time": 1000
}
}
}
],
"cpu-config": null,
"balloon": null,
"vsock": null,
"logger": null,
"metrics": null,
"mmds-config": null,
"entropy": null,
"pmem": [],
"memory-hotplug": null
}
# Limit memory to 512MB
sudo sdbx-jailer --id "$SDBXID" \
--exec-file /usr/local/bin/firecracker \
--uid $(id -u "$POLP") \
--gid $(id -g "$POLP") \
--chroot-base-dir /srv/jailer \
--cgroup-version 2 \
--cgroup "memory.max=536870912" \
--resource-limit "no-file=4096" \
--new-pid-ns \
--daemonize \
-- \
--level debug \
--log-path "./sdbx_firecracker.log" \
--config-file "./sdbx_config.json"
sudo chmod 200 "/srv/jailer/firecracker/$SDBXID/root/run/firecracker.socket"
sudo -u "$POLP" bash -c "curl --unix-socket /srv/jailer/firecracker/$SDBXID/root/run/firecracker.socket \
-X GET 'http://localhost/vm/config' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json'" | jq '.'
[!NOTE]
Please be aware that the device serial console can be reactivated from within the guest even if it was disabled at boot.
ssh -i sdbx_sk root@"${TAP_NET}.2"
# API soft shutdown
sudo -u $POLP bash -c "curl --unix-socket /srv/jailer/firecracker/$SDBXID/root/run/firecracker.socket -i \
-X PUT 'http://localhost/actions' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
\"action_type\": \"SendCtrlAltDel\"
}'"
Firecracker does not natively support graphical hardware, optimizing for headless workloads. To add GUI, run a desktop environment in the guest and expose it securely over the network using SSH tunneling or TLS-enabled protocols to mitigate risks like MITM attacks.
ssh -i sdbx_sk root@"${TAP_NET}.2"
apt update
apt install -y xfce4 xrdp # Lightweight desktop + RDP
systemctl enable xrdp
ufw allow 3389/tcp # RDP port, but we'll tunnel
ssh -L 3389:localhost:3389 zoba@"${TAP_NET}.2"
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/xrdp/key.pem -out /etc/xrdp/cert.pem), edit /etc/xrdp/xrdp.ini to enable security_layer=tls.[!WARNING]
GUI access increases risks. For ultimate security, prefer headless operation and API/scripting over GUI.
sudo cat /srv/jailer/firecracker/$SDBXID/root/sdbx_firecracker.log
sudo pkill -9 firecracker
sudo rm /srv/jailer/firecracker/$SDBXID/root/run/firecracker.socket
sudo rm -rf /srv/jailer/firecracker/$SDBXID/root/dev
sudo ip link del "$TAP_DEV"
sudo ip link del "$BR_IF"
sudo nft -a list ruleset
sudo nft delete rule firecracker postrouting handle 1
sudo nft delete rule firecracker filter handle 2
sudo nft delete table firecracker
# If you have no more guests running on the host
echo 0 | sudo tee /proc/sys/net/ipv4/ip_forward
# Clear the building directory
cd .. && rmdir "${POLP}-build-dir"