This is my experiment to run Arch in a Docker container on my UnRAID server.
Last Updated: 2022-03-27
- VMs are a waste of memory.
- VMs are a waste of processor usage.
- VMs have terrible I/O speed.
- Because I can.
This guide assumes my setup, where /mnt/disks/vm
is where the VM will be located.
To start out, we need to bootstrap an Arch distro onto the host. So, let's do that!
$ mkdir /mnt/disks/vm/Arch
$ docker run --rm -it --mount type=bind,source=/mnt/disks/vm/Arch,target=/BOOTSTRAP archlinux bash
# Now running inside Docker.
$$ ls | grep -v 'dev\|proc\|sys\|tmp\|BOOTSTRAP' | xargs -i{} cp -p -R {} BOOTSTRAP/
$$ exit
With all of the image copied to the host filesystem, it should be possible to create a new container to run the image for daily usage. We need:
- The bootstrapped distro to
chroot
into (--mount type=bind,source=...,target=...
) Immutable root filesystem ((systemd doesn't like this)--read-only
flag)- Priviledged (
--privileged
flag) - Bridge networking (
--network br0
flag for UnRAID) - An image with
mount --bind
andchroot
(e.g.alpine
)
docker run --rm -it \
--privileged --network br0 \
--mount type=bind,source=/mnt/disks/vm/Arch,target=/mnt/live \
alpine
Great, we're in! For a working distro, we need to bind mount /dev
, /proc
, /sys
, and /tmp
:
mount --bind /dev /mnt/live/dev
mount --bind /proc /mnt/live/proc
mount --bind /sys /mnt/live/sys
mount --bind /tmp /mnt/live/tmp
And let's finish it off with a chroot:
$ chroot /mnt/live
$ pacman --version
.--. Pacman v6.0.1 - libalpm v13.0.1
/ _.-' .-. .-. .-. Copyright (C) 2006-2021 Pacman Development Team
\ '-. '-' '-' '-' Copyright (C) 2002-2006 Judd Vinet
'--'
This program may be freely redistributed under
the terms of the GNU General Public License.
If that all worked, we can create a Docker container to do all that for us.
Start by making a new directory:
$ mkdir /tmp/dockerdistro
$ cd /tmp/dockerdistro
And add the following file as init-distro.sh
:
#!/bin/sh
set -xe
LIVE=/mnt/live
INIT=/bin/sh
# Remount with suid enabled.
# Without this, sudo and password checking won't work.
mount -n -o remount,suid,atime,diratime "$LIVE"
# Bind important directories to the chroot home.
test -d "$LIVE/dev" || mkdir "$LIVE/dev"
test -d "$LIVE/proc" || mkdir "$LIVE/proc"
test -d "$LIVE/sys" || mkdir "$LIVE/sys"
test -d "$LIVE/tmp" || mkdir "$LIVE/tmp"
mount --bind /dev "$LIVE/dev"
mount --bind /proc "$LIVE/proc"
mount --bind /sys "$LIVE/sys"
mount --bind /tmp "$LIVE/tmp"
# Check if systemd is installed.
if test -x "$LIVE/usr/sbin/init" && test "$1" != "--shell"; then
INIT="/usr/sbin/init --system"
fi
# Chroot (technically, namespace).
test -d "$LIVE/boot/docker" || mkdir "$LIVE/boot/docker"
pivot_root "$LIVE" "$LIVE/boot/docker"
exec $INIT
Now create a Dockerfile for it:
FROM alpine:latest
COPY init-distro.sh /init
RUN chmod 700 /init
ENTRYPOINT ["/init"]
$ docker build -t hacky-arch .
Sending build context to Docker daemon 3.072kB
Step 1/4 : FROM alpine:latest
---> 9c842ac49a39
Step 2/4 : COPY init-distro.sh /init
---> 9281873f2dc4
Step 3/4 : RUN chmod 700 /init
---> Running in 5c6f2303415e
Removing intermediate container 5c6f2303415e
---> 0d90a3d6f24d
Step 4/4 : CMD /init
---> Running in 2a159e46e89f
Removing intermediate container 2a159e46e89f
---> 1da1dd1c0840
Successfully built 1da1dd1c0840
Successfully tagged hacky-arch:latest
And let's try it!
docker run --rm -it \
--privileged --network br0 \
--mount type=bind,source=/mnt/disks/vm/Arch,target=/mnt/live \
hacky-arch --shell
While running inside your hacky-arch container's chroot jail, install systemd
.
You can ignore all the warnings and errors for now, we'll fix that later.
$ pacman -Syu --noconfirm systemd
And to make sure we never lock ourselves out, let's install openssh:
$ pacman -S --noconfirm openssh
$ systemctl enable sshd.service
And a user to use over SSH...
$ useradd --uid 1000 --create-home --user-group --password temp me
Let's relaunch the container with systemd and the name hacky-arch
:
docker run -it --name hacky-arch \
--privileged --network br0 \
--mount type=bind,source=/mnt/disks/vm/Arch,target=/mnt/live \
hacky-arch
And find it's network address:
$ docker exec -it hacky-arch /bin/sh
$ ip addr