Debug Containers with Docker
Master container debugging using 'docker exec' for live inspection and 'docker logs' for output analysis. Learn step-by-step techniques, compare options, and troubleshoot common issues.
Focus: debug containers with docker exec and logs
Your container application is running, but the output is wrong, a connection is refused, or it crashes at startup. Without a debugger pre-installed and no SSH access, you feel blind. This is the moment you need docker exec and docker logs — your two indispensable tools for peeking inside a running container and understanding what went wrong.
The problem this lesson solves
Containers are designed to be ephemeral and isolated. Traditional debugging methods like ssh into a server or attaching a local debugger often don’t work inside a container. You cannot easily install tools mid-flight because the filesystem is read-only or the image is minimal.
Common pain points:
- You see a Connection refused error but don't know which process failed.
- Your application produces no visible output, or the logs vanish after a restart.
- You need to inspect environment variables, configuration files, or running processes without rebuilding the image.
Without docker exec and docker logs, you would need to rebuild the image with debug tools each time, restart the container, and hope to reproduce the issue. That is slow and inefficient. This lesson teaches you how to debug in situ — live, without restarts.
Core concept / mental model
Think of a container as a sealed box with only one window: the log stream. docker logs shows you everything the process inside printed to stdout/stderr. It is your passive observation tool.
docker exec, on the other hand, lets you open a secret hatch into the running container. You can run any command inside it, just as if you were logged in via SSH. This is your active diagnostic tool.
Pro tip: Use
docker logsfor what happened (output and errors). Usedocker execfor why it happened (inspect state, env vars, files).
Key definitions
| Term | Meaning |
|---|---|
docker logs [container] |
Fetches the stdout/stderr history of the container’s main process (PID 1). |
docker exec [container] [command] |
Runs a new process inside a running container. |
docker exec -it |
Runs interactively with a terminal — like a live shell session. |
How it works step by step
Step 1: Identify the container
First, list your running containers to find the container ID or name:
docker ps
Output example:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a1b2c3d4e5f6 my-web-app "python app.py" 5 minutes ago Up 5 minutes 0.0.0.0:8080->8000/tcp web_app
Note the name (web_app) or the ID (a1b2c3d4e5f6).
Step 2: Read the logs
Start with docker logs to see the application output:
docker logs web_app
If the container was recent, you might see:
* Serving Flask app 'app'
* Debug mode: off
* Running on http://0.0.0.0:8000
No errors? Good. But if you see ModuleNotFoundError: No module named 'flask' or Address already in use, you already have a lead.
Step 3: Use docker exec to inspect the container
Run a shell inside the container to look around:
docker exec -it web_app /bin/bash
If the container uses Alpine Linux (no bash), use sh:
docker exec -it web_app /bin/sh
Once inside, you can:
- List processes: ps aux
- Check environment variables: env
- Read config files: cat /etc/app/config.yaml
- Test networking: curl localhost:8000
Pro tip: If
psis not installed, usedocker exec web_app ls /procto see running process IDs.
Step 4: Run a one-off command without opening a shell
You don’t always need an interactive session. For quick checks:
docker exec web_app env
Output:
PATH=/usr/local/bin:/usr/bin:/bin
PYTHONUNBUFFERED=1
DB_HOST=postgres
Or test connectivity:
docker exec web_app curl -s http://localhost:8000/health
Hands-on walkthrough
Example 1: Debugging a failing Flask app
Suppose your container is running but returning HTTP 500s. Check logs first:
docker logs my-flask-app
You find:
[2025-03-15 10:00:00] ERROR in app: Exception on /api/data [GET]
Traceback (most recent call last):
...
File "/app/app.py", line 42, in get_data
result = db.query("SELECT * FROM items")
AttributeError: 'NoneType' object has no attribute 'query'
Now use docker exec to inspect the environment:
docker exec my-flask-app env | grep DB
Output:
DB_HOST=
DB_USER=
DB_PASS=
Problem: Environment variables are empty. The application is trying to connect to a database with no credentials.
Example 2: Checking if a service is listening
docker exec my-flask-app netstat -tulpn
If netstat is missing, use:
docker exec my-flask-app cat /proc/net/tcp
Example 3: Following logs in real-time
docker logs -f --tail 20 web_app
This shows the last 20 lines and then streams new output — perfect for watching error patterns during request bursts.
Compare options / when to choose what
| Tool | Use Case | Interactive? | Impact on Container |
|---|---|---|---|
docker logs |
View stdout/stderr output, check error history | No | None — read-only |
docker exec <cmd> |
Run a one-off command quickly (e.g., env, ls) |
No | Adds a new process briefly |
docker exec -it <shell> |
Full interactive investigation (shell, editor) | Yes | Spawns a shell process |
docker attach |
Attach to container’s main process stdin/stdout | Yes | Can send signals (Ctrl+C may kill) |
| Docker Dashboard (GUI) | Fast visual inspect for Docker Desktop users | No | None |
When to choose what:
- Use docker logs as your first step — always.
- Use docker exec with a one-off command for targeted checks.
- Use docker exec -it for deep investigation when you suspect environment or file issues.
- Use docker attach only when you need to interact with the main process (rare).
Troubleshooting & edge cases
“Container is not running”
If the container exited, docker exec won't work. Use docker logs to see the crash output. If you need to inspect a stopped container, create a new image with debug tools or use docker commit to snapshot and then run it interactively.
“exec: 'bash': executable file not found in $PATH”
Alpine-based images don’t include bash. Use sh instead:
docker exec -it container_name /bin/sh
“The input device is not a TTY”
This error occurs when you use -it in non-interactive contexts (e.g., CI/CD). Remove -t and use -i only:
docker exec -i container_name command
“Cannot enable tty mode on non tty input”
Your shell is not a real terminal (e.g., in a script or some editors). Run without -t:
docker exec -i container_name /bin/sh
Logs are empty
- Ensure the application writes to stdout/stderr, not to files. Many apps log only to files by default.
- Use
docker execto check if the app is actually running (ps aux). - Try
docker logs --details container_namefor extra info.
“exec failed: operation not permitted”
The container may be running with --read-only filesystem or restricted capabilities. Try running commands that don’t write to the filesystem (e.g., env, curl).
What you learned & what's next
You now have two powerful debugging techniques:
- docker logs — passive, read-only view of your application’s output.
- docker exec — active, live inspection of the container’s state, environment, processes, and files.
These tools connect directly to your learning objectives: you can explain the core idea behind debug containers using docker exec and logs, and you have just completed a practical exercise debugging a common AttributeError scenario.
Next step: Now that you can debug a single container, the next lesson covers debugging multi-container applications with Docker Compose, where docker-compose logs and docker-compose exec follow the same pattern but across services. This foundation will also prepare you for more advanced debugging with distributed tracing in orchestrated environments.
Key takeaway: Debug containers with
docker execanddocker logsis your first line of defense. Always check logs first, then exec into the container to explore. This combination will solve 80% of runtime issues without rebuilding a single image.
Practice recap
Start a simple Python container (docker run -d --name debug-demo python:3.10-slim sleep 3600). Use docker logs to see it's running. Then docker exec -it debug-demo /bin/sh and run ps aux, env, and cat /etc/os-release. Finally, force a crash by running exit inside — then practice reading logs from the stopped container with docker logs debug-demo.
Common mistakes
- Using
docker execon a stopped container — the container must be running. Usedocker logsordocker startfirst. - Forgetting the
-itflags when you want an interactive shell, leading to 'input device is not a TTY' errors. - Assuming every container has bash — Alpine images use
/bin/sh. Always check the base image. - Trying to edit files inside a container with
docker exec— changes are lost on restart. Use volumes for persistence. - Using
docker attachto debug expectingdocker logsbehavior — attach sends signals and can stop the container unexpectedly.
Variations
- Use
docker cpto copy files from the container to your host for deeper analysis, especially whencatinside the container is insufficient. - Use
docker inspectto view container metadata (IP, mounts, env, etc.) without executing commands inside the container. - Consider
docker run --entrypoint /bin/shto start a new container from the same image with a shell, useful when the original container crashes.
Real-world use cases
- A production Flask API returns 500 errors —
docker logsshows missing database env vars,docker execconfirms emptyDB_HOST. - A background worker container appears idle —
docker execto check/procand running processes reveals the worker crashed silently. - A multi-service Docker Compose app fails to communicate —
docker execinto the service tocurlthe other service's endpoint and test connectivity.
Key takeaways
- Always start debugging with
docker logsto see the application's stdout/stderr history. - Use
docker execwith a one-off command for quick checks, ordocker exec -itfor a full shell session. - Alpine-based images lack bash — use
/bin/shor check the base image. - Changes made with
docker execare lost when the container restarts — use volumes for persistent changes. docker execrequires a running container; for stopped containers, usedocker logsor commit and recreate.- Combining
docker logsanddocker execsolves 80% of runtime debugging without rebuilding.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.