Creating a full-featured container for working on MAAS 2.3


Because the development environment for MAAS versions 2.0 through 2.3 was targeted for Ubuntu 16.04 “Xenial”, bugs can and will occur if you try to develop code for MAAS 2.3 on more recent releases of Ubuntu. But developing against older versions of Ubuntu doesn’t have to be cumbersome; LXD offers a great way to create a lightweight, full-featured development environment.

In this post, I’ll show how to launch a Xenial-based container that is capable of running a full-fledged IDE for development purposes. Of course, if you’re just using a generic text editor, you may not need to do this. But I like to use pycharm, so I want my IDE to be able to access all aspects of the development environment, including Python files installed onto system paths. These won’t line up properly if I use the IDE outside of the container.

Scripting the creation of your container

Below you can find the script I use to create a Xenial-based development environment that is able to communicate with the X server running on my host (Bionic) system. (It also installs the pycharm-community snap; you can install pycharm-professional instead if you have a license.) It also imports your SSH keys from launchpad (modify the LAUNCHPAD_USER variable to customize this), and maps your $HOME into the container so that your work environment transitions seamlessly into the container.



lxc init ubuntu:xenial $CONTAINER -s default --no-profiles
lxc network attach virbr0 $CONTAINER eth0 eth0

# An idmap is required in order to allow for a read/write container $HOME,
# and to run X11 apps that will display on the host.
lxc config set $CONTAINER raw.idmap "both $UID 1000"

# Remap home directory.
lxc config device add $CONTAINER home disk source=$HOME path=/home/$USER

# Allow use of DISPLAY=:1 (gdb typically runs on :0; adjust as needed)
lxc config device add $CONTAINER X1 disk path=/tmp/.X11-unix/X1 source=/tmp/.X11-unix/X1

# Allow GPU passthrough for X applications
lxc config device add $CONTAINER gpu gpu
lxc config device set $CONTAINER gpu uid 1000
lxc config device set $CONTAINER gpu gid 1000

lxc config set $CONTAINER user.user-data "#cloud-config
  - name: $USER
    shell: /bin/bash
    ssh_import_id: $LAUNCHPAD_USER
package_upgrade: true
  - jq
  - build-essential
  - silversearcher-ag
  - x11-apps
  - default-jre
  - fonts-dejavu-core
  - fonts-freefont-ttf
  - ttf-ubuntu-font-family
    00: ['install', 'pycharm-community', '--classic']
locale: en_US.UTF-8
timezone: $(timedatectl | grep 'Time zone:' | awk '{print $3}')
  - [touch, /tmp/startup-complete]
lxc config set $CONTAINER "version: 2
      name: eth0
    dhcp4: true
lxc config device add $CONTAINER kvm unix-char path=/dev/kvm
lxc start $CONTAINER
lxc exec $CONTAINER -- /bin/bash -c 'while ! [ -f /tmp/startup-complete ]; do sleep 0.5; done'

Note here that I attached eth0 in the container to the virbr0 bridge, which I recommended using by default with LXD in a previous post.

When creating this container, it was very useful to use a cloud-init configuration to install the packages I’ll need for development.

The custom network-config isn’t really needed here, but I included it in case I wanted to expand on it later, such as by passing through more networks to use for testing.

Additional considerations

If you’re using a local Ubuntu mirror, you might find it useful to include the following in the cloud-config, as a workaround for bug #1791185:

  - [ 'sh', '-c', 'rm -rf /var/lib/apt/lists/*' ]
    - arches: [default]
      uri: http://<your-local-ubuntu-mirror>/ubuntu/

Finally, I added the following hack to my .profile, which makes it so I don’t have to export DISPLAY=:1 every time I log into the container:

if [ "$DISPLAY" = "" ]; then
    if [ -e $X11_SOCk ]; then
        owner=$(ls -l $X11_SOCK | awk '{ print $3 }')
        if [ "$owner" = "$USER" ]; then
            export DISPLAY=:1


Thanks to the following posts, which I referenced while creating this script: