In this blog post Connecting to a Running Container Terminal in Docker and Kubernetes we will walk through practical, safe ways to open a shell inside a live container, plus the technology that makes it work.
Connecting to a running container terminal sounds simple—type a command, get a shell—but it’s a powerful operation. Used well, it speeds up diagnostics, incident response, and one-off maintenance. Used poorly, it can introduce risk. In this article we start high level, then move into hands-on steps for Docker and Kubernetes, with tips for security and troubleshooting.
What “connecting to a container” really means
When you “exec” into a container, you’re starting a new process inside the container’s existing namespaces and cgroups. You share the container’s filesystem, network, PID space (depending on isolation), and environment. You’re not creating a new container; you’re adding a process to one that already exists.
Under the hood, the container runtime (e.g., runc via containerd or dockerd) launches your command in the same isolation context as the container’s main process. A pseudo-TTY (PTY) may be allocated so your terminal can handle interactive input, colors, and line editing. Your stdin, stdout, and stderr are streamed over the Docker or Kubernetes API to your terminal.
When should you do this?
- Live diagnostics: check config files, environment, network connectivity, or running processes.
- One-off maintenance: run a database migration, clear a cache, or inspect a queue.
- Forensics during incidents: gather data quickly without rebuilding images.
Prefer declarative fixes (build a new image, update code, roll out a change) for lasting solutions. Use interactive shells for short-lived troubleshooting and capture what you did.
Key concepts to keep in mind
- TTY vs non-TTY:
-t
requests a terminal (PTY).-i
keeps stdin open. Use-it
for interactive shells; drop them for non-interactive commands. - Exec vs attach: exec starts a new process (safe for debugging). attach binds to the main process’s stdio (risk of sending signals or breaking the app).
- Least privilege: avoid running as
root
unless required. Respect containersecurityContext
and company policy. - Ephemeral tools: production images are often minimal. Use ephemeral containers (Kubernetes) or mount a toolbox image rather than installing packages into a running container.
Connecting to a running Docker container
1) Find the container
docker ps
# or include all, then filter
# docker ps -a --filter "name=myapp"
2) Open a shell
If the container has Bash:
docker exec -it <container-id-or-name> bash
If Bash is missing, try sh
:
docker exec -it <container-id-or-name> sh
3) Run a one-off command (non-interactive)
docker exec <container> env | sort
4) Run as a specific user
Match the app’s user when possible:
docker exec -it -u 1000:1000 <container> bash
# or a named user if present
# docker exec -it -u appuser <container> sh
5) Windows containers
docker exec -it <container> powershell
# or
docker exec -it <container> cmd
6) Avoid docker attach (unless you know why)
docker attach <container>
connects to PID 1’s stdio. If the app reads stdin or handles signals, you can disrupt it. Prefer docker exec
.
If there is no shell
- Use the app’s existing binary for diagnostics, e.g.,
docker exec <c> node -e 'console.log(process.env)'
. - Copy tools in temporarily (acceptable in dev/test, avoid in prod):
docker cp busybox sh <c>:/bin/
(not ideal security-wise). - Rebuild your image with a minimal shell for support environments, or move to Kubernetes ephemeral containers (next section).
Connecting to a running Kubernetes container
1) Locate the pod
kubectl get pods -n my-namespace
2) Exec into the pod
Single-container pod with Bash:
kubectl exec -n my-namespace -it pod/myapp-abc123 -- bash
Use sh
if Bash is missing:
kubectl exec -n my-namespace -it pod/myapp-abc123 -- sh
Multi-container pod (specify container):
kubectl exec -n my-namespace -it pod/myapp-abc123 -c web -- sh
3) Run non-interactive commands
kubectl exec -n my-namespace pod/myapp-abc123 -- ls -al /app
4) When there’s no shell or you need tooling: ephemeral containers
Ephemeral containers let you inject a temporary helper into a live pod without restarting it. They’re perfect for minimal images.
# Add an ephemeral BusyBox and open a shell targeting the 'web' container's namespaces
kubectl debug -n my-namespace -it pod/myapp-abc123 \
--image=busybox:1.36 --target=web -- sh
--target
shares namespaces with the specified container.- Ephemeral containers are for debugging; they persist in the pod status until the pod is recreated or deleted.
- RBAC must allow
pods/ephemeralcontainers
.
5) Permissions and users
- If the pod disallows root (
runAsNonRoot
), you might not be able to escalate inside the main container. Use an ephemeral container image that has the tools you need, respecting policy. - Cluster auth can limit who can exec. Ensure your role includes
pods/exec
on the namespace.
6) Helpful Kubernetes alternatives
- Logs instead of shell:
kubectl logs -n my-namespace pod/myapp-abc123 -c web
- Port forward for local testing:
kubectl port-forward pod/myapp-abc123 8080:80
- Copy files:
kubectl cp -n my-namespace pod/myapp-abc123:/var/log/app.log ./
How it works under the hood
Both Docker and Kubernetes use a container runtime to start new processes inside existing namespaces:
- The API server (Docker Engine or Kubernetes API) receives your
exec
request. - The node agent (dockerd or kubelet via containerd/CRI) starts the new process in the container’s mount, PID, and network namespaces.
- A PTY is allocated when you ask for
-t
, and your terminal’s stdin/stdout are multiplexed over the API connection.
This design keeps the container’s isolation while letting you interact as if you were “inside” the environment.
Security and operational guardrails
- Restrict who can exec: use RBAC roles limited to specific namespaces or apps.
- Audit: enable Kubernetes audit logs and Docker daemon logs to track exec sessions.
- Use minimal, read-only images in production; rely on ephemeral containers for tooling.
- Document commands in an incident or runbook so actions are reproducible.
- Prefer non-interactive commands for automation; reserve shells for humans.
- Set session limits: consider time-bound access via break-glass procedures.
Handy one-liners
# Docker: open a login shell as the container user
USER=$(docker inspect --format '{{.Config.User}}' <container>); \
[ -z "$USER" ] && USER=0; docker exec -it -u "$USER" <container> sh
# Kubernetes: pick the first Ready pod in a deployment and exec
P=$(kubectl get pods -n myns -l app=myapp -o jsonpath='{.items[?(@.status.phase=="Running")].metadata.name}' | awk '{print $1}'); \
kubectl exec -n myns -it pod/$P -- sh
# Kubernetes: add an ephemeral toolbox with DNS and TLS tools
kubectl debug -n myns -it pod/$P --image=alpine:3.20 --target=web -- sh -c "apk add --no-cache bind-tools curl openssl; sh"
Best practices summary
- Default to
exec
, notattach
. - Use
-it
for interactive sessions; omit for one-off commands. - In Kubernetes, prefer ephemeral containers for tooling over mutating the app container.
- Apply least-privilege and audit all interactive access.
- Translate successful ad-hoc fixes into code and images.
Wrap-up
Connecting to a running container terminal is a practical superpower when you understand what’s happening under the hood and apply the right guardrails. With docker exec
, kubectl exec
, and ephemeral containers, you can diagnose issues quickly without compromising safety.
If you want help standardising safe operational practices around containers in your organisation, CloudProinc.com.au can assist with policy, tooling, and training.
Discover more from CPI Consulting
Subscribe to get the latest posts sent to your email.