Skip to content

Convert a Raspberry Pi OS Image into a Docker Container

This guide converts the root filesystem from a Raspberry Pi OS Lite disk image into a Docker image. It works on an ARM64 Raspberry Pi and on an x86-64 Debian or Ubuntu workstation with QEMU emulation.

A container is not a virtual Raspberry Pi

Containers share the host kernel. Firmware, GPIO, config.txt, kernel modules, systemd boot, and Raspberry Pi-specific hardware are not reproduced by this method. Use it to inspect packages, test shell scripts, or prepare an ARM userspace—not to test boot or hardware access.

Choose the Correct Architecture

Download the current Raspberry Pi OS Lite image from the official downloads directory. Choose one architecture deliberately:

Raspberry Pi OS image Debian architecture Docker platform Typical target
64-bit Lite arm64 linux/arm64 Pi 3, 4, 5, Zero 2 W
32-bit Lite armhf linux/arm/v7 Older Pi models and compatibility testing

The commands below use a downloaded .img.xz file without depending on a release-specific URL:

1
2
3
4
5
6
7
8
mkdir -p ~/raspios-container
cd ~/raspios-container
cp ~/Downloads/*raspios*.img.xz .

image_archive=$(find . -maxdepth 1 -name '*.img.xz' -print -quit)
xz -dk "$image_archive"
image_file=${image_archive%.xz}
ls -lh "$image_file"

Use xz, not unzip: Raspberry Pi OS images are distributed as XZ-compressed files.

Inspect and Mount the Root Partition

Display the partition table first:

sudo fdisk --list "$image_file"

Partition 1 is normally the FAT boot partition, and partition 2 is the Linux root filesystem. Attach the image as a loop device and let Linux create partition devices automatically:

1
2
3
4
5
6
loop_device=$(sudo losetup --find --show --partscan "$image_file")
echo "$loop_device"
lsblk -f "$loop_device"

mkdir -p rootfs
sudo mount "${loop_device}p2" rootfs

Check the userspace architecture before importing it:

file rootfs/bin/bash
cat rootfs/etc/os-release

Export the Filesystem Correctly

Preserve numeric user IDs, extended attributes, ACLs, device nodes, and symbolic links. These details are easily lost by a normal desktop archive operation.

1
2
3
4
5
6
sudo tar \
  --numeric-owner \
  --xattrs \
  --acls \
  -C rootfs \
  -cpf raspios-rootfs.tar .

Unmount and detach the loop device even if you only intend to keep the tar archive:

sudo umount rootfs
sudo losetup --detach "$loop_device"

Import the root filesystem and set a useful default command:

1
2
3
4
5
6
7
docker import \
  --change 'CMD ["/bin/bash"]' \
  raspios-rootfs.tar \
  local/raspios-rootfs:latest

docker image inspect local/raspios-rootfs:latest \
  --format '{{.Architecture}} {{.Os}} {{.Size}}'

Run on a Raspberry Pi

On a host with the same architecture, no emulation is required:

1
2
3
docker run --rm -it local/raspios-rootfs:latest
cat /etc/os-release
dpkg --print-architecture

Use a read-only container for inspection when possible:

1
2
3
4
5
docker run --rm -it \
  --read-only \
  --tmpfs /tmp \
  --tmpfs /run \
  local/raspios-rootfs:latest

Run ARM Images on an x86-64 Host

Docker Desktop normally provides emulation automatically. On standalone Docker Engine, register QEMU with binfmt_misc:

docker run --privileged --rm tonistiigi/binfmt --install arm64,arm
docker run --privileged --rm tonistiigi/binfmt

Then specify the image platform explicitly:

docker run --rm -it --platform linux/arm64 local/raspios-rootfs:latest

For a 32-bit image, use --platform linux/arm/v7. QEMU is convenient but significantly slower for compilation and compression. Native ARM hardware or a multi-node Buildx builder is better for performance-sensitive work. See Docker's multi-platform build guide.

Reproducible Checks

Record these values whenever you compare images or publish results:

1
2
3
4
5
6
7
8
docker run --rm --platform linux/arm64 local/raspios-rootfs:latest \
  sh -c 'dpkg --print-architecture; cat /etc/debian_version; getconf LONG_BIT'

/usr/bin/time -f 'elapsed=%e max_rss_kb=%M' \
  docker run --rm --platform linux/arm64 local/raspios-rootfs:latest true

docker image inspect local/raspios-rootfs:latest \
  --format 'created={{.Created}} size={{.Size}} architecture={{.Architecture}}'

Do not label QEMU timings as Raspberry Pi performance. State the host CPU, Docker version, QEMU/binfmt version, source image release, and architecture alongside the result.

Common Failures

exec format error

The container architecture differs from the host and QEMU is not registered. Check docker image inspect, register binfmt, and pass the correct --platform.

${loop_device}p2 does not exist

Confirm that losetup was called with --partscan. Run sudo partprobe "$loop_device" and inspect the result with lsblk. Some unusual images may use a different partition number.

systemctl reports that systemd is not running

This is expected. The imported image has no normal Raspberry Pi boot process. Run the required program directly, or create a purpose-built Dockerfile instead of trying to boot the disk image inside a container.

DNS or package installation fails

First verify the container clock and Docker DNS configuration. Archived Raspberry Pi OS releases may also reference repositories that have moved. Prefer a current Trixie or Bookworm image.

FAQ

Should this image be used as a production base image?

Usually no. A small Debian base image with an explicit Dockerfile is easier to reproduce, update, scan, and maintain. Importing Raspberry Pi OS is useful when you specifically need its package selection or userspace layout.

Can the container access GPIO or the camera?

The imported filesystem alone cannot emulate Raspberry Pi hardware. Device passthrough is possible only when the container runs on a real Raspberry Pi and is granted the appropriate /dev devices and permissions.

Why does uname not prove which Raspberry Pi OS kernel is installed?

Because the container uses the host kernel. Use dpkg --print-architecture, /etc/os-release, and package metadata to identify the imported userspace.