Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes

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

Sponsored

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 logs for what happened (output and errors). Use docker exec for 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 ps is not installed, use docker exec web_app ls /proc to 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 exec to check if the app is actually running (ps aux).
  • Try docker logs --details container_name for 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 exec and docker logs is 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 exec on a stopped container — the container must be running. Use docker logs or docker start first.
  • Forgetting the -it flags 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 attach to debug expecting docker logs behavior — attach sends signals and can stop the container unexpectedly.

Variations

  1. Use docker cp to copy files from the container to your host for deeper analysis, especially when cat inside the container is insufficient.
  2. Use docker inspect to view container metadata (IP, mounts, env, etc.) without executing commands inside the container.
  3. Consider docker run --entrypoint /bin/sh to 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 logs shows missing database env vars, docker exec confirms empty DB_HOST.
  • A background worker container appears idle — docker exec to check /proc and running processes reveals the worker crashed silently.
  • A multi-service Docker Compose app fails to communicate — docker exec into the service to curl the other service's endpoint and test connectivity.

Key takeaways

  • Always start debugging with docker logs to see the application's stdout/stderr history.
  • Use docker exec with a one-off command for quick checks, or docker exec -it for a full shell session.
  • Alpine-based images lack bash — use /bin/sh or check the base image.
  • Changes made with docker exec are lost when the container restarts — use volumes for persistent changes.
  • docker exec requires a running container; for stopped containers, use docker logs or commit and recreate.
  • Combining docker logs and docker exec solves 80% of runtime debugging without rebuilding.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.