Writing charms that use storage

The storage feature can be implemented in any charm running on Juju version 1.25 or later. For applications that can take advantage of block storage or other types of storage there are two additional storage hooks for the code to react to storage changes.

If you are looking for information on the various storage provider types or how to deploy charms that use storage features see Using Juju storage.

Adding storage

Storage requirements may be added to the metadata.yaml file of the charm as follows:

    type: filesystem
    description: junk storage
    shared: false # not yet supported, see description below
    read-only: false # not yet supported, see description below
    minimum-size: 100M
    location: /srv/data

In this definition, the charm is asking for storage called ‘data’, and it further defines a type and location. It is possible to specify as many entries as desired for storage, and all but the ‘type’ key are optional. The ‘type’ attribute specifies the type of the storage: filesystem or block (i.e. block device/disk). The ‘minimum-size’ attribute specifies the minimum size of the store, overriding the default of 1GiB if the user does not specify a size. The location specifies the path at which to mount filesystem-type storage. The ‘read-only’ and ‘shared’ attributes are currently not handled. Support will be added in a future version of Juju.

A filesystem-type store yields a directory in which the charm may store files. Block-type stores yield raw block devices – typically disks or logical volumes. If the charm specifies a filesystem-type store, and the storage provider supports provisioning only disks, then a disk will be created, attached, partitioned, and a filesystem created on top. The filesystem will be presented to the charm, and rest of the details will be managed by Juju.

By default, stores are singletons; a charm will have exactly one of the specified store. It is also possible for a charm to specify storage that may have multiple instantiations, e.g. multiple disks to add to a pool. To do this, you can specify the “multiple” attribute:

    type: block
      range: 0-10

The definition above indicates that the charm may have anywhere from zero to ten block devices allocated to the ‘disks’ store. The formats supported by “range” are: m (a fixed number), m-n (an explicit range), and m- (a minimum number).

Unless a number is explicitly specified during deployment, units of the application will be allocated the minimum number of storage instances specified in the charm metadata. It is then possible to add instances (up to the maximum) by using the juju storage add command, or using the storage-add hook tool.

Storage hooks

For each storage entity defined in the metadata.yaml file, the following hooks may be implemented:

Each hook is prefixed with the name of the store, similar to how relation hooks are prefixed with the name of the relation. So, for example, if we had specified a need for storage labeled ‘data’, we would probably want to implement the hook ‘data-storage-attached’, which might look something like:

set -eux
mountpoint=$(storage-get -s osd-devices/1 location)
sed -i /etc/myservice.conf "s,MOUNTPOINT,$mountpoint"
status-set maintenance “Storage ready and mounted.”

The [name]-storage-attached hooks will be run before the install hook, so that the installation routine may use the storage. The [name]-storage-detaching hook will be run before storage is detached, and always before the stop hook is run, to allow the charm to gracefully release resources before they are removed and before the unit terminates.

There are several hook tools available for dealing with storage within a charm, described below

  • storage-list

    storage-list may be used to list storage instances that are attached to the unit. The names returned may be passed through to storage-get.

  • storage-get

    storage-get may be used to obtain information about storage being attached to, or detaching from, the unit. If the executing hook is a storage hook, information about the storage related to the hook will be reported; this may be overridden by specifying the name of the storage as reported by storage-list, and must be specified for non-storage hooks.

    storage-get should be used to identify the storage location during storage-attached and storage-detaching hooks. The exception to this is when the charm specifies a static location for singleton stores.

  • storage-add

    storage-add may be used to add storage to the unit. The tool takes the name of the storage (as in the charm metadata), and optionally the number of storage instances to add; by default it will add a single instance.

Persistent storage

Some providers have the option to attach and detach storage from the application that is consuming it. This means that even after applications have been removed, the storage and its contents may still exist in your cloud. This can be useful for backup, recovery, or transport purposes. Storage is now destroyed only when the model is destroyed or when the storage is manually removed.

Future work

Shared storage

Some providers, typically network filesystems, permit attaching storage to multiple machines. We intend to support multiple attachment within Juju. Shared storage will be assigned to a application, and each unit of the application will attach to the same shared storage instance.

1 Like

So, I tried following this guide, using my reference charm. cs:erik-lonroth/tiny-bash

I added to the metadata.yaml

    type: filesystem
    description: local scratch
    shared: false # not yet supported, see description below
    read-only: false # not yet supported, see description below
    minimum-size: 100M
    location: /scratch

However, when I deploy this on AWS. There is no such mountpoint and I can’t see what I’m missing or how I misunderstand how this is working.

Also, I’m getting these error messages which makes no sense to me at all trying to learn how storage works… Looking for some advice to understand this…

So, I can see that a directory is created - but its not a separate disk and is shared with the root (/) disk which was not what I thought I was doing.


I’m confused.

… and now I understand what I made wrong.

‘type’ should be ‘block’ to get a separate disk which is now obvious from reading better.

I’m leaving my post for reference so others dont make the same misstake.


I’m trying to understand what happens in the various stages of a charm when attaching storage to it. Specifically, when the hook: [name]-storage-attached is fired.

What can I know has happened to the instance?

My example is nextcloud, that has a storage location “/var/www/nextcloud/data” where files are stored. I specify in the metadata.yaml that this storage is “filesystem”.

    type: filesystem
    description: primary nextcloud data (block)
    minimum-size: 100M
    location: /var/www/nextcloud/data

After deployment, lets say I add storage:

juju add-storage <unit> data==ebs,100G,1

What can I expect has happened to the unit when the hook “data-storage-attached” is ran by the charm?

What should happen in my case, is that I can shut down the service, move the previous data to a “backup location” (E.g. /var/www/nextcloud/data.old) and then rsync the data into the new device and run the nextcloud re-indexing of the files. But, since the location is unchanged, I don’t know how to manage the re-mounting of the disk or if I have to specify a different location for the data.

Since its not clear to me at this point what has happened to the unit, I’m not sure how to plan the implementation.

I’ll experiment some, but the documentation isn’t helping much here or I’m maybe reading it bad again =D

I spent many hours going through what landed in the nextcloud charm.

Much of the stuff I discovered was not documented which made the process a bit difficult to achieve.

For example that if you don’t specify “multiple: range: 0-1”, the charm will always fire the data-storage-attached hook.

It’s likely much more to it which could be good to have more code examples for…