Debug your first container
From install to poking around a live workload container, in about ten minutes. No prior borescope experience required.
Overview
Kubernetes charm workload containers usually run a
rock with no shell, so
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.
In this tutorial you'll install borescope, connect to a running unit, and use it to read the filesystem, inspect services, follow logs, and run a tool that lives in the container. By the end you'll know your way around a borescope session.
Prerequisites
You need:
- A working
jujuCLI, logged in to a controller, with a Kubernetes model containing at least one deployed application. Ifjuju ssh <unit>works for you, borescope will too. uvorpipxto install borescope.- A checkout of borescope (v1 isn't on PyPI yet).
If you don't have a charm deployed, any Kubernetes charm will do. For example:
juju add-model tutorial
juju deploy grafana-k8s
juju status --watch 2s # wait until the unit is active/idle
Throughout this tutorial, replace myapp/0 with your unit (for example
grafana-k8s/0).
Install borescope
From your checkout:
$ uv tool install . # or: pipx install .
Check it's on your PATH:
$ borescope --version
borescope 0.1.0b2
Connect to a unit
Point borescope at your unit:
$ borescope myapp/0
pebble:/#
borescope picked up your current Juju controller and model, confirmed the unit
exists, read the charm's metadata to find its workload container, and checked
that the container's Pebble answers. The pebble:/# prompt is borescope's, not
a real shell, but it behaves like one. You're in the container's root
directory.
If the application declares more than one workload container, borescope uses the first one declared. Pick a specific one with
--container.
Look around the filesystem
The familiar commands work even though the rock has no shell or coreutils. borescope implements them over Pebble's files API:
pebble:/# pwd
/
pebble:/# ls
bin etc usr var
pebble:/# cd /var/log
pebble:/var/log# ls -l
-rw-r--r-- 1024 2026-05-31 09:14 myapp.log
Tab-completion is path-aware: type cat /var/lo and press Tab to
complete the path. Read a file with cat, or peek at the first or last lines
with head and tail:
pebble:/var/log# tail -n 20 myapp.log
Inspect services and logs
Pebble's own vocabulary is built in, no pebble prefix. List the services
Pebble is supervising:
pebble:/# services
Service Startup Current
myapp enabled active
Follow a service's logs the way you would with tail -f:
pebble:/# logs --follow myapp
Press Ctrl-C to stop following. You can also restrict to the last
N lines with logs -n 50, and pipe the output through grep:
pebble:/# logs -n 200 myapp | grep error
Read the plan and checks
The merged Pebble plan tells you how each service is configured to run:
pebble:/# plan
If the charm defines health checks, list them and their status:
pebble:/# checks
pebble:/# health
When you're chasing a failed deploy, changes and tasks show what Pebble has
recently done and whether any step errored.
Reach for exec
borescope ships a deliberately small command set. For anything else, exec
runs a binary that's already in the container, with your current working
directory:
pebble:/# exec ps aux
pebble:/# exec cat /proc/1/status
If the tool isn't in the rock, exec will tell you it can't find it. That's
expected on a minimal image. (See
Scope and philosophy for why borescope leans on
exec rather than bundling tools.)
Leave the session
Type exit (or press Ctrl-D):
pebble:/# exit
$
Next steps
You've connected to a unit, read its filesystem, inspected services and logs, and run a container tool. From here:
- Connect to a unit: choose a container, model, or controller explicitly.
- Run one command (no REPL): use borescope from scripts.
- Capture a state snapshot: dump container state as JSON for a bug report.
- Command reference: the full built-in command set.