The Problem
A container is restarting every 30 seconds. The health check is failing. The application throws errors but you cannot tell if it is a code bug, missing environment variable, or connectivity issue. Docker logs are your first and fastest debugging tool — if you know how to extract the right information.
Viewing Container Logs
docker logs my-container # All logs
docker logs -f my-container # Follow real-time
docker logs --tail 100 my-container # Last 100 lines
docker logs -t my-container # With timestamps
docker logs --since 30m my-container # Last 30 minutes
docker logs --since 1h my-container # Last hour
docker logs --since 2024-01-15T10:00:00 --until 2024-01-15T10:30:00 my-container
docker logs -f --tail 50 my-container # Follow from last 50
Docker Compose logs
docker compose logs # All services
docker compose logs api # Specific service
docker compose logs -f api worker # Follow multiple
docker compose logs --tail 50 -t # Last 50 with timestamps
docker compose logs --since 30m api # Recent logs
Filtering and Searching
docker logs my-container 2>&1 | grep -i "error"
docker logs my-container 2>&1 | grep -B3 -A3 "Exception"
docker logs my-container 2>&1 | grep -c "ERROR"
docker logs my-container 2>&1 | grep "ERROR" | sort -u
docker logs my-container 2>&1 | jq -r 'select(.level == "error")'
docker logs -f my-container 2>&1 | grep --line-buffered "ERROR"
Note: 2>&1 combines stdout and stderr for complete search coverage.
Debugging Container Startup Issues
Container exits immediately
# Check exit code
docker inspect my-container --format='{{.State.ExitCode}}'
docker inspect my-container --format='{{json .State}}' | jq
docker logs my-container
# Common exit codes:
# 0 — Normal exit
# 1 — Application error
# 137 — SIGKILL (OOM killed or docker kill)
# 139 — SIGSEGV (segmentation fault)
# 143 — SIGTERM (graceful shutdown)
Container keeps restarting
docker inspect my-container --format='{{.RestartCount}}'
docker inspect my-container --format='{{.State.StartedAt}}'
docker logs --since 2m my-container
docker events --filter container=my-container --since 10m
Exec into running container
docker exec -it my-container /bin/bash
docker exec -it my-container /bin/sh # Alpine images
docker exec my-container env | sort # Check env vars
docker exec my-container curl -s http://db-host:5432
docker exec my-container nslookup redis-service
docker exec my-container cat /app/config/application.yml
Resource inspection
docker stats my-container # Live stats
docker stats --no-stream my-container # One-shot
docker inspect my-container --format='{{.State.OOMKilled}}'
Log Drivers
Check current driver
docker info --format '{{.LoggingDriver}}'
docker inspect my-container --format='{{.HostConfig.LogConfig.Type}}'
JSON file with rotation
docker run -d \
--log-driver json-file \
--log-opt max-size=10m \
--log-opt max-file=5 \
--name api myapp:latest
Docker Compose configuration
services:
api:
image: myapp:latest
logging:
driver: json-file
options:
max-size: "10m"
max-file: "5"
tag: "{{.Name}}/{{.ID}}"
Other log drivers
# AWS CloudWatch
logging:
driver: awslogs
options:
awslogs-region: us-east-1
awslogs-group: /ecs/myapp
awslogs-stream-prefix: api
# Fluentd
logging:
driver: fluentd
options:
fluentd-address: localhost:24224
tag: docker.myapp
Structured JSON Logging
docker logs my-container 2>&1 | jq '.'
docker logs my-container 2>&1 | jq 'select(.level == "error")'
docker logs my-container 2>&1 | jq -r '[.timestamp, .level, .message] | @tsv'
# Count errors per endpoint
docker logs my-container 2>&1 | \
jq -r 'select(.level == "error") | .path' | sort | uniq -c | sort -rn
# Filter 500s from last hour
docker logs --since 1h my-container 2>&1 | jq 'select(.status_code >= 500)'
Log Management for Production
Global rotation in daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
Finding and managing log files
docker inspect my-container --format='{{.LogPath}}'
du -sh /var/lib/docker/containers/*/
find /var/lib/docker/containers -name "*-json.log" -size +100M
# Emergency truncation
truncate -s 0 $(docker inspect my-container --format='{{.LogPath}}')
Docker Compose Debugging Workflow
# 1. Check container status
docker compose ps
# 2. Check logs
docker compose logs --tail 50 api
# 3. Test connectivity
docker compose exec api curl -s http://postgres:5432
# 4. Verify environment
docker compose exec api env | grep DB_
# 5. View resolved config
docker compose config
# 6. Restart service
docker compose restart api
# 7. Rebuild and restart
docker compose up -d --build api
Common Mistakes
2>&1 when piping — Docker sends stdout and stderr separately. Without combining, grep misses error output.max-size and max-file.docker logs with non-json-file drivers — docker logs only works with json-file and journald. With syslog or fluentd, logs go directly to those systems.docker events — Events show kills, restarts, and OOM kills — information not in application logs.Quick Reference
| Task | Command | |
|---|---|---|
| View logs | <code class="inline-code">docker logs container</code> | |
| Follow logs | <code class="inline-code">docker logs -f container</code> | |
| Last N lines | <code class="inline-code">docker logs --tail 100 container</code> | |
| Since time | <code class="inline-code">docker logs --since 30m container</code> | |
| With timestamps | <code class="inline-code">docker logs -t container</code> | |
| Search logs | <code class="inline-code">docker logs container 2>&1 \ | grep "ERROR"</code> |
| Exit code | <code class="inline-code">docker inspect container --format='{{.State.ExitCode}}'</code> | |
| OOM check | <code class="inline-code">docker inspect container --format='{{.State.OOMKilled}}'</code> | |
| Log file path | <code class="inline-code">docker inspect container --format='{{.LogPath}}'</code> | |
| Resource stats | <code class="inline-code">docker stats container</code> | |
| Compose logs | <code class="inline-code">docker compose logs -f service</code> | |
| Truncate logs | <code class="inline-code">truncate -s 0 $(docker inspect container --format='{{.LogPath}}')</code> |
Summary
Docker logs are your fastest path to diagnosing container issues. Use --since and --tail to narrow the window, pipe through grep and jq for filtering, and always check exit codes and OOM status. Configure log rotation on every production deployment and use structured JSON logging for efficient parsing.
---
Frequently Asked Questions
How do I view Docker container logs?
Use docker logs <container-name> for all logs or docker logs --tail 100 <container-name> for the last 100 lines. Add -f to follow logs in real-time (like tail -f). For containers that have crashed, logs are still available until the container is removed. Use --since 1h to filter by time.
Why are my Docker container logs empty?
The application inside the container must write to stdout/stderr for Docker to capture logs. If the application writes to a file instead, Docker logs will be empty. Fix this by configuring your application to log to stdout, or use a symlink like ln -sf /dev/stdout /var/log/app.log in your Dockerfile. Some official images do this by default.
How do I configure Docker log rotation?
Set log rotation in Docker's daemon.json with "log-opts": {"max-size": "10m", "max-file": "3"} to limit each container to 3 files of 10MB. Without rotation, logs grow unbounded and can fill your disk. You can also set these per-container with --log-opt flags in docker run or in your compose file under logging:.
What is the difference between docker logs and docker attach?
docker logs shows historical output (stdout/stderr) captured by the logging driver without connecting to the container. docker attach connects your terminal to the container's running process and shows live output, but stdin is also connected which means Ctrl+C can stop the container. Use docker logs -f for safe live tailing without risk of stopping the container.
How do I debug a container that keeps crashing?
Use docker logs <container> to see error output before the crash. If the container exits too fast, override the entrypoint with docker run --entrypoint /bin/sh <image> to get a shell for investigation. Check exit codes with docker inspect --format='{{.State.ExitCode}}' <container> — exit code 137 means OOM killed, 1 means application error.
---
Related Resources
- YAML/JSON Converter — YAML to JSON converter for Docker Compose files
- Kubernetes Pod Troubleshooting — K8s troubleshooting after containerization
- Docker Production Best Practices — Security and optimization for production containers
- Docker Compose Environment Variables — Debugging environment variable issues
- Monitoring and Alerting in Production — Production logging and alerting strategies