Build your first charm
Go from zero to a deployed, tested Juju charm in a single sitting. This tutorial takes about 15 minutes.
What you will learn
By the end of this tutorial you will have:
- Installed Cantrip and connected it to an LLM provider.
- Described a workload and reviewed the agent's design proposal.
- Watched the agent build, deploy, and test a charm autonomously.
- Verified that the charm is running and observable.
This tutorial uses a simple Flask application as the example workload. Cantrip handles it via Path A (12-factor PaaS), which is the fastest charm path. The same workflow applies to more complex workloads — only the design proposal and build time differ.
Prerequisites
Before you begin, make sure you have:
- Ubuntu 22.04+ (or another Linux distribution with snap support).
- Juju 3.x installed and bootstrapped with at least one controller. If you need to set up Juju, see the Juju getting started guide.
- Python 3.12+ and uv installed.
Install uv with:
curl -LsSf https://astral.sh/uv/install.sh | sh - A Gemini API key (free tier is sufficient). Get one at Google AI Studio. You can also use Claude or a local inference snap — see Choose an LLM provider.
1. Install Cantrip
Install Cantrip as a standalone tool using uv:
$ uv tool install juju-cantrip
Verify the installation:
$ cantrip --version
cantrip 0.x.y
2. Set up your API key
Export your Gemini API key so Cantrip can access the model:
$ export GEMINI_API_KEY="your-key-here"
Add this to your shell profile (~/.bashrc or
~/.zshrc) so it persists across sessions.
For Claude, Fireworks, OpenRouter, OpenCode Zen, an OpenAI-compatible
endpoint, or a local inference snap, see
Choose an LLM provider — environment variables
for the matching export command. The
CLI reference lists every operational
variable Cantrip reads.
3. Create a project directory
Cantrip works inside a charm project directory. Create one and move into it:
$ mkdir my-flask-charm && cd my-flask-charm
4. Launch Cantrip
Start Cantrip in the default TUI mode:
$ cantrip
The terminal UI opens with three panels: a task checklist on the left, Juju status in the centre, and a chat panel on the right. Your cursor is in the chat input area, ready for your first message.
If you prefer a browser interface, launch with
cantrip --web instead. For a minimal command-line REPL,
use cantrip --no-tui.
5. Describe your workload
In the chat panel, type a description of what you want to charm:
Build a charm for a Flask hello-world application
Press Enter. The agent begins its research phase — you will see tasks appearing in the checklist on the left as it searches the web, checks Charmhub for existing charms, and analyses the workload.
6. Review the design proposal
After a few moments the agent presents a structured design proposal in the chat. It looks something like this:
Substrate: Kubernetes
Charm path: A (12-Factor PaaS)
Base: paas-charm (Flask)
Integrations: ingress, COS
Config: app-port (default 8080)
Observability: ops-tracing, Prometheus metrics
The agent waits for your confirmation. Read through the proposal. If it looks right, reply:
Looks good, go ahead
If you want changes, tell the agent what to adjust — for example, "add a database integration" or "use machine substrate instead".
7. Watch the build
Once confirmed, the agent enters the autonomous work loop. Watch the task checklist update as subagents work through the plan:
- Scaffolding the charm project with
charmcraft init - Writing an
AGENTS.md(with aCLAUDE.mdsymlink) so any IDE agent that opens the charm later — Cursor, Codex, Claude Code, Cantrip itself — inherits the same project context - Generating a
rockcraft.yamlfor the OCI image - Writing integration tests
- Writing the charm code
- Packing and deploying
- Running acceptance tests
You do not need to type anything during this phase. The agent handles errors automatically — if a build fails, it diagnoses the issue using traces and logs and creates a fix task.
For a simple Flask charm, the build typically completes in under two minutes. More complex workloads (Path B or C) take longer.
8. Verify the deployment
When all tasks show a tick in the checklist, your charm is deployed and tested. Verify it with Juju:
$ juju status
Model Controller Cloud/Region Version ...
default lxd localhost/localhost 3.x.y ...
App Version Status Scale Charm Channel Rev
my-flask-charm active 1 my-flask-charm 0
Unit Workload Agent Machine ...
my-flask-charm/0* active idle 0 ...
The charm should be in active/idle status. Observability
is automatically wired up via COS — traces and metrics are
flowing.
Next steps
You have built and deployed your first charm with Cantrip. From here:
- Improve an existing charm — point Cantrip at a charm you have already written.
- Choose an LLM provider — try Claude or local inference snaps.
- The three charm paths — understand how Cantrip handles different workload types.
- How Cantrip works — learn about the two-loop architecture and subagents.
- CLI reference — explore all available flags and commands.