SSH Into a Running Environment: Debugging Containers in the Cloud Like Local
GuideSeptember 24, 202610 min read

SSH Into a Running Environment: Debugging Containers in the Cloud Like Local

Why "Just Read the Logs" Isn't Always Enough

Logs tell you what your application decided to print. They do not tell you what files actually exist in /app right now, what a curl from inside the container to a dependency actually returns, what environment variables are actually set (as opposed to what you think you configured), or what the process table looks like when something is stuck. For that class of debugging, you need to actually be inside the container — the cloud equivalent of SSH-ing into a box and poking around, except the "box" is a Kubernetes pod that didn't exist five minutes ago and won't exist tomorrow.

Bunnyshell gives you two commands for this: bns exec for one-off commands, and bns ssh for a persistent interactive session. Both authenticate through your Bunnyshell API token — no SSH keypairs, no direct kubectl cluster credentials required, which matters for letting developers debug without handing out raw cluster access.

bns exec: One Command, One Answer

The simplest and most common use case is running a single diagnostic command and getting its output back, no interactive session required:

Bash
1$ bns exec YLGZVV3NGQ -- sh -c "whoami; hostname; env | grep DEMO_SECRET"
2
3root
4web-54c4996887-6lcmz
5DEMO_SECRET=hello-from-bunnyshell-content-demo

This is real output from a live deployed component — note the pod name web-54c4996887-6lcmz is the actual live Kubernetes pod identifier, and the environment variable is exactly what was configured in the environment's bunnyshell.yaml. This single command answers three questions at once (what user is this running as, what's this pod actually called, is this environment variable set correctly) without needing to open an interactive session at all.

The component ID (YLGZVV3NGQ above) can be found via bns components list --environment <ENV_ID>, or, once set as your CLI context, omitted entirely on subsequent commands.

For specifying a particular pod or container explicitly — necessary when a pod runs more than one container:

Bash
bns exec YLGZVV3NGQ --pod my-pod-abc -c api -- /bin/bash

bns ssh: When You Need More Than One Command

For exploratory debugging — where you don't yet know what you're looking for and expect to run several commands in sequence, checking a file, then a process, then a network call — an interactive shell is the better tool:

Bash
bns ssh --component YLGZVV3NGQ

This drops you into a real shell session inside the running container, with a TTY allocated the same way a traditional SSH session would behave. From there you can cat config files, check running processes, test a database connection with the actual client the app uses, or reproduce a bug step by step in the exact runtime environment where it's happening — not a local approximation of it.

Bash
bns ssh --component YLGZVV3NGQ --container api --shell /bin/bash

The --shell flag matters for minimal container images (Alpine-based images, for instance, often only have /bin/sh, not /bin/bash) — specifying the wrong shell for the image just fails to start a session, so it's worth checking the base image before assuming bash is available.

Piping a Local Script Into a Remote Container

A pattern worth knowing explicitly: you can pipe a local file's contents into bns exec via stdin, which runs it as a script inside the container without a separate "copy the file in, then execute it" step:

Bash
bns exec YLGZVV3NGQ --stdin -- python3 < local-debug-script.py

This is particularly useful for a reusable diagnostic script (check disk usage, dump a specific table's row count, verify a third-party API is reachable from inside the environment's network) that you want to run consistently across many environments without maintaining a copy of the script inside every container image.

Viewing Logs Alongside a Live Shell Session

bns exec/ssh and bns logs are complementary, not competing — often the most productive debugging flow is a log stream in one terminal and an interactive shell in another:

Bash
1# Terminal 1
2bns logs --environment 5bDn8MrEgm --tail 10 --follow
3
4# Terminal 2
5bns ssh --component YLGZVV3NGQ

Real log output from the environment used throughout this pillar's examples:

Bash
1$ bns logs --environment 5bDn8MrEgm --tail 10
2
3[web/web-54c4996887-6lcmz/web] 2026/09/24 09:03:05 [notice] 1#1: start worker process 34
4[web/web-54c4996887-6lcmz/web] 2026/09/24 09:03:05 [notice] 1#1: start worker process 35

Watching logs update in real time while you trigger a specific action inside a shell session in the second terminal turns "reproduce, then guess what happened" into "reproduce, and watch exactly what happened as it happens."

When SSH Access Should Be Restricted

Interactive shell access to a running environment is powerful, and that power has an obvious flip side: it's a genuine capability that should be governed by the same permission model as any other environment action. Bunnyshell's environment-level permissions (view, manage, operate) apply to exec/ssh the same as they apply to deploy/stop/start — a PAT scoped to view-only on a given environment cannot open a shell into it, the same way it cannot trigger a deploy. See the Personal Access Token scoping guidance if you're setting up access for a team where not everyone should have shell-level debugging capability on every environment.

Practical Debugging Recipes

"Is this environment variable actually set correctly?"

Bash
bns exec <component-id> -- env | grep MY_VAR

"Can this service actually reach its database from inside the network?"

Bash
bns exec <component-id> -- sh -c "nc -zv db-host 5432"

"What does the filesystem actually look like right now?"

Bash
bns exec <component-id> -- ls -la /app

"I need to poke around and I don't know what I'm looking for yet."

Bash
bns ssh --component <component-id>

bns exec/ssh vs. Direct kubectl exec

For teams whose platform engineers already have direct cluster access, a fair question is why not just use kubectl exec directly rather than a wrapper command. The two are functionally similar for someone who already has a correctly configured kubeconfig and direct cluster credentials — but that "already has" is exactly the gap bns exec/ssh closes. A developer debugging their own PR's environment should not need direct Kubernetes cluster credentials handed to them just to check a log file or run a health check — that's a much broader grant of access than the task requires, and it means onboarding a new developer to be able to debug their own environments involves provisioning cluster access, not just a Bunnyshell account. bns exec/ssh authenticate through the same API token used for every other CLI operation, scoped by the same environment-level permissions — so a developer can be granted exactly "debug access to environments in this project," without a parallel, broader Kubernetes RBAC conversation happening alongside it.

For platform engineers who genuinely do need raw kubectl access (debugging cluster-level issues that aren't specific to one environment, for instance), that access can coexist — bns exec/ssh is not a replacement for kubectl at the platform-engineering layer, it's the appropriate tool for the much more common case of an individual developer debugging their own environment.

Common Errors and How to Fix Them

"Error: multiple containers found, please specify one with --container" — the pod has more than one container (a sidecar, an init container that's still around, a service mesh proxy) and the command can't infer which one you mean. Run bns components show --id <component-id> first to see the container names, then re-run with --container <name>.

Session hangs with no output after bns ssh — usually means the wrong --shell was specified for a minimal image. Alpine-based images typically only ship /bin/sh; specifying --shell /bin/bash against one hangs waiting for a binary that doesn't exist. Default to /bin/sh unless you've confirmed the image includes bash.

"Error: pod not found" — the environment may have redeployed or scaled between when you last checked its component list and when you ran the exec/ssh command, meaning the specific pod name has changed. Re-run bns components list --environment <ENV_ID> to get the current component ID rather than reusing a stale one from an earlier session.

Auditing Who Accessed What, and When

Because shell-level access into a running environment is a genuinely sensitive capability, it's worth knowing that Bunnyshell logs exec/ssh session initiation as part of the same event/audit trail as other environment actions (deploys, stops, starts) — visible through the organization's event history. For a team in a regulated environment where "who had shell access to this environment, and when" needs to be answerable during an audit, this existing event log is the place to look, rather than needing to bolt on a separate session-recording system for this specific purpose.

SSH/Exec as Part of a Remote Development Workflow

Debugging via exec/ssh is closely related to, but distinct from, full remote development (syncing local code changes into a running remote container for active development, rather than just diagnosing an already-deployed build). Bunnyshell's remote-development command builds on the same underlying container-access mechanism as exec/ssh but adds file-sync on top, for the workflow of actively writing code against a cloud environment rather than only debugging one that's already deployed:

Bash
bns remote-development --component <component-id> --local-path ./src --remote-path /app/src

This is worth knowing about even if your primary use case is debugging rather than active remote development, because it means the same component you're SSHing into for a one-off diagnostic check can, without any additional environment configuration, become a live remote development target later — the underlying access mechanism doesn't change between "debug this once" and "actively develop against this for the rest of the day," only which CLI command you reach for. See the broader Remote Development pillar for the full workflow beyond debugging.

A Debugging Session Worked Through Start to Finish

To make the earlier recipes concrete, here's a realistic sequence for tracking down a "the API returns 500 for this one specific request" bug that only reproduces in the deployed environment, not locally. First, confirm the process is actually running and hasn't crash-looped:

Bash
bns exec <api-component-id> -- ps aux

Then check whether the specific dependency the failing request touches is reachable:

Bash
bns exec <api-component-id> -- sh -c "nc -zv redis-host 6379"

If that's healthy, drop into an interactive shell to reproduce the exact failing call directly against the running process, with real request/response visibility that a black-box HTTP client from outside the container wouldn't give you:

Bash
bns ssh --component <api-component-id>
$ curl -v localhost:8080/api/orders/12345

This sequence — process check, dependency check, then interactive reproduction — moves from cheapest/fastest checks to more involved ones, which is generally the right order for debugging anything: rule out the simple explanations before reaching for the more time-consuming interactive session.

Why This Doesn't Undermine Environment Reproducibility

A fair concern about giving developers shell access into a running environment: doesn't this open the door to manual, undocumented changes that silently drift the environment away from its tracked bunnyshell.yaml definition, the exact drift problem the primary-environment blueprint pattern exists to prevent? The honest answer is: it can, if a developer makes a change inside a shell session and never reflects it back into the tracked configuration. The mitigation isn't restricting shell access itself, which is genuinely valuable for debugging — it's cultural and procedural: treating anything discovered or fixed via an interactive shell session as provisional until it's captured in the actual configuration file and redeployed properly. A change made via bns ssh that fixes a problem should be understood as a diagnosis and a temporary patch, not a permanent solution, until the equivalent fix lands in bunnyshell.yaml and the next redeploy confirms it holds without the manual intervention.

A Closing Note on Trust and Convenience

The tension running through this guide — powerful, convenient shell access versus the discipline needed to keep environments reproducible and secure — isn't unique to Bunnyshell; it's the same tension every team navigates with kubectl exec or a traditional SSH bastion. What's different here is that the convenience (no separate cluster credentials, authentication through the same token as everything else) and the governance (permission-scoped PATs, an existing audit trail) come from the same system, rather than convenience being a workaround that has to fight against a separately-bolted-on security layer. That alignment is what makes it reasonable to actually use this capability freely for debugging, rather than treating it as an emergency-only escape hatch nobody's quite comfortable reaching for.

For the broader command surface exec/ssh belong to, see Managing Environment Lifecycle via CLI. For the secrets you might be verifying during a debugging session, see Encrypting and Decrypting Secrets via CLI.

Checklist

  • You know the component ID (or have set CLI context) before reaching for exec/ssh
  • --container is specified explicitly for any pod running more than one container
  • Interactive ssh is reserved for genuinely exploratory debugging; one-off checks use exec for speed and scriptability
  • Shell-level access is scoped through PAT permissions appropriately for who on the team actually needs it
✦✦

Debug your environments like they're right there on your laptop.

14-day full-feature trial. No credit card required. Pay-as-you-go from $0.007/min per environment.

Frequently Asked Questions

What is the difference between bns exec and bns ssh?

bns exec runs a specific command in a container and returns its output — good for one-off checks and scripting. bns ssh opens a persistent interactive shell session in the container, closer to a traditional SSH login, good for exploratory debugging where you will run several commands in sequence.

Do I need SSH keys configured to use bns ssh?

No. Authentication is handled through your Bunnyshell API token, not SSH key pairs — bns ssh establishes the session through Bunnyshell's API and your cluster credentials, not a traditional SSH keypair-based connection.

Can I run a local script inside the remote container?

Yes, by piping it through stdin: bns exec <component-id> --stdin -- python3 < local-script.py runs the local file's contents as a script inside the container without needing to first copy the file into the pod.

What happens if a pod has multiple containers?

You need to specify which one with --container (or -c), since exec/ssh cannot infer which container you mean when a pod runs more than one. Omitting it when there are multiple containers will hang or prompt for a selection depending on your terminal mode.

Is this the same as using kubectl exec directly?

Functionally similar, but bns exec/ssh authenticate through your Bunnyshell token rather than requiring you to have kubectl configured with direct cluster credentials — useful for developers who should be able to debug an environment without being handed raw Kubernetes cluster access.