Skip to main content

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

CommandWhat It Does
docker logs my-containerDump all logs since container creation
docker logs -f my-containerFollow logs in real-time (like tail -f)
docker logs --tail 100 my-containerShow only the last 100 lines
docker logs --since 5m my-containerShow logs from the last 5 minutes
docker logs --timestamps my-containerPrefix each line with a timestamp
docker logs -f --since 10m my-containerCombine: follow from 10 minutes ago
Empty Logs?

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
Production Safety

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

SymptomFirst CommandFollow-Up
Container not runningdocker ps -a + docker logsCheck exit code and last error
Restart loopdocker inspect (state + restart count)Check logs + restart policy
Application errordocker logs -fdocker exec to check env and config
Missing datadocker inspect (mounts)docker exec ls to verify paths
Network unreachabledocker inspect (networks)docker exec to test connectivity
OOM killeddocker inspect (OOMKilled flag)Review memory limits

Key Takeaways

  • Logs first: docker logs is always your starting point. Check what the application reported.
  • Inspect for metadata: docker inspect reveals state, exit codes, mounts, network, and restart counts -- often faster than guessing.
  • Exec for investigation: docker exec gets 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