Lxdev: disposable dev envs for Linux with X11/Audio (github.com)

Hi All,

Just noticed this post on HN by Brian Ketelsen @ Microsoft,
Show HN: lxdev: disposable dev envs for Linux with X11/Audio (github.com)
Discussion on HN: https://news.ycombinator.com/item?id=18963772

He introduces a tool called lxdev that can be used to launch LXD containers with specific characteristics.
For example, there is a gui LXD profile that is used to setup a new LXD container that can run GUI programs. Here is a list of the currently provided LXD profiles, https://github.com/bketelsen/lxdev/tree/master/templates

It would be possible to get the same functionality with LXD commands, like

lxc launch ubuntu:18.04 mycontainer --profile default --profile gui

With lxdev, you would write instead the following (takes as default the default,gui profiles),

lxdev create mycontainer

I think there is a good need to create preconfigured LXD containers, similar to using Dockerfiles to create custom Docker containers. lxdev works on this path.


You posted my post before I could post my post!
Thanks for noticing. I’d particularly appreciate any suggestions on how to use lxd features more efficiently.

After reading all the docs, and perusing as many examples as I can, I’m starting to feel like profiles are best suited for devices and general container configuration, and I’m doing it wrong trying to merge multiple profiles into one.

Would it be more idiomatic LXD usage to have more base containers? Say one for each programming language, and one for general X11 apps?

You would use multiple profiles if you want to keep some common default settings in the default profile (networking, storage, etc), and then add specialization with the subsequent profiles. That’s the way to go if you do not have access to a tool like lxdev.

You mention in the first Issue on Github that when you stack profiles together, LXD does not merge together the configuration for the same key but rather uses the configuration from the last profile. Are you referring specifically to cloud-init configuration here?

I think it’s good to have individual profiles for specific functionality, and have the option to select which ones should be used for the new container. In terms of usability, it would be good at some point in the future to be able to specify programming (an alias) and have gui,go,vscode enabled in the new container.

There is a configuration phase in lxdev and during that phase it is possible to put all sorts of customization.
You mention in the video that you would need to edit the network interface name for the macvlan.
The network interface could be asked during the configuration, or deduced from the default route.

In addition, the Unix socket for X11 is either X0 or X1 depending on whether you have just a single GPU or two (and you use the second GPU). This also can be asked or deduced during the configuration phase.

A final issue is that when you launch a gui container, it takes time to install the necessary packages. Also, at the very end it adds the PULSE_SERVER into the ~/.profile. Therefore, if you get a shell in the container before the PULSE_SERVER is set, there is no audio. I would suggest here to have a mode that lxdev would wait for cloud-init to complete before returning the shell prompt.

Thanks for writing lxdev. It will make the onboarding process for LXD better.
Tell me your thoughts about the above and I’ll post issues on github in the afternoon.


Thank you for this thoughtful response, I really appreciate you taking the time to look through it.
I’ve refactored quite a bit of the process so that the “extra profiles” are now bash scripts that are copied in and executed after the container is created. It feels a lot better.

I’m going to look at the audio settings, and try to determine X & network settings at runtime. I think there a few other usability improvements I can make too.

Generally I’m quite enjoying the workflow. Even with cloning my dotfiles and setting up my whole typical linux dev environment it only takes a minute or two to have a fully functioning container.

Thanks again!

1 Like

I just released v0.0.1 of lxdev which is a pretty big refactor. Created much more documentation too.

Question: How can I tell if cloud-init is done in the container? Is it something simple like looking for a file existing or not? Or watching a process?


The cloud-init command in the container has a parameter status. It shows the status, either running or done. Therefore, one option is to poll for the output of that command.

ubuntu@cloudinit:~$ cloud-init status
status: running
ubuntu@cloudinit:~$ cloud-init status
status: done

An alternative is to run the command with the --wait parameter, as shown at
This will make the command to wait until cloud-init completes.

A third option is to manually check the file /var/lib/cloud/instance/boot-finished. When cloud-init completes, that file is created (I think cloud-init doestouch /var/lib/cloud/instance/boot-finished`).

I spend some time on compiling lxdev in a LXD 18.04 container.
Here are my commands to prepare the container in a minimal way.
I am trying here with go get -d to clone the repository. When I compiled lxdev two days ago, it was complaining that the source was not under ~/go/src/. Perhaps this refactor does not have the requirement anymore.

$ lxc launch ubuntu:18.04 lxdev -c security.nesting=true
$ lxc exec lxdev -- sudo --user ubuntu --login
ubuntu@lxdev:~$ sudo apt update 
ubuntu@lxdev:~$ sudo apt install -y make golang-go
ubuntu@lxdev:~$ echo 'PATH=$HOME/go/bin:$PATH' >> .profile
ubuntu@lxdev:~$ source .profile 
ubuntu@lxdev:~$ go get -d -v github.com/bketelsen/lxdev
ubuntu@lxdev:~$ cd ~/go/src/github.com/bketelsen/lxdev/

I get this error,

ubuntu@lxdev:~/go/src/github.com/bketelsen/lxdev$ make deps
# cd .; git clone https://github.com/bketelsen/libgo /home/ubuntu/go/src/github.com/bketelsen/libgo
Cloning into '/home/ubuntu/go/src/github.com/bketelsen/libgo'...
fatal: could not read Username for 'https://github.com': terminal prompts disabled
package github.com/bketelsen/libgo/events: exit status 128
Makefile:21: recipe for target 'deps' failed
make: *** [deps] Error 1

This part, git clone https://github.com/bketelsen/libgo should be git clone github.com/bketelsen/libgo. I couldn’t find where it is set.

Another issue is that there are two packagings of LXD, deb package and snap package.
The deb package has the Unix socket at /var/lib/lxd/unix.socket and the snap package at /var/snap/lxd/common/lxd/unix.socket. The cloud images of Ubuntu up to 18.04 have the deb version LXD, like in the LXD container lxdev that was created earlier. How can you distinguish whether the DEB package or the snap package are in use? Well, it is possible to have both installed and at the same time use the DEB package (the commands take precedence in the $PATH). I believe that for now, a simple check with which lxc should suffice. It’s /usr/bin/lxc for the deb package, and /snap/bin/lxc for the snap package.

1 Like

libgo wasn’t published, that’s my mistake. I made it public so the go build should work now.