Write your first Kubernetes charm for a Spring Boot app

Imagine you have a Spring Boot app backed up by a database such as PostgreSQL and need to deploy it. In a traditional setup, this can be quite a challenge, but with Charmcraft you’ll find yourself packaging and deploying your Spring Boot app in no time.

In this tutorial we will build a Kubernetes charm for a Spring Boot app using Charmcraft, so we can have a Spring Boot app up and running with Juju. Let’s get started!

This tutorial should take 90 minutes for you to complete.

If you’re new to the charming world, Spring Boot apps are specifically supported with a template to quickly generate a rock and a matching template to generate a charm. A rock is a special kind of OCI-compliant container image, while a charm is a software operator for cloud operations that use the Juju orchestration engine. The combined result is a Spring Boot app that can be deployed, configured, scaled, integrated, and so on, on any Kubernetes cluster.

What you’ll need

  • A local system, e.g., a laptop, with AMD64 or ARM64 architecture which has sufficient resources to launch a virtual machine with 4 CPUs, 4 GB RAM, and a 50 GB disk.

  • Familiarity with Linux.

What you’ll do

  1. Create a Spring Boot app.

  2. Use that to create a rock with Rockcraft.

  3. Use that to create a charm with Charmcraft.

  4. Use that to test, deploy, configure, etc., your Spring Boot app on a local Kubernetes cloud with Juju.

  5. Repeat the process, mimicking a real development process.

Important

Should you get stuck or notice issues, please get in touch on Matrix or Discourse

Set things up

First, install Multipass.

Use Multipass to launch an Ubuntu VM with the name charm-dev from the 24.04 blueprint:

multipass launch --cpus 4 --disk 50G --memory 4G --name charm-dev 24.04

Once the VM is up, open a shell into it:

multipass shell charm-dev

In order to create the rock, you need to install Rockcraft with classic confinement, which grants it access to the whole file system:

sudo snap install rockcraft --channel latest/edge --classic

LXD will be required for building the rock. Make sure it is installed and initialized:

lxd --version
lxd init --auto

If LXD is not installed, install it with sudo snap install lxd.

In order to create the charm, you’ll need to install Charmcraft:

sudo snap install charmcraft --channel latest/edge --classic

MicroK8s is required to deploy the Spring Boot application on Kubernetes. Let’s install MicroK8s using the 1.31-strict/stable track, add the current user to the group, and activate the changes:

sudo snap install microk8s --channel 1.31-strict/stable
sudo adduser $USER snap_microk8s
newgrp snap_microk8s

Several MicroK8s add-ons are required for deployment:

# Required for Juju to provide storage volumes
sudo microk8s enable hostpath-storage
# Required to host the OCI image of the application
sudo microk8s enable registry
# Required to expose the application
sudo microk8s enable ingress

Check the status of MicroK8s:

sudo microk8s status --wait-ready

If successful, the terminal will output microk8s is running along with a list of enabled and disabled add-ons.

Juju is required to deploy the Spring Boot application. We’ll install Juju using the 3.6/stable track. Since the snap is sandboxed, we’ll also manually create a directory to contain its files. Once Juju is ready, we initialize it by bootstrapping a development controller:

sudo snap install juju --channel 3.6/stable
mkdir -p ~/.local/share
juju bootstrap microk8s dev-controller

It could take a few minutes to download the images.

As the spring-boot-framework extensions for Rockcraft and Charmcraft are still in development, we must enable experimental extensions for each:

export ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true
export CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true

Create the Spring Boot app

Start by creating the “Hello, world” Spring Boot app that will be used for this tutorial.

Install devpack-for-spring and Java.

sudo snap install devpack-for-spring --classic
sudo apt update && sudo apt install -y openjdk-21-jdk

Create the demo Spring Boot app that will be used for this tutorial.

See also

For more information about the options: Spring Boot CLI | Using the CLI

devpack-for-spring boot start \
  --path spring-boot-hello-world \
  --project maven-project \
  --language java \
  --boot-version 3.4.4 \
  --version 0.0.1 \
  --group com.example \
  --artifact demo \
  --name demo \
  --description "Demo project for Spring Boot" \
  --package-name com.example.demo \
  --dependencies web \
  --packaging jar \
  --java-version 21
cd spring-boot-hello-world

Create a new “Hello world” file with nano ~/spring-boot-hello-world/src/main/java/com/example/demo/HelloController.java. Then, copy the following text into it, and save:

~/spring-boot-hello-world/src/main/java/com/example/demo/HelloController.java
package com.example.demo;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @GetMapping("/")
    public String index() {
        return "Hello, world!";
    }
}

Run the Spring Boot app locally

First, we need to build the Spring Boot app so it can run:

./mvnw clean install

The app compiles to a JAR called demo-0.0.1.jar in ~/spring-boot-hello-world/target/. We’ll only use this JAR for local testing, as Rockcraft will package the Spring Boot app when we pack the rock later.

Let’s run the app to verify that it works:

java -jar target/demo-0.0.1.jar

The app starts an HTTP server listening on port 8080 that we can test by using curl to send a request to the root endpoint. You will need a new terminal for this – use multipass shell charm-dev to open a new terminal in Multipass:

curl localhost:8080

The Spring Boot app should respond with Hello, world!

The Spring Boot app looks good, so let’s stop it for now with Ctrl + C and close the second terminal.

Pack the Spring Boot app into a rock

Now let’s create a container image for our Spring Boot app. We’ll use a rock, which is an OCI-compliant container image based on Ubuntu.

First, we’ll need a rockcraft.yaml project file. We’ll take advantage of a pre-defined extension in Rockcraft with the --profile flag that caters initial rock files for specific web app frameworks. Using the spring-boot-framework profile, Rockcraft automates the creation of rockcraft.yaml and tailors the file for a Spring Boot app. From the ~/spring-boot-hello-world directory, initialize the rock:

rockcraft init --profile spring-boot-framework

The rockcraft.yaml file will automatically be created and set the name based on your working directory.

Check out the contents of rockcraft.yaml:

cat rockcraft.yaml

The top of the file should look similar to the following snippet:

~/spring-boot-hello-world/rockcraft.yaml
name: spring-boot-hello-world
# see https://documentation.ubuntu.com/rockcraft/en/latest/explanation/bases/
# for more information about bases and using 'bare' bases for chiselled rocks
base: bare # as an alternative, a ubuntu base can be used
build-base: [email protected] # build-base is required when the base is bare
version: '0.1' # just for humans. Semantic versioning is recommended
summary: A summary of your Spring Boot application # 79 char long summary
description: |
    This is spring-boot-hello-world's description. You have a paragraph or two to tell the
    most important story about it. Keep it under 100 words though,
    we live in tweetspace and your description wants to look good in the
    container registries out there.
# the platforms this rock should be built on and run on.
# you can check your architecture with `dpkg --print-architecture`
platforms:
    amd64:
    # arm64:
    # ppc64el:
    # s390x:

Verfiy that the name is spring-boot-hello-world.

The platforms key must match the architecture of your host. Check the architecture of your system:

dpkg --print-architecture

Edit the platforms key in rockcraft.yaml if required.

Now let’s pack the rock:

rockcraft pack

Depending on your system and network, this step can take several minutes to finish.

For more options when packing rocks

See the pack command reference.

Once Rockcraft has finished packing the Spring Boot rock, the terminal will respond with something similar to Packed spring-boot-hello-world_0.1_<architecture>.rock. The file name reflects your system’s architecture. After the initial pack, subsequent rock packings are faster.

The rock needs to be copied to the MicroK8s registry. This registry acts as a temporary Dockerhub, storing OCI archives so they can be downloaded and deployed in the Kubernetes cluster. Copy the rock:

rockcraft.skopeo copy \
  --insecure-policy \
  --dest-tls-verify=false \
  oci-archive:spring-boot-hello-world_0.1_$(dpkg --print-architecture).rock \
  docker://localhost:32000/spring-boot-hello-world:0.1

This command contains the following pieces:

  • --insecure-policy: adopts a permissive policy that removes the need for a dedicated policy file.

  • --dest-tls-verify=false: disables the need for HTTPS and verify certificates while interacting with the MicroK8s registry.

  • oci-archive: specifies the rock we created for our Spring Boot app.

  • docker: specifies the name of the image in the MicroK8s registry.

See also

See more: Ubuntu manpage | skopeo

Create the charm

From the ~/spring-boot-hello-world directory, let’s create a new directory for the charm and change inside it:

mkdir charm
cd charm

Similar to the rock, we’ll take advantage of a pre-defined extension in Charmcraft with the --profile flag that caters initial charm files for specific web app frameworks. Using the spring-boot-framework profile, Charmcraft automates the creation of the files needed for our charm, including a charmcraft.yaml project file, requirements.txt and source code for the charm. The source code contains the logic required to operate the Spring Boot app.

Initialize a charm named spring-boot-hello-world:

charmcraft init --profile spring-boot-framework --name spring-boot-hello-world

The files will automatically be created in your working directory.

Check out the contents of charmcraft.yaml:

cat charmcraft.yaml

The top of the file should look similar to the following snippet:

~/spring-boot-hello-world/charm/charmcraft.yaml
# This file configures Charmcraft.
# See https://juju.is/docs/sdk/charmcraft-config for guidance.

name: spring-boot-hello-world

type: charm

base: [email protected]

# the platforms this charm should be built on and run on.
# you can check your architecture with `dpkg --print-architecture`
platforms:
  amd64:
  # arm64:
  # ppc64el:
  # s390x:

# (Required)
summary: A very short one-line summary of the Spring Boot app.

...

Verify that the name is spring-boot-hello-world. Ensure that platforms includes the architecture of your host. Edit the platforms key in the project file if required.

Tip

Want to learn more about all the configurations in the spring-boot-framework profile? Run charmcraft expand-extensions from the ~/spring-boot-hello-world/charm/ directory.

Let’s pack the charm:

charmcraft pack

Depending on your system and network, this step can take several minutes to finish.

Once Charmcraft has finished packing the charm, the terminal will respond with something similar to Packed spring-boot-hello-world_ubuntu-24.04-<architecture>.charm. The file name reflects your system’s architecture. After the initial pack, subsequent charm packings are faster.

For more options when packing charms

See the pack command reference.

Deploy the Spring Boot app

A Juju model is needed to handle Kubernetes resources while deploying the Spring Boot app. The Juju model holds the app along with any supporting components. In this tutorial, our model will hold the Spring Boot app, ingress, and a PostgreSQL database.

Let’s create a new model:

juju add-model spring-boot-hello-world

Constrain the Juju model to your architecture:

juju set-model-constraints -m spring-boot-hello-world \
  arch=$(dpkg --print-architecture)

Now let’s use the OCI image we previously uploaded to deploy the Spring Boot app. Deploy using Juju by specifying the OCI image name with the --resource option:

juju deploy \
  ./spring-boot-hello-world_$(dpkg --print-architecture).charm \
  spring-boot-hello-world --resource \
  app-image=localhost:32000/spring-boot-hello-world:0.1

It will take a few minutes to deploy the Spring Boot app. You can monitor its progress with:

juju status --watch 2s

It can take a couple of minutes for the app to finish the deployment. Once the status of the App has gone to active, you can stop watching using Ctrl + C.

Tip

To monitor your deployment, keep a juju status session active in a second terminal.

See more: Juju | juju status

The Spring Boot app should now be running. We can monitor the status of the deployment using juju status, which should be similar to the following output:

~$ juju status
Model                    Controller      Cloud/Region        Version  SLA          Timestampspring-boot-hello-world  dev-controller  microk8s/localhost  3.6.6    unsupported  16:22:04+02:00 App                      Version  Status  Scale  Charm                    Channel  Rev  Address         Exposed  Messagespring-boot-hello-world           active      1  spring-boot-hello-world             0  10.152.183.157  no Unit                        Workload  Agent  Address       Ports  Messagespring-boot-hello-world/0*  active    idle   10.1.223.117

Let’s expose the app using ingress. Deploy the nginx-ingress-integrator charm and integrate it with the Spring Boot app:

juju deploy nginx-ingress-integrator --channel=latest/stable --trust
juju integrate nginx-ingress-integrator spring-boot-hello-world

The hostname of the app needs to be defined so that it is accessible via the ingress. We will also set the default route to be the root endpoint:

juju config nginx-ingress-integrator \
  service-hostname=spring-boot-hello-world path-routes=/

Note

By default, the port for the Spring Boot app should be 8080. If you want to change the default port, it can be done with the configuration option app-port that will be exposed as the environment variable SERVER_PORT to the Spring Boot app.

Monitor juju status until everything has a status of active.

Use curl http://spring-boot-hello-world --resolve spring-boot-hello-world:80:127.0.0.1 to send a request via the ingress. It should return the Hello, world! greeting.

Note

The --resolve spring-boot-hello-world:80:127.0.0.1 option to the curl command is a way of resolving the hostname of the request without setting a DNS record.

The development cycle

So far, we have worked though the entire cycle, from creating an app to deploying it. But now – as in every real-world case – we will go through the experience of iterating to develop the app, and deploy each iteration.

Configure the Spring Boot app

To demonstrate how to provide a configuration to the Spring Boot app, we will make the greeting configurable. We will expect this configuration option to be available in the Spring Boot app configuration under the keyword GREETING. Change back to the ~/spring-boot-hello-world directory using cd .. and replace the code into src/main/java/com/example/demo/HelloController.java with the following:

~/spring-boot-hello-world/src/main/java/com/example/demo/HelloController.java
package com.example.demo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @Value("${app.greeting:Hello, world!}")
    private String greeting;

    @GetMapping("/")
    public String index() {
        return greeting;
    }
}

Update the rock

Increment the version in rockcraft.yaml to 0.2 such that the top of the rockcraft.yaml file looks similar to the following:

~/spring-boot-hello-world/rockcraft.yaml
name: spring-boot-hello-world
# see https://documentation.ubuntu.com/rockcraft/en/latest/explanation/bases/
# for more information about bases and using 'bare' bases for chiselled rocks
base: bare # as an alternative, a ubuntu base can be used
build-base: [email protected] # build-base is required when the base is bare
version: '0.2' # just for humans. Semantic versioning is recommended
summary: A summary of your Spring Boot app # 79 char long summary
description: |
    This is spring-boot-hello-world's description. You have a paragraph or two to tell the
    most important story about it. Keep it under 100 words though,
    we live in tweetspace and your description wants to look good in the
    container registries out there.
# the platforms this rock should be built on and run on.
# you can check your architecture with `dpkg --print-architecture`
platforms:
    amd64:
    # arm64:
    # ppc64el:
    # s390x:

Let’s pack and upload the rock:

rockcraft pack
rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \
  oci-archive:spring-boot-hello-world_0.2_$(dpkg --print-architecture).rock \
  docker://localhost:32000/spring-boot-hello-world:0.2

Update the charm

Change back into the charm directory using cd charm.

The spring-boot-framework Charmcraft extension supports adding configurations to charmcraft.yaml, which will be passed as environment variables to the Spring Boot app. Add the following to the end of the charmcraft.yaml file:

~/spring-boot-hello-world/charm/charmcraft.yaml
# configuration snippet for Spring Boot application

config:
  options:
    greeting:
      description: |
        The greeting to be returned by the Spring Boot application.
      default: "Hello, world!"
      type: string

Note

Configuration options are automatically capitalized and - are replaced by _. An APP_ prefix will also be added as a namespace for app configurations.

We can now pack and deploy the new version of the Spring Boot app:

charmcraft pack
juju refresh spring-boot-hello-world \
  --path=./spring-boot-hello-world_$(dpkg --print-architecture).charm \
  --resource app-image=localhost:32000/spring-boot-hello-world:0.2

After we wait for a bit monitoring juju status the app should go back to active again. Verify that the new configuration has been added using juju config spring-boot-hello-world | grep -A 6 greeting:, which should show the configuration option.

Using curl http://spring-boot-hello-world --resolve spring-boot-hello-world:80:127.0.0.1 shows that the response is still Hello, world! as expected.

Now let’s change the greeting:

juju config spring-boot-hello-world greeting='Hi!'

After we wait for a moment for the app to be restarted, using curl http://spring-boot-hello-world  --resolve spring-boot-hello-world:80:127.0.0.1 should now return the updated Hi! greeting.

Integrate with a database

Now let’s keep track of how many visitors your app has received. This will require integration with a database to keep the visitor count. This will require a few changes:

  • We will need to create a database migration that creates the visitors table.

  • We will need to keep track how many times the root endpoint has been called in the database.

  • We will need to add a new endpoint to retrieve the number of visitors from the database.

Let’s start with the database migration to create the required tables. We will use the auto DDL generation from JPA using the property spring.jpa.generate-ddl.

Go back out to the ~/spring-boot-hello-world directory using cd ... Add the following line to the src/main/resources/application.properties file:

~/spring-boot-hello-world/src/main/resources/application.properties
spring.jpa.generate-ddl=true

To connect the Spring Boot app to PostgreSQL, we need Spring Data JPA and the postgresl driver. We also need the h2 driver for the tests to pass. Add the following snippet to the app’s pom.xml, under the dependencies tag:

~/spring-boot-hello-world/pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.7.7</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>test</scope>
</dependency>

Replace the contents of src/main/java/com/example/demo/HelloController.java with the following:

~/spring-boot-hello-world/src/main/java/com/example/demo/ HelloController.java
package com.example.demo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @Value("${app.greeting:Hello, world!}")
    private String greeting;

    private final VisitorService visitorService;

    public HelloController(VisitorService visitorService) {
        this.visitorService = visitorService;
    }
    @GetMapping("/")
    public String index(@RequestHeader(value = "User-Agent") String userAgent) {
    	visitorService.createVisitor(userAgent);
        return greeting;
    }
    
    @GetMapping("/visitors")
    public String visitorsCount() {
        return String.format("Number of visitors %d", visitorService.countVisitors());
    }
}

Now we’ll create some new classes in the src/main/java/com/example/demo/ directory. Create the class ApplicationConfig in a new ApplicationConfig.java file with the following content:

~/spring-boot-hello-world/src/main/java/com/example/demo/ ApplicationConfig.java
package com.example.demo;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@Configuration
@EnableJpaAuditing
class ApplicationConfig {

}

Create the class Visitor in a new Visitor.java file with the following content:

~/spring-boot-hello-world/src/main/java/com/example/demo/ Visitor.java
package com.example.demo;

import jakarta.persistence.*;

import java.io.Serial;
import java.io.Serializable;
import java.sql.Timestamp;

import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "visitors")
public class Visitor implements Serializable {

    @Serial
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @CreatedDate
    @Column
    private Timestamp timestamp;

    @Column(columnDefinition = "TEXT", nullable = false)
    private String userAgent;

	public void setUserAgent(String userAgent) {
		this.userAgent = userAgent;
	}


}

Create the class VisitorRepository in a new VisitorRepository.java file with the following content:

~/spring-boot-hello-world/src/main/java/com/example/demo/ VisitorRepository.java
package com.example.demo;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;


@Repository
public interface VisitorRepository extends JpaRepository<Visitor, Long> {
}

Finally, create the class VisitorService in a new VisitorService.java file with the following content:

~/spring-boot-hello-world/src/main/java/com/example/demo/ VisitorService.java
package com.example.demo;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
public class VisitorService {
    private final VisitorRepository visitorRepository;

    public VisitorService( VisitorRepository visitorRepository) {
        this.visitorRepository = visitorRepository;
    }

    public Visitor createVisitor(String userAgent) {
    	Visitor visitor = new Visitor();
    	visitor.setUserAgent(userAgent);
    	visitorRepository.save(visitor);
        return visitor;
    }
    
    public Long countVisitors() {
        return visitorRepository.count();
    }

}

Update the rock again

Increment the version in rockcraft.yaml to 0.3 such that the top of the rockcraft.yaml file looks similar to the following:

~/spring-boot-hello-world/rockcraft.yaml
name: spring-boot-hello-world
# see https://documentation.ubuntu.com/rockcraft/en/latest/explanation/bases/
# for more information about bases and using 'bare' bases for chiselled rocks
base: bare # as an alternative, a ubuntu base can be used
build-base: [email protected] # build-base is required when the base is bare
version: '0.3' # just for humans. Semantic versioning is recommended
summary: A summary of your Spring Boot app # 79 char long summary
description: |
    This is spring-boot-hello-world's description. You have a paragraph or two to tell the
    most important story about it. Keep it under 100 words though,
    we live in tweetspace and your description wants to look good in the
    container registries out there.
# the platforms this rock should be built on and run on.
# you can check your architecture with `dpkg --print-architecture`
platforms:
    amd64:
    # arm64:
    # ppc64el:
    # s390x:

Let’s pack and upload the rock:

rockcraft pack
rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \
  oci-archive:spring-boot-hello-world_0.3_$(dpkg --print-architecture).rock \
  docker://localhost:32000/spring-boot-hello-world:0.3

Update the charm again

Change back into the charm directory using cd charm.

The Spring Boot app now requires a database which needs to be declared in the charmcraft.yaml file. Open charmcraft.yaml in a text editor and add the following section to the end of the file:

~/spring-boot-hello-world/charm/charmcraft.yaml
# requires snippet for Spring Boot application with a database

requires:
  postgresql:
    interface: postgresql_client
    optional: false

We can now pack and deploy the new version of the Spring Boot app:

charmcraft pack
juju refresh spring-boot-hello-world \
  --path=./spring-boot-hello-world_$(dpkg --print-architecture).charm \
  --resource app-image=localhost:32000/spring-boot-hello-world:0.3

Now let’s deploy PostgreSQL and integrate it with the Spring Boot app:

juju deploy postgresql-k8s --trust
juju wait-for application postgresql-k8s --timeout 20m
juju integrate spring-boot-hello-world postgresql-k8s

Wait for juju status to show that the App is active again. During this time, the Spring Boot app may enter a blocked state as it waits to become integrated with the PostgreSQL database. Due to the optional: false key in the endpoint definition, the Spring Boot app will not start until the database is ready.

Running curl http://spring-boot-hello-world --resolve spring-boot-hello-world:80:127.0.0.1 should still return the Hi! greeting.

To check the local visitors, use curl http://spring-boot-hello-world/visitors --resolve spring-boot-hello-world:80:127.0.0.1, which should return Number of visitors 1 after the previous request to the root endpoint. This should be incremented each time the root endpoint is requested. If we repeat this process, the output should be as follows:

~$ curl http://spring-boot-hello-world --resolve spring-boot-hello-world:80:127.0.0.1
Hi!
~$ curl http://spring-boot-hello-world/visitors --resolve spring-boot-hello-world:80:127.0.0.1
Number of visitors 2

Tear things down

We’ve reached the end of this tutorial. We went through the entire development process, including:

  • Creating a Spring Boot app

  • Deploying the app locally

  • Packaging the app using Rockcraft

  • Building the app with Ops code using Charmcraft

  • Deploying the app using Juju

  • Exposing the app using an ingress

  • Configuring the app

  • Integrating the app with a database

If you’d like to quickly tear things down, start by exiting the Multipass VM:

exit

And then you can proceed with its deletion:

multipass delete charm-dev
multipass purge

If you’d like to manually reset your working environment, you can run the following in the directory ~/spring-boot-hello-world/charm for the tutorial:

charmcraft clean
# Back out to main directory for cleanup
cd ..
rockcraft clean
# exit and delete the charm dir
rm -rf charm
# delete all the files created during the tutorial
rm spring-boot-hello-world_0.1_$(dpkg --print-architecture).rock \
  spring-boot-hello-world_0.2_$(dpkg --print-architecture).rock \
  spring-boot-hello-world_0.3_$(dpkg --print-architecture).rock \
  rockcraft.yaml mvnw.cmd mvnw HELP.md pom.xml
rm -rf src target
# Remove the juju model
juju destroy-model spring-boot-hello-world --destroy-storage --no-prompt --force

You can also clean up your Multipass instance by exiting and deleting it using the same commands as above.

Next steps

By the end of this tutorial you will have built a charm and evolved it in a number of typical ways. But there is a lot more to explore:

If you are wondering…

Visit…

“How do I…?”

How-to guides, Ops | How-to guides

“How do I debug?”

Charm debugging tools

“How do I get in touch?”

Matrix channel

“What is…?”

Reference, Ops | Reference, Juju | Reference

“Why…?”, “So what?”

Ops | Explanation, Juju | Explanation