Juju Operations Assistant
Operate a live Juju environment: deploy charms, manage models, configure applications, integrate services, debug issues, and manage the full lifecycle.
Live Environment
Current controller: !juju whoami --format=json 2>/dev/null || echo '{"error": "not connected"}'
Controllers: !juju controllers --format=tabular 2>/dev/null | head -15 || echo "No controllers found"
Active model status: !juju status --format=short 2>/dev/null | head -20 || echo "No active model"
Session Setup
When starting work that requires a Juju model, always:
- Choose the right controller for the substrate:
juju switch concierge-lxd # Machine charms juju switch concierge-k8s # Kubernetes charms
If the controllers were not set up by Concierge, they will have different names, and you will need to refer to the "cloud" field in juju status.
Create a dedicated model with a recognisable, unique name:
juju add-model claude-<descriptive-id>Immediately tell the user how to observe the model — print this right after model creation, and again at the end of the session:
Model created: <controller>:<model> To watch status: juju status -m <controller>:<model> --watch 2s To stream logs: juju debug-log -m <controller>:<model> --tail
Core Workflows
Deploy a Charm
# From Charmhub
juju deploy postgresql-k8s --channel 14/stable --trust
juju deploy ubuntu --base ubuntu@24.04 -n 3
# From a local .charm file (pack first)
charmcraft pack
charmcraft analyse ./*.charm
juju deploy ./<name>.charm --resource <name>=<image>
# With configuration
juju deploy mysql-k8s --channel 8.0/stable --config profile=testing
# With constraints
juju deploy app-k8s --constraints "mem=4G cores=2"
# With storage
juju deploy postgresql-k8s --storage pgdata=kubernetes,10G
Key flags:
--trust— grant the charm access to cloud credentials (required by many charms)--channel— specify the risk channel (e.g.,14/stable,latest/edge)--base— target OS for machine charms (e.g.,ubuntu@22.04)-n— number of units to deploy--config— pass key=value or a YAML config file--constraints— resource requirements (mem, cores, root-disk, virt-type, arch)--resource— attach OCI images or files--storage— attach storage (format:<store>=<pool>,<size>)
Configure Applications
# View current config
juju config <app>
# Set values
juju config <app> key1=value1 key2=value2
# Set from YAML file
juju config <app> --file config.yaml
# Reset to default
juju config <app> --reset key1,key2
# View a single key
juju config <app> key1
Integrate (Relate) Applications
# Auto-match endpoints
juju integrate <app1> <app2>
# Explicit endpoints
juju integrate <app1>:endpoint1 <app2>:endpoint2
# Remove a relation
juju remove-relation <app1> <app2>
# View relations
juju status --relations
Common integration patterns:
- Database:
juju integrate myapp postgresql-k8s - Ingress:
juju integrate myapp traefik-k8s - TLS:
juju integrate myapp self-signed-certificates - Logging:
juju integrate myapp grafana-agent-k8s
Scale Applications
# Kubernetes — set target scale
juju scale-application <app> <count>
# Machine — add units
juju add-unit <app> -n <count>
# Machine — remove specific unit
juju remove-unit <app>/<unit-number>
Run Actions
# List available actions
juju actions <app>
# Run an action (waits for result)
juju run <app>/<unit> <action-name>
# Run with parameters
juju run <app>/<unit> <action-name> param1=value1 param2=value2
# Run with timeout
juju run <app>/<unit> <action-name> --wait=5m
# List past operations
juju operations --actions
Expose / Network Access
# Expose to all (use with caution)
juju expose <app>
# Expose to specific CIDRs
juju expose <app> --to-cidrs 10.0.0.0/24
# Expose to specific spaces
juju expose <app> --to-spaces public
# Remove exposure
juju unexpose <app>
Status and Monitoring
Reading Status
# Human-readable
juju status
juju status --relations # Include relation info
juju status --watch 2s # Continuous refresh, not available in Juju 4, use the `watch` CLI tool instead
juju status --format json # Machine-parseable
# Specific application
juju show-application <app>
juju show-unit <app>/<unit>
Status interpretation:
- active — charm is healthy (the text part of the status may indicate degraded performance)
- waiting/idle — charm is waiting for something (often a relation)
- blocked/idle — charm needs user intervention (read the status message!)
- maintenance — charm is performing an operation
- error — a hook failed; check logs and use
juju resolved
Streaming Logs
# Tail all logs
juju debug-log --tail
# Filter by unit
juju debug-log --tail --include unit-<app>-<n>
# Filter by log level
juju debug-log --tail --level ERROR
# Include specific modules
juju debug-log --tail --include <app>
# Set logging verbosity on the model
juju model-config logging-config="<root>=WARNING;unit=DEBUG"
SSH and Exec
# SSH into a unit
juju ssh <app>/<unit>
# Run a command on a unit
juju ssh <app>/<unit> -- <command>
# SSH into a K8s workload container
juju ssh --container <container-name> <app>/<unit>
# Copy files
juju scp <local-path> <app>/<unit>:<remote-path>
juju scp <app>/<unit>:<remote-path> <local-path>
# Run command across all units
juju exec --all -- <command>
juju exec --application <app> -- <command>
Model Management
# List models
juju models
# Create a model
juju add-model <name>
juju add-model <name> --config logging-config="<root>=INFO"
# Switch between controllers/models
juju switch <controller>
juju switch <controller>:<model>
# View model config
juju model-config
juju model-config <key>
juju model-config <key>=<value>
# Set model-wide constraints
juju set-model-constraints cores=2 mem=4G
Secrets Management
# Create a secret
juju add-secret <name> key1=value1 key2=value2
# Grant a secret to an application
juju grant-secret <name> <app>
# Revoke access
juju revoke-secret <name> <app>
# List secrets
juju secrets
# Show secret details (not content)
juju show-secret <name>
# Show secret content
juju show-secret <name> --reveal
# Update a secret
juju update-secret <name> key1=newvalue
# Remove a secret
juju remove-secret <name>
Debugging Workflows
When a unit is in error state:
Read the status message — it often tells you what is wrong:
juju status --format json | python3 -c " import sys, json s = json.load(sys.stdin) for app, info in s.get('applications', {}).items(): for unit, u in info.get('units', {}).items(): ws = u.get('workload-status', {}) if ws.get('current') in ('error', 'blocked'): print(f'{unit}: {ws[\"current\"]} — {ws.get(\"message\", \"\")}') "Check the logs for the failing unit:
juju debug-log --tail --level ERROR --include unit-<app>-<n>Retry the failed hook once the issue is understood:
juju resolved <app>/<unit>For deeper inspection, SSH in:
juju ssh <app>/<unit> # Machine charm juju ssh --container <container> <app>/<unit> # K8s workloadInteractive hook debugging (drops you into a tmux session when hook fires):
juju debug-hooks <app>/<unit>
For comprehensive troubleshooting, see references/troubleshooting.md
Cleanup and Teardown
Always clean up models when done. Follow this order:
# Remove specific applications first (optional, for partial cleanup)
juju remove-application <app> --destroy-storage
# Destroy the entire model (preferred for full cleanup)
juju destroy-model <controller>:<model> --destroy-storage -y
# Force-destroy if stuck (last resort)
juju destroy-model <controller>:<model> --destroy-storage --force --no-wait -y
Safety rules:
- Never destroy a controller unless the user explicitly asks
- Never destroy a model that you did not create unless the user explicitly asks
- Never remove packages installed by Concierge unless explicitly asked
- Always use
--destroy-storageto avoid orphaned volumes - Always use
-m <controller>:<model>for destructive commands to avoid accidents
Working with Both Substrates
The environment may have one or both of Kubernetes and LXD controllers. Choose based on the charm type. If the environment was set up by Concierge, then the controllers will be named concierge-k8s and concierge-lxd, and the model in both cases will be named testing.
| Kubernetes | Machine/LXD | |
|---|---|---|
| Charm type | K8s charms (sidecar pattern) | Machine charms |
| Workload | OCI images via Pebble | Debs, snaps, binaries |
| Scale | juju scale-application |
juju add-unit / juju remove-unit |
| SSH to workload | juju ssh --container <c> <u> |
juju ssh <u> (needs a key configured in Juju 4 and above) |
| Storage | kubernetes pool |
lxd pool |
Best Practices
- Always use
-m controller:modelfor commands that modify state — avoids acting on the wrong model - Use
--format jsonwhen parsing output programmatically — human-readable format changes between versions - Check
juju statusafter deploy/integrate/config changes — don't assume success - Use
--trustwhen deploying charms that need cloud API access - Set
logging-configearly:juju model-config logging-config="<root>=WARNING;unit=DEBUG" - Wait for operations — use
watch -n2 juju statusor poll withjuju status --format jsonrather than guessing timing - Read charm documentation before deploying:
juju info <charm>shows metadata, channels, and supported bases
Additional References
When you need detailed information:
- Full command reference by category: See references/command-reference.md
- Troubleshooting guide: See references/troubleshooting.md
- Cross-model relations and offers: See references/cross-model-relations.md
- Storage and secrets deep-dive: See references/storage-and-secrets.md
- Juju documentation: https://documentation.ubuntu.com/juju/latest/
- Juju CLI reference: https://documentation.ubuntu.com/juju/latest/reference/juju-cli/
- CLI command output format: can be found in the Jubilant code