Right, so your pod is running, but it’s doing something deeply weird. Maybe it’s eating CPU like it’s at an all-you-eat-buffer, or perhaps it’s just… not responding. The logs (kubectl logs) are useless, showing nothing but the digital equivalent of crickets chirping. This is where you stop looking at the autopsy report and start talking to the patient. You need to exec into the running container.

Think of kubectl exec as your all-access backstage pass. It lets you open an interactive shell right inside the container, or run any one-off command you can dream up. It’s the difference between reading a log file and actually being there, poking around the filesystem, checking processes, and seeing what the application actually sees. It’s your primary tool for live debugging, and you should be deeply suspicious of anyone who tells you to debug a container without it.

The Basic Incantation

The most common use case is to get an interactive shell. This assumes your container image actually has a shell (/bin/sh or /bin/bash), which most do, but some ultra-minimalist distroless images might not. We’ll get to that heartbreak later.

kubectl exec -it <pod-name> -- /bin/bash

Let’s break this down because the flags matter:

  • -i: Stands for “interactive.” It keeps your standard input open. Without it, your shell would be a sad, non-interactive statue.
  • -t: Allocates a “TTY,” a pseudo-terminal. This makes your shell look like a proper shell with a prompt, line editing, and all the comforts of home. You almost always want both -i and -t together as -it.
  • --: This is a classic command-line convention. It tells kubectl, “Everything after this double-dash is the command for the container, not an option for me.” It’s good practice, especially if your command has flags that might confuse kubectl itself.
  • /bin/bash: The command we want to run inside the container. Swap this for /bin/sh if bash isn’t available.

When You Just Need a Quick Answer

You don’t always need a full-blown shell session. Often, you just need to run a single diagnostic command. exec is perfect for this. Just drop the -it and specify the command.

# Check the running processes. Is your app even running?
kubectl exec <pod-name> -- ps aux

# See what's in the application's current directory
kubectl exec <pod-name> -- ls -la

# Check network connectivity from the container's perspective
kubectl exec <pod-name> -- curl -I http://localhost:8080/health

Here’s where the designers made a choice that’s… well, let’s call it “pragmatic.” If your pod has multiple containers (a primary app and a sidecar, for example), kubectl exec will stubbornly refuse to guess which one you want. It will pick the first container listed in the spec and run with it. This is almost never what you want.

You must specify the target container explicitly using the -c flag. If you don’t, you’ll be debugging your log aggregation sidecar instead of your broken web app, and you will waste 20 minutes of your life you’ll never get back.

# This is how you do it right
kubectl exec -it <pod-name> -c <container-name> -- /bin/bash

For example, if you have a pod with containers named app and istio-proxy, and you need to check the main application:

kubectl exec -it my-fancy-pod-abcd123 -c app -- /bin/bash

The Shell-Less Abyss and What to Do About It

I told you we’d get here. Some images, like those based on scratch or Google’s distroless bases, are built for supreme security and minimal size. They are also built for supreme debugging frustration because they lack a shell. No /bin/sh, no /bin/bash, nothing.

If you try to exec into one of these, kubectl will throw a fit: "OCI runtime exec failed: exec failed: unable to start container process: exec: '/bin/bash': stat /bin/bash: no such file or directory".

All is not lost. You can still run commands, but only if they are statically compiled binaries inside the container. The classic trick is to use whatever tooling you know is in there. For a Go application, you might have luck with the go tool pprof for profiling. Often, your best bet is to include a debugging binary like busybox in your distroless image during development builds, but that’s a story for another section. For now, know that the shell’s absence is a very real wall you will hit.

Best Practices: Don’t Be a Barbarian

  1. You Are a Guest: This is a live production (or production-like) container. You are not root in your own VM. Don’t install vim or curl using apt-get on a live container. You’ll break its immutability, and any changes will be vaporized the moment the pod restarts. Use exec for inspection, not mutation.
  2. It’s a Trap (for Resources): That shell session you left open? It’s holding resources. If you exit out of it, the connection is closed cleanly. If your network connection drops abruptly, the session can sometimes linger. Be mindful.
  3. Security is a Thing: Requiring exec access is a sign that your containers might not be as observable as they should be. While it’s an essential debugging tool, reliance on it in production can be a security smell. Good logging, metrics, and health checks should be your first line of defense.

So go forth, exec with confidence, and figure out what that little container is really up to. Just remember to specify your container name and for the love of Turing, don’t try to install anything.