Esc

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:

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: