3.6 to4.0containeragentaction-failaction-getaction-logaction-setapplication-version-setclose-portconfig-getcredential-getgoal-stateis-leaderjuju-logjuju-rebootleader-getleader-setnetwork-getopen-portopened-portspayload-registerpayload-status-setpayload-unregisterrelation-getrelation-idsrelation-listrelation-model-getrelation-setresource-getsecret-addsecret-getsecret-grantsecret-idssecret-info-getsecret-removesecret-revokesecret-setstate-deletestate-getstate-setstatus-getstatus-setstorage-addstorage-getstorage-listunit-getjuju CLIjuju CLI commandsjujuactionsjujuadd-cloudjujuadd-credentialjujuadd-k8sjujuadd-machinejujuadd-modeljujuadd-secretjujuadd-secret-backendjujuadd-spacejujuadd-ssh-keyjujuadd-storagejujuadd-unitjujuadd-userjujuagreejujuagreementsjujuattach-resourcejujuattach-storagejujuautoload-credentialsjujubindjujubootstrapjujucancel-taskjujuchange-user-passwordjujucharm-resourcesjujucloudsjujuconfigjujuconstraintsjujuconsumejujucontroller-configjujucontrollersjujucreate-backupjujucreate-storage-pooljujucredentialsjujudashboardjujudebug-codejujudebug-hooksjujudebug-logjujudefault-credentialjujudefault-regionjujudeployjujudestroy-controllerjujudestroy-modeljujudetach-storagejujudiff-bundlejujudisable-commandjujudisable-userjujudisabled-commandsjujudocumentationjujudownloadjujudownload-backupjujuenable-commandjujuenable-destroy-controllerjujuenable-hajujuenable-userjujuexecjujuexport-bundlejujuexposejujufindjujufind-offersjujufirewall-rulesjujugrantjujugrant-cloudjujugrant-secretjujuhelpjujuhelp-action-commandsjujuhelp-hook-commandsjujuimport-filesystemjujuimport-ssh-keyjujuinfojujuintegratejujukill-controllerjujuloginjujulogoutjujumachinesjujumigratejujumodel-configjujumodel-constraintsjujumodel-defaultsjujumodelsjujumove-to-spacejujuofferjujuoffersjujuoperationsjujupayloadsjujurefreshjujuregionsjujuregisterjujureload-spacesjujuremove-applicationjujuremove-cloudjujuremove-credentialjujuremove-k8sjujuremove-machinejujuremove-offerjujuremove-relationjujuremove-saasjujuremove-secretjujuremove-secret-backendjujuremove-spacejujuremove-ssh-keyjujuremove-storagejujuremove-storage-pooljujuremove-unitjujuremove-userjujurename-spacejujuresolvedjujuresourcesjujuresume-relationjujuretry-provisioningjujurevokejujurevoke-cloudjujurevoke-secretjujurunjujuscale-applicationjujuscpjujusecret-backendsjujusecretsjujuset-application-basejujuset-constraintsjujuset-credentialjujuset-firewall-rulejujuset-model-constraintsjujushow-actionjujushow-applicationjujushow-cloudjujushow-controllerjujushow-credentialjujushow-machinejujushow-modeljujushow-offerjujushow-operationjujushow-secretjujushow-secret-backendjujushow-spacejujushow-status-logjujushow-storagejujushow-taskjujushow-unitjujushow-userjujuspacesjujusshjujussh-keysjujustatusjujustoragejujustorage-poolsjujusubnetsjujususpend-relationjujuswitchjujusync-agent-binaryjujutrustjujuunexposejujuunregisterjujuupdate-cloudjujuupdate-credentialjujuupdate-k8sjujuupdate-public-cloudsjujuupdate-secretjujuupdate-secret-backendjujuupdate-storage-pooljujuupgrade-controllerjujuupgrade-machinejujuupgrade-modeljujuusersjujuversionjujuwait-forjujuwait-for_applicationjujuwait-for_machinejujuwait-for_modeljujuwait-for_unitjujuwhoamijuju environment variablesjuju-metadatajuju-dashboard (The Juju dashboard)juju web CLIjujucjujudJuju is a tool for provisioning cloud infrastructure and deploying and operating applications on that infrastructure using charms. Charms are software packages that contain code for how to operate an application. Juju and charms work together to provide a cloud- and application-agnostic operations solution for any major operation (provision, install, configure, integrate, scale, upgrade, …) on any type of cloud (Kubernetes or machines).
In this tutorial you will get acquainted with Juju and charms by deploying a chat service on a Kubernetes cloud.
What you’ll need:
A workstation that has sufficient resources to launch a virtual machine with 4 CPUs, 8 GB RAM, and 50 GB disk space.
Familiarity with a terminal.
What you’ll do:
Set up an isolated test environment with Multipass, then set up Juju with a localhost MicroK8s cloud, and use it to deploy, configure, and integrate the charms required to set up a chat service based on Mattermost and PostgreSQL.
When you’re trying things out it’s nice to work in an isolated test environment. Let’s spin up an Ubuntu virtual machine (VM) with Multipass!
First,install Multipass.
Now, launch an Ubuntu VM and open a shell in the VM:
~$multipasslaunch--cpus4--memory8G--disk50G--namemy-juju-vmLaunched: my-juju-vm
~$multipassshellmy-juju-vmWelcome to Ubuntu 24.04.2 LTS (GNU/Linux 6.8.0-64-generic x86_64) * Documentation: https://help.ubuntu.com * Management: https://landscape.canonical.com * Support: https://ubuntu.com/pro System information as of Fri Aug 1 09:46:41 CEST 2025 System load: 0.24 Processes: 145 Usage of /: 3.3% of 47.39GB Users logged in: 0 Memory usage: 3% IPv4 address for ens3: 10.238.98.204 Swap usage: 0%Expanded Security Maintenance for Applications is not enabled.18 updates can be applied immediately.18 of these updates are standard security updates.To see these additional updates run: apt list --upgradableEnable ESM Apps to receive additional future security updates.See https://ubuntu.com/esm or run: sudo pro statusTo run a command as administrator (user "root"), use "sudo <command>".See "man sudo_root" for details.
(If the VM launch fails, runmultipassdelete--purgemy-juju-vm to clean up, then try the launch line again.)
Anything you type after the VM shell prompt (ubuntu@my-juju-vm:~$) will run on the VM.
At any point:
To exit the shell, pressmod +C (e.g.,Ctrl+D) or typeexit.
To stop the VM after exiting the VM shell, runmultipassstopmy-juju-vm.
To restart the VM and re-open a shell into it, typemultipassshellmy-juju-vm.
Juju consists of at least a client and a controller, and needs access to a cloud (anything that can provide compute, networking, and storage) and to Charmhub (the charm store; otherwise, a local source of charms).¶
Juju consists of at least a client and a controller, and needs access to a cloud (anything that can provide compute, networking, and storage) and to Charmhub (the charm store; otherwise, a local source of charms).¶
The way Juju works is that you use a client to talk to a controller; the controller talks to a cloud to provision infrastructure and toCharmhub (or a local source for charms) to get charms to deploy, configure, integrate, scale, upgrade, etc., applications on that infrastructure; and the controller itself must live on a cloud resource, so before you do any of that you must use the client to talk to a cloud and Charmhub to bootstrap the controller into the cloud. Let’s prepare all those pieces and make sure your Juju is good to go!
To Juju a cloud is anything that has an API where you can request compute, storage, and networking.
This includes traditional machine clouds (Amazon AWS, Google GCE, Microsoft Azure, but also MAAS, OpenStack, Oracle OCI, and LXD) as well as Kubernetes clusters (Amazon EKS, Google GKE, Microsoft AKS but also Canonical Kubernetes or MicroK8s).
In this tutorial we will use MicroK8s, a lightweight Kubernetes that you can also use to get a small, single-node localhost Kubernetes cluster. Let’s set it up on your VM:
ubuntu@my-juju-vm:~$sudosnapinstallmicrok8s--channel1.28-strict2025-08-01T09:47:10+02:00 INFO Waiting for automatic snapd restart...microk8s (1.28-strict/stable) v1.28.15 from Canonical✓ installed
ubuntu@my-juju-vm:~$sudoadduser$USERsnap_microk8sinfo: Adding user `ubuntu' to group `snap_microk8s' ...
ubuntu@my-juju-vm:~$sudochown-f-R$USER~/.kubeubuntu@my-juju-vm:~$sudomicrok8sstatus--wait-readymicrok8s is runninghigh-availability: no datastore master nodes: 127.0.0.1:19001 datastore standby nodes: noneaddons: enabled: dns # (core) CoreDNS ha-cluster # (core) Configure high availability on the current node helm # (core) Helm - the package manager for Kubernetes helm3 # (core) Helm 3 - the package manager for Kubernetes disabled: cert-manager # (core) Cloud native certificate management cis-hardening # (core) Apply CIS K8s hardening community # (core) The community addons repository dashboard # (core) The Kubernetes dashboard host-access # (core) Allow Pods connecting to Host services smoothly hostpath-storage # (core) Storage class; allocates storage from host directory ingress # (core) Ingress controller for external access mayastor # (core) OpenEBS MayaStor metallb # (core) Loadbalancer for your Kubernetes cluster metrics-server # (core) K8s Metrics Server for API access to service metrics minio # (core) MinIO object storage observability # (core) A lightweight observability stack for logs, traces and metrics prometheus # (core) Prometheus operator for monitoring and logging rbac # (core) Role-Based Access Control for authorisation registry # (core) Private image registry exposed on localhost:32000 rook-ceph # (core) Distributed Ceph storage using Rook storage # (core) Alias to hostpath-storage add-on, deprecated
ubuntu@my-juju-vm:~$sudomicrok8senablehostpath-storagednsInfer repository core for addon hostpath-storageInfer repository core for addon dnsWARNING: Do not enable or disable multiple addons in one command. This form of chained operations on addons will be DEPRECATED in the future. Please, enable one addon at a time: 'microk8s enable <addon>'Enabling default storage class.WARNING: Hostpath storage is not suitable for production environments. A hostpath volume can grow beyond the size limit set in the volume claim manifest.deployment.apps/hostpath-provisioner createdstorageclass.storage.k8s.io/microk8s-hostpath createdserviceaccount/microk8s-hostpath createdclusterrole.rbac.authorization.k8s.io/microk8s-hostpath createdclusterrolebinding.rbac.authorization.k8s.io/microk8s-hostpath createdStorage will be available soon.Addon core/dns is already enabled
ubuntu@my-juju-vm:~$sudosnapaliasmicrok8s.kubectlkubectlAdded: - microk8s.kubectl as kubectl
ubuntu@my-juju-vm:~$newgrpsnap_microk8subuntu@my-juju-vm:~$mkdir-p~/.local/shareCongratulations, your cloud is ready!
Your Juju can automatically reach Charmhub – nothing to be done.
juju CLI client¶In Juju a (user-facing) client is anything that can talk to a Juju controller. That currently includes thejuju CLI, theterraform CLI when used with thejuju provider plugin, two Python clients, and a JavaScript client. However, to get a Juju controller you need thejuju CLI client and it must have access to a cloud and Charmhub. Let’s set it up!
In your VM, install thejuju CLI client:
ubuntu@my-juju-vm:~$sudosnapinstalljujujuju (3/stable) 3.6.8 from Canonical✓ installed
Now, ensure the client has access to your cloud (i.e., knows where to find your cloud and has the credentials to access your cloud). For a localhost MicroK8s cloud installed from a strictly confined snap like ours, yourjuju client can read the local kubeconfig file and retrieve the cloud definition (and credentials) from there automatically, as you can verify:
ubuntu@my-juju-vm:~$jujuclouds--clientOnly clouds with registered credentials are shown.There are more clouds, use --all to see them.You can bootstrap a new controller using one of these clouds...Clouds available on the client:Cloud Regions Default Type Credentials Source Descriptionlocalhost 1 localhost lxd 0 built-in LXD Container Hypervisormicrok8s 1 localhost k8s 1 built-in A Kubernetes Cluster
(If this doesn’t show any output: Exit the VM (exit), re-enter it (multipassshellmy-juju-vm), then try again.)
Ensure also that the client has access to Charmhub by performing a random search, e.g., using the keyword “ingress”, and then asking for more information about one of the results it shows:
ubuntu@my-juju-vm:~$jujufindingress# Output should show all the charms or charm bundles related to this query that are available on Charmhub.# For best results always double-check Charmhub.
ubuntu@my-juju-vm:~$jujuinfotraefik-k8s# Output should show name, publisher, quick description, integration endpoints, etc.# For the full features and more info see directly the charm's page on Charmhub.
Ourjuju client is ready! Take a quick look at what it can do:jujuhelpcommands. (You can also pipe a query to zoom in on feature:jujuhelpcommands|grepapplication.)
A Juju controller is your Juju control plane – the entity that holds the Juju API server and Juju’s database. Anything you do in Juju post-controller-setup goes through a Juju controller, and to work properly the controller needs access to a cloud and to Charmhub (or a local source of charms). Let’s set it up!
In your VM, use your client and its access to the MicroK8s cloud to bootstrap a Juju controller:
ubuntu@my-juju-vm:~$jujubootstrapmicrok8smy-first-juju-controllerCreating Juju controller "my-first-juju-controller" on microk8s/localhostBootstrap to Kubernetes cluster identified as microk8s/localhostCreating k8s resources for controller "controller-my-first-juju-controller"Downloading imagesStarting controller podBootstrap agent now startedContacting Juju controller at 10.152.183.180 to verify accessibility...Bootstrap complete, controller "my-first-juju-controller" is now available in namespace "controller-my-first-juju-controller"Now you can runjuju add-model <model-name>to create a new model to deploy k8s workloads.
This will use ingredients from your client, thejuju-controller charm from Charmhub and a pod from MicroK8s (backed by your current node – your VM) to give you a running Juju controller.
Now, to be fully operational a controller needs access to a cloud and to Charmhub (or a local source for charms). Our controller already has access to our ‘microk8s’ cloud – this access was granted implicitly through bootstrap. Also, as before, so long as you’re connected to the internet, your controller has access to Charmhub too. Your controller is all set!
At this point we could connect to it further clouds or set up the Juju dashboard. For the purpose of this tutorial, however, we will skip ahead to talking about users and permissions.
A user is any person that can log in to a Juju controller.¶
A user is any person that can log in to a Juju controller.¶
Your client and controller can already talk to a cloud and Charmhub, but they don’t run on their own – enter the user! In Juju, the user is any person that can log in to a controller, and what they can do can be controlled at the level of the controller or some of the smaller entities associated with that controller. As the entity that has bootstrapped the controller, you have automatically been logged in and givensuperuser access. Let’s verify:
ubuntu@my-juju-vm:~$jujuwhoamiController: my-first-juju-controllerModel: <no-current-model>User: admin
ubuntu@my-juju-vm:~$jujushow-useradminuser-name: admindisplay-name: adminaccess: superuserdate-created: 3 hours agolast-connection: just now
At this point you could add further users and control their permissions. However, for the purpose of this tutorial, we will skip ahead and deploy our chat service!
A user interacts with the client to reach the controller. The controller talks to the cloud and to Charmhub to provision infrastructure and to deploy charms. Next to a deployed charm there is always a Juju agent which periodically checks its internal state against the Juju controller and executes the deployed charm accordingly to install, configure, and otherwise manage applications.¶
A user interacts with the client to reach the controller. The controller talks to the cloud and to Charmhub to provision infrastructure and to deploy charms. Next to a deployed charm there is always a Juju agent which periodically checks its internal state against the Juju controller and executes the deployed charm accordingly to install, configure, and otherwise manage applications.¶
Anything you provision or deploy and operate with a Juju controller goes onto a workspace called a ‘model’. Let’s create the model that will hold our chat applications:
ubuntu@my-juju-vm:~$jujuadd-modelmy-chat-modelAdded 'my-chat-model' model on microk8s/localhost with credential 'microk8s' for user 'admin'
This will automatically also switch you to that model.
Now, let’s deploy, configure, and integrate the charmed applications that will make up our chat service:
First,Mattermost:
ubuntu@my-juju-vm:~$jujudeploymattermost-k8s--constraints"mem=2G"Deployed "mattermost-k8s" from charm-hub charm "mattermost-k8s", revision 27 in channel latest/stable on ubuntu@20.04/stable
Now, its dependencies. Mattermost needs a PostgreSQL database, andits charmed version supports an easy way to integrate with such a database. Let’s deploythe PostgreSQL charm for Kubernetes in the recommended way, from track14 with riskstable; with--trust – i.e., permission to use our cloud credentials (this charm needs to create and manage some Kubernetes resources); because we’re just playing around, settingtheprofile config totesting, so we don’t use too many resources; and, just for fun, with-n2, that is, two replicas (in a real life setting you’ll want to distribute them over multiple nodes – something Juju would do automatically here too, except we’re doing everything on a single node).
ubuntu@my-juju-vm:~$jujudeploypostgresql-k8s--channel14/stable--trust--configprofile=testing-n2Deployed "postgresql-k8s" from charm-hub charm "postgresql-k8s", revision 495 in channel 14/stable on ubuntu@22.04/stable
Mattermost wants PostgreSQL status to be TLS-encrypted. There are a few ways to do that. Because we’re just trying things out, we can useSelf Signed X.509 Certificates (don’t do this in production!). Let’s deploy it and integrate it with our PostgreSQL to enable TLS encryption on our PostgreSQL cluster:
ubuntu@my-juju-vm:~$jujudeployself-signed-certificatesDeployed "self-signed-certificates" from charm-hub charm "self-signed-certificates", revision 317 in channel 1/stable on ubuntu@24.04/stable
ubuntu@my-juju-vm:~$jujuintegrateself-signed-certificatespostgresql-k8sFinally, time to integrate Postgresql with Mattermost:
ubuntu@my-juju-vm:~$jujuintegratepostgresql-k8s:dbmattermost-k8sWhile executing any of these commands returns automatically so you can execute the next, standing things up in the cloud takes a little bit of time; watch your progress withjujustatus--relations--color--watch1s. Things are all set when the output looks similar to the one below:
ubuntu@my-juju-vm:~$jujustatus--relations--colorModel Controller Cloud/Region Version SLA Timestampmy-chat-model my-first-juju-controller microk8s/localhost 3.6.8 unsupported 13:26:28+02:00App Version Status Scale Charm Channel Rev Address Exposed Messagemattermost-k8s .../mattermost:v8.1.3-20.04... active 1 mattermost-k8s latest/stable 27 10.152.183.144 nopostgresql-k8s 14.15 active 2 postgresql-k8s 14/stable 495 10.152.183.184 noself-signed-certificates active 1 self-signed-certificates 1/stable 317 10.152.183.160 noUnit Workload Agent Address Ports Messagemattermost-k8s/0* active idle 10.1.32.142 8065/TCPpostgresql-k8s/0* active idle 10.1.32.139 Primarypostgresql-k8s/1 active idle 10.1.32.140self-signed-certificates/0* active idle 10.1.32.141Integration provider Requirer Interface Type Messagepostgresql-k8s:database-peers postgresql-k8s:database-peers postgresql_peers peerpostgresql-k8s:db mattermost-k8s:db pgsql regularpostgresql-k8s:restart postgresql-k8s:restart rolling_op peerpostgresql-k8s:upgrade postgresql-k8s:upgrade upgrade peerself-signed-certificates:certificates postgresql-k8s:certificates tls-certificates regular
Time to test the results! From the output ofjujustatus>Unit >mattermost-k8s/0, retrieve the IP address and the port and feed them tocurl on the templatecurl<IPaddress>:<portnumber>/api/v4/system/ping. Given the IP we got above:
ubuntu@my-juju-vm:~$curl10.1.32.142:8065/api/v4/system/ping{"ActiveSearchBackend":"database","AndroidLatestVersion":"","AndroidMinVersion":"","IosLatestVersion":"","IosMinVersion":"","status":"OK"}Congratulations, your chat service is up and running!
At this point you can keep your current Juju setup to experiment further or proceed to the next section to tear things down.
Tip
To tear everything down at once, skip to the step where you delete your Multipass VM and uninstall Multipass.
Tear down your Juju deployment:
ubuntu@my-juju-vm:~$jujudestroy-modelmy-chat-model--destroy-storageWARNING This command will destroy the "my-chat-model" model and affect the following resources. It cannot be stopped. - 3 applications will be removed - application list: "mattermost-k8s" "postgresql-k8s" "self-signed-certificates" - 2 filesystems and 2 volumes will be destroyedTo continue, enter the name of the model to be unregistered: my-chat-modelDestroying modelWaiting for model to be removed, 3 application(s), 2 volume(s), 2 filesystems(s)..........Waiting for model to be removed, 3 application(s), 2 volume(s), 1 filesystems(s).....Waiting for model to be removed, 3 application(s), 2 volume(s).....Waiting for model to be removed, 3 application(s), 1 volume(s)...Waiting for model to be removed, 2 application(s).....Waiting for model to be removed, 1 application(s)............Waiting for model to be removed........Model destroyed.
ubuntu@my-juju-vm:~$jujudestroy-controllermy-first-juju-controllerWARNING This command will destroy the "my-first-juju-controller" controller and all its resourcesTo continue, enter the name of the controller to be unregistered: my-first-juju-controllerDestroying controllerWaiting for model resources to be reclaimedAll models reclaimed, cleaning up controller machines
ubuntu@my-juju-vm:~$sudosnapremovejujujuju removed
ubuntu@my-juju-vm:~$sudomicrok8sresetDisabling all addonsDisabling addon : core/cert-managerDisabling addon : core/cis-hardeningDisabling addon : core/dashboardDisabling addon : core/dnsDisabling addon : core/helmDisabling addon : core/helm3Disabling addon : core/host-accessDisabling addon : core/hostpath-storageDisabling addon : core/ingressDisabling addon : core/mayastorDisabling addon : core/metallbDisabling addon : core/metrics-serverDisabling addon : core/minioDisabling addon : core/observabilityDisabling addon : core/prometheusDisabling addon : core/rbacDisabling addon : core/registryDisabling addon : core/rook-cephDisabling addon : core/storageAll addons are disabled.Deleting the CNICleaning resources in namespace defaultCleaning resources in namespace kube-node-leaseCleaning resources in namespace kube-publicCleaning resources in namespace kube-systemRemoving CRDsRemoving PriorityClassesRemoving StorageClassesRestarting clusterSetting up the CNI
ubuntu@my-juju-vm:~$$sudosnapremovemicrok8smicrok8s removed
ubuntu@my-juju-vm:~$sudogpasswd-d$USERsnap_microk8sRemoving user ubuntu from group snap_microk8s
Now exit the VM (in your terminal typeexit); then, from your host machine, delete the VM:
~$multipassdelete--purgemy-juju-vm~$multipasslistNo instances found.
Finally,uninstall Multipass.