Logs, Exec, and Inspect
When a container misbehaves, you have three essential tools: logs to see what the application said, inspect to see the container's configuration and state, and exec to get inside and investigate. This lesson covers all three and shows you how to combine them into an effective debugging workflow.
Viewing Logs (docker logs)
Docker captures everything your container writes to STDOUT and STDERR. This is your first stop when something goes wrong:
docker logs my-container
Useful Flags
| Command | What It Does |
|---|---|
docker logs my-container | Dump all logs since container creation |
docker logs -f my-container | Follow logs in real-time (like tail -f) |
docker logs --tail 100 my-container | Show only the last 100 lines |
docker logs --since 5m my-container | Show logs from the last 5 minutes |
docker logs --timestamps my-container | Prefix each line with a timestamp |
docker logs -f --since 10m my-container | Combine: follow from 10 minutes ago |
If docker logs returns nothing, your application is probably writing to a file instead of STDOUT. Docker only captures output written to the standard streams. Reconfigure your app to log to STDOUT/STDERR, or mount the log file as a volume.
Shell Access (docker exec)
docker exec runs a new process inside a running container. Use it to inspect files, check environment variables, or test network connectivity:
Interactive Shell
# For Debian/Ubuntu-based images
docker exec -it my-container bash
# For Alpine-based images (no bash)
docker exec -it my-container sh
The flags: -i keeps STDIN open (interactive), -t allocates a pseudo-terminal (so you get a proper shell prompt).
One-Off Commands
You do not need a shell for quick checks:
# Check environment variables
docker exec my-container env
# List files in the app directory
docker exec my-container ls -lah /app
# Check running processes
docker exec my-container ps aux
# Test DNS resolution
docker exec my-container cat /etc/resolv.conf
# Test connectivity to another service
docker exec my-container wget -qO- http://database:5432
In production, prefer read-only commands first (env, ps, cat). Avoid installing packages or modifying files inside a running container -- those changes are lost when the container restarts and can mask the real problem.
Metadata Inspection (docker inspect)
docker inspect returns a large JSON object with every detail about a container: its state, configuration, network settings, mounts, and more. Use the -f flag (Go template) to extract specific fields:
Most Useful Fields
# Container state (running, exited, etc.)
docker inspect -f '{{.State.Status}}' my-container
# Exit code (0 = clean, 137 = OOM killed, etc.)
docker inspect -f '{{.State.ExitCode}}' my-container
# Was it killed by OOM?
docker inspect -f '{{.State.OOMKilled}}' my-container
# IP address
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' my-container
# Mounted volumes
docker inspect -f '{{json .Mounts}}' my-container | python3 -m json.tool
# Restart policy and count
docker inspect -f '{{json .HostConfig.RestartPolicy}}' my-container
docker inspect -f '{{.RestartCount}}' my-container
# Health status (if healthcheck configured)
docker inspect -f '{{json .State.Health}}' my-container
The Debugging Workflow
When a container is misbehaving, follow this order:
flowchart TD
A["1. Check Status<br/>docker ps -a"] --> B{"Running?"}
B -->|"No - Exited"| C["2. Read Logs<br/>docker logs --tail 200 app"]
B -->|"Yes - But broken"| D["2. Follow Logs<br/>docker logs -f app"]
C --> E["3. Inspect State<br/>docker inspect app"]
D --> E
E --> F{"Root cause clear?"}
F -->|"No"| G["4. Exec Into Container<br/>docker exec -it app sh"]
F -->|"Yes"| H["5. Fix and Redeploy"]
G --> H
style A fill:#e3f2fd,stroke:#1565c0
style H fill:#e8f5e9,stroke:#2e7d32
Example: Container Keeps Restarting
# 1. Check status -- is it restarting?
docker ps -a
# 2. Read the last error messages
docker logs --tail 200 my-container
# 3. Check state details
docker inspect -f '{{json .State}}' my-container
docker inspect -f '{{.RestartCount}}' my-container
# 4. Common root causes:
# - Bad environment variable → docker exec my-container env
# - Dependency unreachable → docker exec my-container ping database
# - Wrong entrypoint → docker inspect -f '{{.Config.Entrypoint}}' my-container
Example: Data Missing in Container
# Check what volumes are mounted and where
docker inspect -f '{{json .Mounts}}' my-container
# Verify the files exist at the expected path
docker exec my-container ls -lah /data
Common causes: wrong mount target path, missing volume, or permission mismatch.
Example: Service Not Reachable
# Check what network the container is on
docker inspect -f '{{json .NetworkSettings.Networks}}' my-container
# Check if the app is actually listening
docker exec my-container ss -tulpen
# Check recent logs for bind errors
docker logs --tail 100 my-container
Quick Reference
| Symptom | First Command | Follow-Up |
|---|---|---|
| Container not running | docker ps -a + docker logs | Check exit code and last error |
| Restart loop | docker inspect (state + restart count) | Check logs + restart policy |
| Application error | docker logs -f | docker exec to check env and config |
| Missing data | docker inspect (mounts) | docker exec ls to verify paths |
| Network unreachable | docker inspect (networks) | docker exec to test connectivity |
| OOM killed | docker inspect (OOMKilled flag) | Review memory limits |
Key Takeaways
- Logs first:
docker logsis always your starting point. Check what the application reported. - Inspect for metadata:
docker inspectreveals state, exit codes, mounts, network, and restart counts -- often faster than guessing. - Exec for investigation:
docker execgets you inside the container for deeper checks, but prefer read-only commands in production. - Always collect evidence before restarting -- once you remove a container, its logs and state are gone.
What's Next
- Continue to Healthchecks and Restart Policies to monitor container health automatically.