Setting up a development environment and contribution workflow

Here are some notes that I’ve been collecting as my experience has grown since joining the Juju core team 2 months ago.


The first thing that I did when starting was play around with Juju as a user:

snap install --classic juju 

This gave me some exposure to Juju’s terminology and concepts. It’s also been useful also in case I need to check to something works with a “known good” version while I write code and break things.

Creating a Development Environment

My personal setup (Canonical employees work remotely from home) is fairly straightforward:

  • laptop (Dell XPS 15)
  • VS Code
  • Go 1.11
  • GNU make 4.2

I found the Juju README quite helpful to get started. Luckily, Juju builds very easily via:

$ go get -d -v
$ make dep
$ go install -v

As it happens, this can be simplified further by simply running:

$ make install

You should now have a working Juju binary at $GOPATH/bin/juju.


My general pattern for fixing bugs:

  • Branch from the develop branch of the Juju git repository
  • Make changes to the code
  • Run a few tests locally:
    • make pre-check
    • go test ./path/to/folder/...
  • Run all unit and integration tests via Canonical’s build infrastructure for Juju. This is activated by pushing to my local branch to Github, then creating a pull request.

When developing a new feature, I often need to experiment by live testing on a LXD cluster locally:

  • Bootstrap locally with LXD: juju bootstrap localhost testing
  • Deploy a minimal workload: juju deploy wiki-simple
  • Make changes to the code (often including copious logging messages!)
  • Follow the unit testing steps above
  • Run the following commands:
    • Create a new Juju binary: make install
    • Upload the new binary and upgrade the current system on the fly: juju upgrade-model --build-agent
  • Lower Juju’s logging levels: juju model-config logging-config="juju.apiserver=TRACE;juju.api=TRACE;<root>=DEBUG"
  • Evaluate the system by running juju commands with the --show-log --logging-config="<root>=TRACE" arguments, e.g. juju --show-log --logging-config="<root>=TRACE" status

Extra tips

Feature Flags

Developing behind feature flags involves defining the flag, using it within the code base, then enabling it within the running juju/jujud binaries.

Speeding up local development

The juju bootstrap command supports several configuration parameters, many of which are tailored for production environments. For local development, modifying the defaults can speed things up.

From the wiki page “Faster LXD”:

Turn off automatic package upgrades

By default Juju will run apt-get upgrade on machines it creates. While this is useful in production scenarios, it isn’t usually necessary for test deployments. Turning it off can greatly speed up deployments.

The relevant Juju configuration for this is:

enable-os-upgrade: false

Note that there is also a enable-os-refresh-update option which you might be tempted to disable but I’ve found this normally causes more problems than it’s worth (charms fail to install).

To make use of these configuration options, add them to juju bootstrap and juju add-model commands as command-line arguments:

juju bootstrap --config enable-os-upgrade=false

The whole invocation can become quite convoluted with the addition of feature flags:

JUJU_DEV_FEATURE_FLAGS=mongodb-snap juju --debug bootstrap --config enable-os-upgrade=false localhost c-snap

To mitigate some of this verbosity, it’s possible to add these configuration settings to your own cloud definition. Again, from the wiki page “Faster LXD”:

Suggested Juju config for LXD deployments

Here’s a complete suggested configuration for test LXD deployments that takes into account the ideas discussed in this article (as well as turning on DEBUG logging):

logging-config: "<root>=DEBUG"
enable-os-refresh-update: true
enable-os-upgrade: false
default-series: xenial

You can keep this in a file and pass it as the --config option to juju bootstrap and juju add-model but a more > convenient approach is to create a custom LXD cloud with these options set. A section like this in
~/.local/share/juju/clouds.yaml will define a LXD cloud called “dev”:

   type: lxd
     default-series: xenial
     enable-os-refresh-update: true
     enable-os-upgrade: false
     logging-config: <root>=DEBUG

This can then be used like this:

juju bootstrap foo dev

Further Resources


Nice post, thanks for sharing and helping new folks with some of those common getting started issues. It’s easy to take such knowledge for granted if you’ve been around while.

There’s one glaring inaccuracy though - I’m sure you meant to type Goland instead of VS Code :stuck_out_tongue:


There is one important difference if you want make changes for juju on Kubernetes, instead of

make install


make microk8s-operator-update

which will run make install by default and create a juju operator for you which is placed in docker. This does require docker to be installed.

sudo snap install docker

A handy guide for your next steps: Using juju with microk8s