How Not To Deploy Using LXC

I seem to have a pathological need to do things the hard way. This is a braindump of things I’ve learned and things I’m still confused about for working with LXC.

I would like to host several services on a single server, and I decided to use linux containers (LXC) instead of something more mainstream like docker. This decision may have been inspired by NixNet’s decision to use LXC. To make things even more interesting, I wanted to use a Hetzner Volume, and encrypt it with LUKS following Will Webberly’s guide to volume encryption.

First off, there are several things with similar names:

  • LXC is the overall container ecosystem.
  • lxc is a command used to manipulate LXC containers, images, etc.
  • LXD is a daemon that wraps LXC in a powerful and convenient REST API
  • lxd is a command used to work with LXD

I am still a bit fuzzy on exactly which command should be used in any given situation. As a result I may be trying to mix lxc and lxd in strange ways which probably doesn’t help.

Volume encryption

This process went just fine. I was a bit skeptical about whether it would be possible to apply encryption encryption on top of a Hetzner volume since it’s probably some kind of networked storage rather than a regular drive. I just followed Will Webberly’s tutorial and everything worked just fine.

I did get a bit confused about the mapped device name. After running cryptsetup on /dev/sdX, the named encrypted volume showed up in lsblk under /dev/sdX/<name>. That name doesn’t work for formatting and mounting though, you have to use /dev/mapper/<name> instead.

Storage Pool Configuration

Running lxd init as root allows interactive setup of various options, including a default storage pool. I went through this carefully, mostly selecting default options except for configuring the encrypted volume as a default storage pool.

At the end, there is an option to output the YAML preseed configuration. I expected this command to also apply the configuration, but it doesn’t seem to. Later, when trying to create a container, lxc seemed to have no idea what default storage pool I was talking about, and tried to create a btrfs subvolume on a non-btrfs filesystem, which went about as well as you’d expect. I thought this was due to the configuration not being applied, but after applying it by piping it into lxd init --preseed, the problems with the storage pool continued.

By the way you can’t give lxd init --preseed a file, the yaml has to be on stdin.

Reformatting Storage Pool Volume as BTRFS

I had originally formatted the storage volume as ext4, but using BTRFS for the LXD storage pool has several advantages. It makes snapshots and backups fast and easy, and each container will be assigned its own btrfs subvolume which I think will facilitate enforcement of storage quotas.

When creating a subvolume through LXD, it runs mkfs.btrfs for you. This failed because the volume already contained an ext4 filesystem. You can pass --force to mkfs.btrfs to tell it to overwrite the existing filesystem anyway. I did this outside of LXD to reformat the volume as btrfs, but LXD still wants to do its own reformatting, and I don’t think passing --force is possible from outside LXD.

Therefore I used wipefs to remove the existing filesystem before re-running the LXD storage volume creation. Except that didn’t work because wipefs seems to not remove btrfs filesystems by default. I had to use wipefs --all. Probably wipefs --all --types btrfs would have been safer.

Networking Configuration

I configured the network during lxd init and lxd init --preseed, but I’m not convinced it was actually setup because running lxc profile show default doesn’t mention anything about subnets.

Although lxc network list includes lxdbr0, and lxc network show lxdbr0 lists subnets, so maybe it is configured.

Permission Confusion

There is apparently a way to create unprivileged containers as a (non-root) user, but pretty much whenever I run lxc as non-root, I get permission denied on /var/lib/lxd/unix.socket.

That’s where the LXD daemon listens. You can add a user to the lxd group to grant them access to that socket, but doing so is equivalent to making that user root according to the Security section of the LXD readme. The whole point of creating containers as a non-root user is that they’re not root!

I have tried the following all as a non-root user:

  • lxc-create --name=alpine-test --bdev=btrfs --template=download -- --dist=alpine --release=3.17
  • lxc-create --name=alpine-test --bdev=btrfs --template=download
  • systemd-run --unit=alpine-test --user --scope --property="Delegate=yes" -- lxc-create --template=download --bdev=btrfs --name=alpine-test
  • lxc launch alpine/3.17 alpine-test –storage default

They all either try to create a btrfs subvolume in the wrong place or try to connect to the LXD socket and are denied.