Skip to content

Instantly share code, notes, and snippets.

@eth-p
Last active March 27, 2022 18:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eth-p/c7af00010b86f271f940e4b0ccdb5aeb to your computer and use it in GitHub Desktop.
Save eth-p/c7af00010b86f271f940e4b0ccdb5aeb to your computer and use it in GitHub Desktop.
Linux Desktop in Docker

Linux Desktop in Docker

This is my experiment to run Arch in a Docker container on my UnRAID server.

Last Updated: 2022-03-27

Why?

  • VMs are a waste of memory.
  • VMs are a waste of processor usage.
  • VMs have terrible I/O speed.
  • Because I can.

Step 1: Bootstrapping

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

Step 2: Shell into the bootstrapped distro.

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 (--read-only flag) (systemd doesn't like this)
  • Priviledged (--privileged flag)
  • Bridge networking (--network br0 flag for UnRAID)
  • An image with mount --bind and chroot (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.

Step 3: Shell into the bootstrapped distro, automatically.

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

Step 4: Time for an init system!

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

Step 5: SSH fun.

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment