Getting Started with charm development

This guide is for anyone that wants to create the code that we call a charm; the part that does the work of installing and managing applications in a Juju model. Many charms exist in the Charm Store already, but if your favourite application isn’t covered or you would like to make your own spin on an existing charm, you will discover all the tools and information you need here.

For an example of a community-driven charm development project see the OpenStack Charm Guide.


  • A Juju controller: If you have not used Juju before, it’s a good idea to start here.
  • Python 3.x: it is possible to develop charms using other languages, but this guide focuses on Python-based development.
  • Charm Tools: Command line utilities to make it easy to create, build, fetch and test charms. See the Charm Tools page for more information.
  • Charm Helpers: A Python library that provides an extensive collection of functions for developers to reuse. Many common charm patterns are encapsulated in functions of this library. See Charm Helpers for details.
  • This guide also uses the Vanilla PHP Forum software as an example.

Designing your charm

To begin writing a charm, you should have a good plan of how it’s going to be implemented, what configuration options you wish to expose to anyone deploying the charm, and what dependent charms (if any) it will be related to. It’s a good idea to start with a diagram.

This visual representation of your charm deployment will help to solidify the configuration, deployment, and management of your application. Take the following example:

Looking at this diagram we see the Vanilla charm with two units. The Vanilla application requires a relationship to a database using the “mysql” interface. The MariaDB charm implements the mysql interface, which fulfills the db relation and is already in the Store:

Writing your charm

The fastest way to write a new charm is to build off of existing layers. This allows you to create code that is very focused on the application you are trying to implement.

Layers let you build on the work of other charmers, whether that work is in the form of other charms that you can extend and modify, interfaces that communicate with remote applications, or partial base layers that make managing dependencies much easier. And it does this in a consistent, repeatable, and incremental way.

The available layers and interfaces can be found at Juju Charm Layers Index. The basic layer provides charm helpers Python library and the reactive framework that makes layers possible.

Reactive and layered charms


Another software paradigm is reactive programming. Do something when the state or conditions indicate. Juju offers the charms.reactive package to allow charms to be written in the reactive paradigm. In charms.reactive code execution is controlled by boolean logic. You can define when the conditions are right, run this code, or when something is not set, run different code or do nothing at all.


The idea of charm layers is to combine objects or data into more complex objects or data. When applied to Charms, layers allow you to extend or build off other charms to make more complex or useful charms. The layer.yaml file in the root directory of the charm controls what layer(s) will be imported.

Creating a new layer

First off, you require a local charm repository in which to work. This involves creating three directories – layers, interfaces, and charms – and setting some environment variables.

The layers directory contains the source code of the layered charm covered in our examples. The interfaces directory is where you’d place any interface layers you may wish to write, and the charms directory holds the assembled, ready to deploy charm.

export CHARM_DIR=$HOME/charms
export LAYER_PATH=$CHARM_DIR/layers
export INTERFACE_PATH=$CHARM_DIR/interfaces


cd $CHARM_DIR/layers

Exporting the environment variables in this way only sets the variables for the current terminal. If you wish to make these changes persist, add the same export statements to a resource file that are evaluated when you create a new console such as ~/.bashrc depending on your shell.

Once in the layers directory clone the example charm layer - layer-vanilla:

git clone
cd layer-vanilla

If you’d like to write your own layer, or simply learn more about how layers are implemented, see How to Write a Layer.

Assemble the layers

Now that the layer is complete, let’s build it and deploy the final charm. From within the layer directory, this is as simple as:

charm build

The build will take all of the layers and interfaces included by your charm, either from your local LAYER_PATH and INTERFACE_PATH directories or automatically downloaded from the Juju Charm Layers Index and create a new charm in $CHARM_DIR/trusty/vanilla:

build: Composing into /home/user/charms
build: Processing layer: layer:basic
build: Processing layer: layer:apache-php
build: Processing layer: .

To inspect how the charm was assembled, there is a charm layers command that shows what file belongs to which layer. Change to the charm directory and view the layer map:

cd $CHARM_DIR/trusty/vanilla
charm layers

Then we can deploy mariadb and the new charm:

juju deploy mariadb
juju deploy $CHARM_DIR/trusty/vanilla --series trusty
juju add-relation mariadb vanilla
juju expose vanilla

Add GUI user notes

Optionally leave some notes for those users who will deploy the charm from the Juju GUI. This consists of including a Markdown-formatted file called at the root of the charm’s directory. Once the charm (or bundle) is deployed, the file will be rendered and displayed to the user.

The file should include the user’s next steps. Here is a guideline for what to include:

  • State prerequisites for various application features.
  • Include instructions for achieving a working application at a rudimentary level.
  • Provide links for further reading.

As for style, here are some pointers:

  • Keep in mind that the user is reading this information from the GUI, so write accordingly.
  • Do not over-complicate. This is a small beginners’ guide.
  • Use available Markdown formatting features such as section headers, lists, and code blocks. See this Markdown help.

Finally, here is an example of a file:

Testing your charm

Because Juju is a large complex system, not unlike a Linux software distribution, there is a need to test the charms themselves and how they interact with one another. All new charms require tests that verify that the application installs, configures, scales and relates as intended. The tests should be self-contained, installing all the required packages so the tests can be run automatically with a tool called bundletester. Similar to hooks the tests should be executable files in a tests/ directory of the charm. While you can write tests in Bash or other languages, it is recommended to use the Amulet library, which makes it easy to write charm tests in Python.

For more information about writing tests please refer to the charm testing guide documentation.

Next steps

Once you have finished testing your charm or bundle visit the Charm Store page and consider the following topics:

  • Pushing to the store
  • Releasing to channels
  • Publishing your charm
  • Sharing charms and bundles

Thanks for the fix @jlosito

1 Like

Hey ! juju i think based on, ubuntu core philosophy. Have a nice life.

Hey @timClicks . This doc looks to be using the deprecated INTERFACE_PATH and LAYER_PATH environment variables. Should we update this to the newer CHARM_INTERFACES_DIR and CHARM_LAYERS_DIR?

Yes, this doc is quite old. Among other things, it uses the older style of charms that only supported a single series:

juju deploy $CHARM_DIR/trusty/vanilla --series trusty

Charming documentation is being revamped over the next several weeks and this will be addressed as part of that.


Hello if we update series to focal
juju deploy . --series focal

we have an error with PyYAML on juju deploy phase:

2021-09-05 14:28:05 WARNING unit.vanilla/1.install logger.go:60 Traceback (most recent call last):
2021-09-05 14:28:05 WARNING unit.vanilla/1.install logger.go:60   File "/var/lib/juju/agents/unit-vanilla-1/charm/hooks/install", line 8, in <module>
2021-09-05 14:28:05 WARNING unit.vanilla/1.install logger.go:60     basic.bootstrap_charm_deps()
2021-09-05 14:28:05 WARNING unit.vanilla/1.install logger.go:60   File "lib/charms/layer/", line 214, in bootstrap_charm_deps
2021-09-05 14:28:05 WARNING unit.vanilla/1.install logger.go:60     env=_get_subprocess_env())
2021-09-05 14:28:05 WARNING unit.vanilla/1.install logger.go:60   File "/usr/lib/python3.6/", line 311, in check_call
2021-09-05 14:28:05 WARNING unit.vanilla/1.install logger.go:60     raise CalledProcessError(retcode, cmd)
2021-09-05 14:28:05 WARNING unit.vanilla/1.install logger.go:60 subprocess.CalledProcessError: Command '['pip3', 'install', '-U', '--force-reinstall', '--no-index', '--no-cache-dir', '-f', 'wheelhouse', 'charmhelpers==0.20.22', 'PyYAML==5.2', 'pbr==5.6.0', 'Tempita==0.5.2', 'MarkupSafe==1.1.1', 'Jinja2==2.10.1', 'charms.reactive==1.4.1', 'wheel==0.33.6', 'six==1.16.0', 'pyaml==21.8.3', 'netaddr==0.7.19']' returned non-zero exit status 1.
2021-09-05 14:28:06 ERROR juju.worker.uniter.operation runhook.go:139 hook "install" (via explicit, bespoke hook script) failed: exit status 1
2021-09-05 14:28:06 INFO juju.worker.uniter resolver.go:144 awaiting error resolution for "install" hook
2021-09-05 14:28:48 INFO juju.worker.uniter resolver.go:144 awaiting error resolution for "install" hook
2021-09-05 14:28:58 WARNING unit.vanilla/1.install logger.go:60 Cannot uninstall 'PyYAML'. It is a distutils installed project and thus we cannot accurately determine which files belong to it which would lead to only a partial uninstall.