A shell for the containers that have no shell.
Kubernetes charm workloads run a rock with no shell. When something
breaks, juju ssh --container=workload drops you nowhere
useful. borescope gives you a prompt that feels like bash, talking
to the container's Pebble through the Juju authority you already have.
A debug shell that meets the rock where it is
No shell in the image, no kubectl, no cluster-admin. Just the
prompt you wish juju ssh gave you.
Works on rocks with no shell at all
The workload rock has no bash, no coreutils, often
nothing. borescope drives the workload's Pebble through the charm
container, which always has a shell, so the familiar file commands
work even when the image is empty.
pebble:/# ls /var/log/myapp
app.log errors.log audit/
pebble:/# grep -c ERROR errors.log
42
Pebble's vocabulary, first-class
services, logs, plan,
checks, changes, notices:
Pebble's own commands are built in, not hidden behind a
pebble prefix. Start and stop services, follow logs,
read the merged plan.
pebble:/# services
pebble:/# logs --follow myapp
pebble:/# plan
Your Juju authority, nothing more
borescope reaches the container with juju ssh, exactly
as you would by hand. If you can juju ssh to the unit,
borescope works. If you can't, it fails the same way. No kubeconfig.
Feels like bash
A real REPL with cd and pwd, path-aware
tab-completion, history scoped per unit, single pipes
(logs | grep error), and the familiar file commands.
Muscle memory mostly just works.
An exec escape hatch
borescope ships a deliberately small command set. Anything it
doesn't cover, exec <tool> runs directly in the
container with your current working directory.
One-shot and snapshot modes
Script it with --command "services": runs once and exits.
File a bug with --snapshot: services, plan, checks,
notices, and recent logs as a single stable JSON document.
How it works
Three thin, independently-testable layers.
-
Discover the right Pebble
borescope turns
myapp/0into a target: it confirms the unit, reads the charm'smetadata.yamlfor workload container names, and checks the container is alive, all over your Juju model access. -
Open a transport
The default backend reaches the workload's Pebble socket through the charm container via
juju ssh. When the socket is directly reachable, inside a charm or a local Pebble, it uses the realops.pebble.ClientHTTP API instead. -
Drop you at a prompt
A small REPL maps familiar commands onto Pebble's files and exec APIs. Pebble's own vocabulary is first-class.
execcovers the rest.
Built on the Canonical ecosystem
borescope is a thin layer over tools you already run.
- Jujuorchestration & authority
- Pebbleprocess supervisor
- OpsPebble API
- RockcraftOCI images
- Canonical K8sKubernetes
Get started in seconds
Install from a checkout while v1 is under active development. Stable snap and PyPI releases are coming.
# Install from a checkout
$ uv tool install . # or: pipx install .
# Open a prompt on a unit's workload container
$ borescope myapp/0
pebble:/# services
pebble:/# logs --follow myapp
pebble:/# tail -f /var/log/myapp/error.log
pebble:/# exec ps aux
pebble:/# exit
# One-shot, for scripts
$ borescope myapp/0 --command "services"
# Dump container state as JSON
$ borescope myapp/0 --snapshot
borescope picks up your current Juju controller and model, just like
the juju CLI. Point it at a different model with
--model, choose a workload with --container,
or run it inside a charm container with --here.
Several ways to drive it
Interactive REPL
A bash-like prompt with history, tab-completion, and pipes against a single unit.
borescope myapp/0
One-shot
Run a single command and exit, for scripts, aliases, and CI.
borescope myapp/0 -c "plan"
Snapshot
A stable JSON dump of container state, ideal for bug reports and tooling.
borescope myapp/0 --snapshot