Using systemd-nspawn to run an older Ubuntu version and Docker images

Work for this post was partly triggered by something I saw at work and partly because of a lightning talk I saw on Monday. There was a remark by a friend that even though we love and want to run latest and greatest supported versions, we cannot always upgrade in time. And as such, sometimes we end-up with unsupported OS versions for a significant time. Not unlike the law of welded systems.

But it got me thinking. Assuming, for example, that you have a number of Xenial systems that due to library dependencies you cannot upgrade, is there a middle ground that might be acceptable for a while until you push forward? Using a Xenial docker container and treating it as a lightweight VM somehow, could be a solution. But what if you need to run docker containers too? Maybe you need something more elaborate.

I fired up a VM running Ubuntu Focal and decided to figure out how to run a systemd-nspawn Xenial container.

Create the machine:

# debootstrap --arch=amd64 xenial /var/lib/machines/xenial1

Start the machine

# systemd-nspawn -D /var/lib/machines/xenial1
root@xenial1 # rm /etc/securetty
root@xenial1 # passwd root
root@xenial1 # apt-get update
root@xenial1 # apt-get install dbus resolvconf
root@xenial1 # systemctl enable systemd-resolved
root@xenial1 # cat > /etc/resolvconf/resolv.conf.d/base
nameserver 127.0.0.53
options edns0 trust-ad
search home
ctrl-D
root@xenial1 #

Yes, I know that it is best to fix securetty instead of removing it, but this is a PoC on my VM. You can now proceed to configure systemd to run the container:

# /etc/systemd/system/xenial1.service

[Unit]
Description=Xenial1 Container

[Service]
LimitNOFILE=100000
ExecStart=/usr/bin/systemd-nspawn --machine=xenial1 --directory=/var/lib/machines/xenial1/ --bind /var/run/docker.sock:/var/run/docker.sock --bind /mnt2:/mnt2 -b 
Restart=always

[Install]
Also=dbus.service

In your host system you can now:

root@focal # systemctl daemon-reload
root@focal # systemctl start xenial1

You can of course log into the machine with machinectl login xenial1. Notice above that the nspawn container mounts the docker socket, since we assume that you have docker installed in the Focal host and that you want to "run" docker containers from within the Xenial machine.

We only need to install the docker client in the Xenial container and as such:

root@xenial1 # cat > /etc/apt/sources.list.d/docker.list
deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu xenial stable
ctrl-D
root@xenial1 # apt-get update
root@xenial1 # apt-get install docker-ce-cli=5:18.09.7~3-0~ubuntu-xenial

Now you’re all set. The docker client inside Xenial, starts whatever container you want in Focal and does whatever you like.

Suppose that you want a non-root user like ubuntu in the Xenial machine to be able to run docker commands; what do you do? You add the /etc/group line for the docker group from Focal in Xenial and in Xenial you simply useradd -aG docker ubuntu.

ubuntu@xenial1 > docker run -d -p 8080:8080 bitnami/nginx
:
ubuntu@focal > curl http://127.0.0.1:8080/
:
If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.
:

Enjoy.