Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
Docker in LXD
exit #Not that kind of script, these sections should be run manually
# First get lxd working
# Install lxc/lxd. On ubuntu 18.04 I'm using snap to install lxd
sudo apt install snapd
sudo snap install lxd #
# Using ZFS for container storage makes it easy to move things around
sudo apt-get install zfsutils-linux
# Initialize lxd (will setup a new zfs pool too, or you can do that before running init)
sudo lxd init
Would you like to use LXD clustering? (yes/no) [default=no]: no
Do you want to configure a new storage pool? (yes/no) [default=yes]: yes
Name of the new storage pool [default=default]:
Name of the storage backend to use (btrfs, ceph, dir, lvm, zfs) [default=zfs]:
Create a new ZFS pool? (yes/no) [default=yes]:
Would you like to use an existing block device? (yes/no) [default=no]: no
Size in GB of the new loop device (1GB minimum) [default=40GB]: 200GB
Would you like to connect to a MAAS server? (yes/no) [default=no]: no
Would you like to create a new local network bridge? (yes/no) [default=yes]: yes
What should the new bridge be called? [default=lxdbr0]:
What IPv4 address should be used? (CIDR subnet notation, “auto” or “none”) [default=auto]:
Would you like LXD to NAT IPv4 traffic on your bridge? [default=yes]: yes
What IPv6 address should be used? (CIDR subnet notation, “auto” or “none”) [default=auto]: none
Would you like LXD to be available over the network? (yes/no) [default=no]: yes
Address to bind LXD to (not including port) [default=all]:
Port to bind LXD to [default=8443]:
Trust password for new clients:
Would you like stale cached images to be updated automatically? (yes/no) [default=yes]
Would you like a YAML "lxd init" preseed to be printed? (yes/no) [default=no]:
# Add user to lxd group for access without using sudo (similar to docker group)
sudo usermod -a -G lxd $USER
newgrp lxd # start a new shell session with the new group
# Create some test containers
lxc launch images:ubuntu/xenial/amd64 ubuntu-test
lxc launch images:ubuntu/bionic/amd64 ubuntu-test2
# view details of zfs pool
zpool list
default 186G 372K 186G - 0% 0% 1.00x ONLINE -
zpool status
pool: default
state: ONLINE
scan: none requested
default ONLINE 0 0 0
/var/snap/lxd/common/lxd/disks/default.img ONLINE 0 0 0
zfs list
default 366M 180G 24K none
default/containers 5.34M 180G 24K none
default/containers/ubuntu-test 2.58M 180G 168M /var/snap/lxd/common/lxd/storage-pools/default/containers/ubuntu-test
default/containers/ubuntu-test2 2.74M 180G 192M /var/snap/lxd/common/lxd/storage-pools/default/containers/ubuntu-test2
default/custom 24K 180G 24K none
default/custom-snapshots 24K 180G 24K none
default/deleted 24K 180G 24K none
default/images 360M 180G 24K none
default/images/6c08ba0fb413ef92d8ea7b6437ded184246644cd0820936cf8351ad6e93e0a13 168M 180G 168M none
default/images/98fa50a3b311163c682bc596ebb6bf054b22e7d480c85430a6db46205b2d2cdc 192M 180G 192M none
default/snapshots 24K 180G 24K none
# snap run lxd in a separate mount namespace so the above mountpoints don't work directly
# instead if you want to access the container rootfs directly (excluding any extra mounts like /tmp /var ...)
sudo ls /var/snap/lxd/common/mntns/var/snap/lxd/common/lxd/storage-pools/default/containers/ubuntu-test/rootfs/
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
# Fun but optional networking stuff:
# Use ip_forwarding on the host and a static route on your router for "host bridged" style networking
# once enabled you can disable nat: lxc network set lxdbr0 ipv4.nat false
# Change domain assigned to containers (can use local or router's dnsmasq to forward requests: server=/myhost.lxd/
# lxc network set lxdbr0 dns.domain myhost.lxd
# host ubuntu-test.myhost.lxd -> ubuntu-test.myhost.lxd has address
# If you want to do X11 or audio stuff, check out
# Next setup Docker in an LXD container. This makes it more portable (can move the container to another host) and more, uh, contained
# From
lxc launch images:ubuntu/bionic/amd64 $NAME -c security.nesting=true
lxc exec $NAME -- apt update # The above image should be kept up to date, but can force an update if needed
lxc exec $NAME -- apt dist-upgrade -y
# Add an ssh server to the container if you don't want to use exec commands/sessions
lxc exec $NAME -- bash -c 'apt-get install openssh-server -y;mkdir -m 0700 ~/.ssh'
lxc file push -p --uid=0 --gid=0 ~/.ssh/authorized_keys "$NAME/root/.ssh/authorized_keys"
# Next we'll run commands in the container. Could use lxc exec $NAME bash instead but I like treating these like servers
ssh root@docker.myhost.lxd
# change default username (it is benefitial to keep the same UID/GID you have on your host, which is 1000 in my case)
usermod -l $NEWUSER ubuntu
groupmod -n $NEWUSER ubuntu
mv /home/ubuntu /home/$NEWUSER
sed -i "s/home\/ubuntu/home\/$NEWUSER/" /etc/passwd
# Copy ssh settings from root to new user
cp -r ~/.ssh "/home/$NEWUSER/.ssh"
chown -R $NEWUSER:$NEWUSER "/home/$NEWUSER/.ssh"
# Then install docker. Lots of ways to do that but I like using their repo
sudo apt-get install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL | sudo apt-key add -
sudo add-apt-repository \
"deb [arch=amd64] \
$(lsb_release -cs) \
sudo apt-get update
sudo apt-get install docker-ce
# Run a test container
docker run -it --rm ubuntu:bionic bash -c 'echo hello from $(hostname)'
hello from d9179cea965a
# Great, now we have docker running in an lxd container, what good is that?
# Well, you can install all the client tools on the host and manage the docker instance "remotely"
# New versions at
cd ~/Installs/docker
wget`uname -m`/docker-$VERSION.tgz
sudo tar -zxvf docker-$VERSION.tgz --strip-components=1 --directory /usr/local/bin/ docker/docker
# Also docker-compose
wget`uname -s`-`uname -m` -O ./docker-compose
chmod +x ./docker-compose
sudo cp ./docker-compose /usr/local/bin/docker-compose
# Install docker machine (optional, but nice to have for connecting to multiple truely remote instances)
wget$(uname -s)-$(uname -m) -O ./docker-machine
chmod +x ./docker-machine
sudo cp ./docker-machine /usr/local/bin/docker-machine
# Setup remote docker daemon (may want to create a snapshot first )
docker-machine create --driver generic --generic-ip-address=docker.myhost.lxd --generic-ssh-key ~/.ssh/id_rsa --generic-ssh-user=root name_of_remote
# Note for lxd may need to change /etc/systemd/system/docker.service.d/10-machine.conf to use vfs instead of aufs
# It will also change the hostname, so change that back:
eval $(docker-machine env name_of_remote)
# But my preferred way is proxying the docker socket. Insecure option over TCP
# Or lxd has a built in proxy as well. Blog post for sharing lxc socket to a container, but same approach works for docker container to host
lxc config device add docker dockersocket proxy connect=unix:/var/run/docker.sock listen=unix:/tmp/docker.sock bind=host uid=0 gid=999 mode=0660 security.uid=65534 security.gid=999
export DOCKER_HOST=unix:///tmp/docker.sock
docker run -it --rm ubuntu bash
# Note, if you mounted it to /var/run/docker.sock on the host it would be picked up by default, but I think there were some permission issues.
# You can also use docker-compose on the host using local docker files
# But keep in mind the references in the docker files will be sourced on the docker container not your local host
# So to fix that you can mount your development folder as another device :-P
# Mount local host folder into docker container
lxc config device add docker scripts disk source=/home/$USER/code/scripts/ path=/home/$USER/scripts
lxc config device add docker dockerfiles disk source=/home/$USER/code/dockerfiles path=/home/$USER/dockerfiles
lxc config device add docker go disk source=/home/$USER/code/go path=/home/$USER/go
# Now those folders are mounted in the container, however they currently have a different UID:GID due to the container's user namespace
root@docker:/home/greg.bray# ls -hl
total 12K
drwxr-xr-x 4 nobody nogroup 4.0K May 13 01:31 dockerfiles
drwxr-xr-x 5 nobody nogroup 4.0K Apr 23 16:09 go
drwxr-xr-x 7 nobody nogroup 4.0K May 22 21:30 scripts
# To fix nobody:nobody you have to add a custom idmap
# This is where assuming the UID:GID in the container matches the host comes into play. Should be possible if they are different but much easier if they are the same
# You may need to sudo vim /etc/sub{u,g}id but on ubuntu 18.04 with latest lxd I was able to skip that step
lxc config set docker raw.idmap "both $UID $UID"
lxc restart docker #Should see a message about it remapping the users on the container filesystem
# Now the files mounted in the container have the correct owner:group
lxc exec docker -- bash -c "ls -hl /home/$USER"
total 12K
drwxr-xr-x 4 greg.bray greg.bray 4.0K May 13 01:31 dockerfiles
drwxr-xr-x 5 greg.bray greg.bray 4.0K Apr 23 16:09 go
drwxr-xr-x 7 greg.bray greg.bray 4.0K May 22 21:30 scripts
# And so you can reference them via dockerfiles or docker-compose or as mounted volumes into containers
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment