TAIL OSv0.9.0

Running TAIL OS in QEMU

Try TAIL OS on your Linux machine in under two minutes — no hardware, no toolchain. For installing TAIL OS on real Raspberry Pi 3 hardware, see Installing on Raspberry Pi 3.

Prerequisites

Item Install command
Linux host (Ubuntu 22.04 / 24.04 tested) —
qemu-system-aarch64 sudo apt-get install -y qemu-system-arm qemu-utils
curl or wget usually preinstalled

That's it. Everything else is bundled in the prebuilt images.

Run TAIL OS

Download the launcher, then run it:

curl -O https://tail-os.com/downloads/run_tailos_qemu.sh
chmod +x run_tailos_qemu.sh
./run_tailos_qemu.sh

The launcher will:

  1. Check that QEMU is installed.
  2. Download tail_qemu.rfs (kernel) and tail_disk.img (data disk) into ~/.cache/tailos/, verifying each against the published SHA256SUMS. Later runs reuse the cache until a new image is published.
  3. Boot TAIL OS in QEMU with the correct flags.

When you see /$, the system is ready. Exit QEMU with Ctrl-A, then lowercase x. Booting it again is ./run_tailos_qemu.sh: it starts from the cached images.

Try the Shell

/$ help
/$ pwd
/$ ls /usr/bin
/$ /usr/bin/ps
/$ cd /usr/bin
/usr/bin$ exit

The prompt shows the current directory (it changes after cd). The shell also supports arrow-key history, per-process cwd, and background jobs with &.

exit ends the shell, and no new one starts: nothing runs and no prompt returns, though what you type still echoes. Quit QEMU with Ctrl-A, then lowercase x.

Manual Launch (alternative)

If you prefer not to run a remote script, do it by hand:

# 1. Install QEMU
sudo apt-get install -y qemu-system-arm qemu-utils

# 2. Download the images, unpack them, and check them
curl -fsSLO https://tail-os.com/downloads/tail_qemu.rfs.gz
curl -fsSLO https://tail-os.com/downloads/tail_disk.img.gz
curl -fsSLO https://tail-os.com/downloads/SHA256SUMS
gunzip tail_qemu.rfs.gz tail_disk.img.gz
sha256sum -c --ignore-missing SHA256SUMS

# 3. Boot
qemu-system-aarch64 \
    -M raspi3b \
    -kernel tail_qemu.rfs \
    -serial mon:stdio \
    -display none \
    -drive file=tail_disk.img,format=raw,if=sd \
    -netdev user,id=tailnet0 \
    -device usb-net,netdev=tailnet0

What's Inside the Images

tail_qemu.rfs is a custom ELF container that bundles the entire OS, 16 components:

tail_qemu.rfs
├── aarch64-startup                Early boot (EL3 -> EL1, MMU, jump to kernel)
├── kernel                         Microkernel (process, thread, IPC, scheduler)
├── uart_pl011                     UART + TTY server (user-space)
├── gpio_rpi3                      GPIO controller driver
├── sd_sdhost_rpi3                 SD card block driver (user-space)
├── fat_file_system_server         Mounts tail_disk.img as "/"
├── read_only_file_system_server   Serves these files at /rfs
├── log_server                     System log
├── logview                        Copies logged errors to the console
├── pipe_server                    Pipes between processes
├── dwc2_rpi3                      USB host controller driver
├── network                        Network stack
├── netdev_lo_module               Loopback network device
├── route                          Sets the default route at boot
├── debug_server                   GDB remote protocol stub (packed, not started)
└── tsh                            Shell

ls /rfs lists them, and the shell itself is /rfs/tsh. /rfs does not appear in ls /, because ls / lists the root directory of the FAT disk, and /rfs is a separate file system mounted beside it rather than a directory on that disk. Paths under it work: which tsh prints /rfs/tsh.

tail_disk.img is exposed to the guest as an SD card. It has two FAT32 partitions, laid out like the Raspberry Pi 3 image: the first, 64 MiB, is the boot partition a real Pi reads its firmware from, and it is empty here, because QEMU loads tail_qemu.rfs directly. The second is mounted as / and holds:

tail_disk.img
├── /etc/hosts, /etc/resolv.conf     Name lookup
├── /hello.py                        Example Python script
├── /usr/bin/                        27 programs: ls, ps, top, vi, python, ping, ... (*)
└── /usr/lib/python3.14/,
    /usr/lib/python314.zip           Python 3.14 standard library

(*) Plus list-root-long, a copy of ls under a long file name that shows the FAT server's long-name support, so ls /usr/bin lists 28 names.

Boot Sequence

qemu-system-aarch64 -M raspi3b
  |
  v
QEMU loads tail_qemu.rfs at 0x80000 (raspi3b entry point)
  |
  v
TAIL OS startup (aarch64-startup)
  |-- Switch EL3 -> EL1
  |-- Initialize MMU, caches
  |-- Jump to kernel
  v
TAIL OS kernel
  |-- Initialize process/thread/IPC/memory subsystems
  |-- Load user-space servers from RFS
  |-- Mount tail_disk.img as "/" via SD + FAT
  |-- Start tsh shell
  v
/$ _

Troubleshooting

qemu-system-aarch64: command not found: Install QEMU with sudo apt-get install -y qemu-system-arm qemu-utils.

No output at all in the QEMU window: The launcher passes -serial mon:stdio for you. If you're running QEMU by hand, make sure that flag is present — otherwise UART output goes nowhere visible.

Want to force a fresh download: Clear the cache: rm -rf ~/.cache/tailos and re-run the launcher.

QEMU boots but hangs before /$: The FAT filesystem takes several seconds to initialize on first boot; give it up to a minute. If it never appears, re-download the images (the cached copy may be corrupted) with rm -rf ~/.cache/tailos.

Exiting: Inside QEMU, press Ctrl-A, then lowercase x to quit. Ctrl-A, then lowercase c drops into the QEMU monitor (type quit). Uppercase X and C do nothing.

See Also

Generated from doc/install_qemu.md in the TAIL OS repository.