A natural shell for debugging Juju Kubernetes workload containers.
Kubernetes charm workload containers usually run a rock
with no shell — so when something breaks, juju ssh --container=workload … drops
you nowhere useful. borescope gives you a prompt that feels like bash but talks to
the container's Pebble instead of a real shell:
$ borescope myapp/0
pebble:/# ls /var/log/myapp
pebble:/# tail -f /var/log/myapp/error.log
pebble:/# ps -ef
pebble:/# services
pebble:/# logs --follow myapp
pebble:/# plan
pebble:/# exitNo setup ceremony: borescope picks up your current Juju controller/model and uses your
existing juju authority — if you can juju ssh to the unit, borescope works; if you
can't, it fails the same way.
From the snap store:
sudo snap install borescope
sudo snap connect borescope:juju-client-observe
sudo snap connect borescope:ssh-keysThe two snap connect calls are required today and will go away once
the snap store grants the auto-connect declarations
(#31).
Without them, borescope can't read your ~/.local/share/juju to find
your controller, and juju ssh can't see your SSH keys.
A few things to know about the snap:
- It bundles its own juju (currently
juju/4/stable), so it works even without juju installed on the host. - It reads your
~/.local/share/juju(JUJU_DATA) read-only via thejuju-client-observeinterface, then copies it into a writable per-snap directory at startup. Runjuju login/juju switchoutside borescope — changes made inside a borescope session don't propagate back to the host JUJU_DATA.
Or from PyPI:
uv tool install borescope # or: uvx borescope, pipx install borescopeborescope <unit> # default (first) workload container
borescope <unit> --container=<name> # a specific workload container
borescope --model <model> <unit>
borescope <unit> --command "services" # one-shot, no REPL (for scripts)
borescope <unit> --snapshot # dump container state as JSONFull documentation — a tutorial, how-to guides, and CLI/command reference — is at https://tonyandrewmeyer.github.io/borescope/.
The docs are plain Markdown under docs/src/, built into static
HTML with a small script (no docs framework). To build them locally:
uv run python docs/src/_build.py # or: tox -e docsSee docs/README.md for the authoring rules.
borescope is three thin, independently-testable layers:
- Transport — talks to a Pebble. The primary backend (
CliTransport) reaches the workload's Pebble through the charm container —juju ssh <unit>(the charm container always has a shell) pointed at the workload's socket, which Juju mounts there at/charm/containers/<name>/pebble.socket. This works even against rocks with no shell (the shell lives in the charm container, not the rock) and stays entirely within your Juju authority — nokubectlor cluster-admin. It drivespebblevia shimmer (a drop-inops.pebble.Clientover the Pebble CLI). When the Pebble socket is directly reachable (running inside the charm, or a local Pebble),SocketTransportuses the realops.pebble.ClientHTTP API instead. - Discovery — turns a unit reference into the right Pebble: confirms the unit,
reads the charm's
metadata.yamlfor workload container names, and sanity-checks the container is alive. Everything uses your Juju model access — nokubectl/ cluster-admin. - Shell — a small REPL:
cd/pwd, path-aware tab completion, history, and a minimal command set. Pebble's own vocabulary (services,logs,plan, …) is first-class, not hidden behind apebbleprefix. For anything else,exec <cmd>runs a binary that's already in the container.
borescope is for Kubernetes charms (which run Pebble). Machine charms already have
a real shell and are out of scope. It deliberately ships a minimal command set and
grows on request — if a tool exists in the container, reach it with exec.