Docker Logs: A Complete Beginner's Guide to Container Logging in Your Homelab
Learn how to view, manage, and centralize Docker container logs. Covers docker logs command, log rotation, disk space fixes, and Promtail + Loki + Grafana setup for homelabs.
Author
David Okonkwo
Key Takeaways
- The
docker logscommand is your first tool for debugging any container - learn the flags that matter (-f,--tail,--since,-t). - Docker's default logging driver stores logs without rotation - configure
max-sizeandmax-filebefore logs fill your disk. - You can set log rotation globally in
/etc/docker/daemon.jsonor per-container in Docker Compose files. - When your homelab grows past 10-15 containers, centralized logging with Promtail, Loki, and Grafana becomes essential.
- The
locallogging driver is more efficient thanjson-filefor most homelab setups.
If you've ever SSH'd into your homelab server, noticed your disk was mysteriously full, and traced it back to a container that quietly wrote 50GB of logs - you're not alone. Docker logging is one of those topics that nobody thinks about until something goes wrong. And when it goes wrong at 2 AM, you want to know exactly how to fix it.
This guide walks you through everything you need to know about Docker logs - from reading your first log entry to setting up centralized logging across your entire homelab. We'll go step by step, and I'll explain why each thing matters before we do it.
What Are Docker Container Logs (and Why Should You Care)?
When a Docker container runs, it writes output to two streams: stdout (standard output) and stderr (standard error). Docker captures both of these and stores them as logs. Think of it like a notepad that records everything a program prints to the screen - except Docker keeps the notepad even after the program exits.
Why this matters for your homelab:
- Troubleshooting: When a container stops working, logs tell you why.
- Monitoring: Regular log checks catch problems before they become outages.
- Security: Logs show unauthorized access attempts, failed logins, and suspicious activity.
- Auditing: Need to know when something happened? Logs have timestamps.
Most container images - whether it's Nextcloud, Jellyfin, Pi-hole, or Portainer - write useful information to stdout and stderr. The docker logs command is how you read it.
Viewing Docker Logs - The Basics
Let's start with the most fundamental command. If you only learn one thing from this article, make it this:
docker logs <container_name_or_id>
That's it. Replace <container_name_or_id> with the actual name or ID of your container. Not sure what your containers are called? Run:
docker ps --format "table {{.Names}}\t{{.Status}}"
This shows you a clean list of running containers with their names and status.
Following Logs in Real-Time
Sometimes you need to watch what a container is doing right now - maybe you're debugging a startup issue or watching a migration. The -f (or --follow) flag streams the logs in real-time, just like tail -f works on regular files:
docker logs -f <container_name>
This will keep printing new log entries as they appear. Press Ctrl+C to stop following - it won't stop the container, just the log stream.
Showing Only Recent Logs
When a container has been running for months, docker logs can return thousands of lines. Use --tail to limit the output:
# Show only the last 50 lines
docker logs --tail 50 <container_name>
# Show only the last 10 lines
docker logs --tail 10 <container_name>
This is the command I use most often. It gives you the recent context without drowning in output.
Adding Timestamps
By default, Docker logs don't include timestamps. Add the -t (or --timestamps) flag to see exactly when each entry was written:
docker logs -t --tail 20 <container_name>
Timestamps are invaluable when you're trying to figure out when a problem started. The format is ISO 8601 - for example: 2026-06-13T10:45:32.123456789Z.
Filtering by Time
Need to see what happened in the last hour? Or between two specific times? Docker has you covered:
# Logs from the last 30 minutes
docker logs --since 30m <container_name>
# Logs from the last 2 hours
docker logs --since 2h <container_name>
# Logs up until 1 hour ago
docker logs --until 1h <container_name>
# Logs from a specific timestamp
docker logs --since "2026-06-13T08:00:00" <container_name>
Supports relative durations (30m, 2h, 1d) and RFC 3339 timestamps. This combination is powerful for narrowing down exactly when an issue occurred.
Combining Flags
The real power comes from combining these flags. Here are the combinations I use daily:
# Follow the last 100 lines with timestamps
docker logs -f -t --tail 100 <container_name>
# Last hour of logs, last 200 lines, with timestamps
docker logs --since 1h --tail 200 -t <container_name>
# Search logs for errors (using grep)
docker logs <container_name> 2>&1 | grep -i "error\|fail\|exception"
That last one is a lifesaver. Pipe the output through grep to find error messages, failures, or exceptions in the log stream.
Understanding Docker's Logging Drivers
Here's something most beginner guides skip: Docker doesn't just store logs in a text file. It uses something called a logging driver - a pluggable system that determines how and where logs are stored.
The default logging driver is json-file, which stores logs as JSON files on your host machine. But Docker supports several others, each with different trade-offs.
Logging Driver Comparison
| Driver | Stores Logs | Supports docker logs | Log Rotation | Best For |
|---|---|---|---|---|
json-file |
JSON files on disk | Yes | Manual (must configure) | Development, simple setups |
local |
Compressed binary files | Yes | Built-in (auto-rotation) | Production homelabs |
syslog |
Syslog server | No | Handled by syslog | Existing syslog infrastructure |
journald |
systemd journal | No (use journalctl) | Handled by journald | Systemd-based hosts |
fluentd |
Fluentd collector | No | Handled by Fluentd | Centralized logging setups |
For most homelab setups, I recommend starting with json-file with rotation configured, then moving to local once you're comfortable. The local driver uses less disk space and has built-in rotation - it's the best of both worlds for small-to-medium homelabs.
Here's a quick reference from the Docker logging configuration docs if you want to explore all available drivers.
Setting Up Log Rotation (The Most Important Step)
This is the section that will save your homelab. By default, Docker's json-file driver does not rotate logs. That means every log line your containers write gets stored on disk forever - or until your disk runs out.
With 15-20 containers running, it's easy to accumulate gigabytes of log data without realizing it. Here's how to prevent that.
Global Log Rotation via daemon.json
Edit (or create) the Docker daemon configuration file:
sudo nano /etc/docker/daemon.json
Add the following configuration:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
What this does:
max-size: "10m"- Each log file is capped at 10 megabytes.max-file: "3"- Docker keeps 3 rotated log files, deleting the oldest when the limit is reached.
After saving, restart Docker:
sudo systemctl restart docker
Important: This applies to newly created containers only. Existing containers keep their old logging settings until they're recreated. If you have containers you can't recreate right away, you can set per-container overrides (see the next section).
Per-Container Log Rotation in Docker Compose
For containers managed by Docker Compose, you can set logging options directly in your docker-compose.yml:
services:
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
volumes:
- ./config:/config
- /media:/media
Here's a more complete example with a logging section you can reuse across services using Docker Compose best practices:
services:
portainer:
image: portainer/portainer-ce:latest
container_name: portainer
logging:
driver: json-file
options:
max-size: "5m"
max-file: "2"
ports:
- "9443:9443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- portainer_data:/data
homepage:
image: ghcr.io/gethomepage/homepage:latest
container_name: homepage
logging:
driver: json-file
options:
max-size: "5m"
max-file: "2"
ports:
- "3000:3000"
volumes:
- ./config:/app/config
volumes:
portainer_data:
You can also use YAML anchors to avoid repeating the logging config in every service:
x-logging: &default-logging
driver: json-file
options:
max-size: "10m"
max-file: "3"
services:
jellyfin:
image: jellyfin/jellyfin:latest
logging: *default-logging
portainer:
image: portainer/portainer-ce:latest
logging: *default-logging
This keeps your Compose files clean while ensuring every container has proper log rotation.
Using the Local Logging Driver
If you want more efficient logging with built-in rotation, switch to the local driver. It compresses logs automatically and uses less disk space:
{
"log-driver": "local",
"log-opts": {
"max-size": "10m",
"max-file": "5"
}
}
The local driver stores logs in a binary format, which means smaller files and faster writes. The trade-off is that you can't just cat the log files directly - you need to use docker logs to read them. For most homelab users, this is perfectly fine.
For more details on log driver options, check the Docker json-file driver docs and the Docker local driver docs.
Emergency: What to Do When Docker Logs Fill Your Disk
If you're reading this section with a full disk and a panicking heart - take a breath. Here's the fix.
Step 1: Find Which Containers Are Using the Most Log Space
# Check overall Docker disk usage
docker system df
# Check individual container sizes
docker ps -a --format "table {{.Names}}\t{{.Size}}" | sort -k2 -h
The Size column shows how much space each container is using, including its logs. You can also check the actual log file sizes:
# Find all Docker log files and their sizes
sudo du -sh /var/lib/docker/containers/*/
Step 2: Truncate the Large Log Files
You don't need to stop containers to fix this. Truncate the log file to zero bytes:
# Truncate a specific container's logs
truncate -s 0 $(docker inspect --format='{{.LogPath}}' <container_name>)
# Or truncate ALL container logs at once
sudo truncate -s 0 /var/lib/docker/containers/*/*-json.log
This immediately frees the disk space. The container keeps running and continues logging - you've just cleared the backlog.
Step 3: Apply Log Rotation to Prevent Recurrence
Now configure log rotation so this doesn't happen again (see the "Setting Up Log Rotation" section above). After configuring daemon.json, restart Docker and recreate the problematic containers:
# Restart Docker
sudo systemctl restart docker
# Recreate containers to apply new logging settings
docker compose down && docker compose up -d
This is one of the most common homelab problems, and it's completely preventable with 5 minutes of configuration.
Understanding Where Docker Stores Logs
It helps to know exactly where Docker keeps log files on your system. With the default json-file driver, logs live in:
/var/lib/docker/containers/<container_id>/<container_id>-json.log
The <container_id> is the full 64-character container ID. You can find it with:
# Get the log path for a specific container
docker inspect --format='{{.LogPath}}' <container_name>
# Example output:
# /var/lib/docker/containers/abc123def456.../abc123def456...-json.log
These files are standard JSON, one entry per line. Each line contains a timestamp and the log message. You can read them directly with cat or process them with jq:
# Read raw log file
sudo cat /var/lib/docker/containers/abc123.../abc123...-json.log
# Parse with jq to see just the log messages
sudo cat /var/lib/docker/containers/abc123.../abc123...-json.log | jq -r '.log'
Knowing this path is useful when you need to check disk usage or manually clean up logs without using Docker commands.
Docker Logs After Container Restart
A common question: do logs survive a container restart?
Yes - Docker logs persist across container restarts. The log file lives on the host filesystem, not inside the container. When you docker restart a container, the same log file continues to accumulate entries.
No - logs do NOT survive container removal. When you run docker rm, the log file is deleted. If you need logs to persist after container removal, you need to either:
- Copy them before removal:
docker logs <container> > backup.log - Use a centralized logging system (covered in the next section)
- Mount the log directory to a volume
This is why centralized logging becomes important as your homelab grows - you want a copy of your logs somewhere other than the container's ephemeral storage.
Centralized Logging for Your Homelab: Promtail + Loki + Grafana
When you're running more than about 10-15 containers, checking docker logs on each one becomes impractical. You need a centralized place to search, filter, and visualize all your logs at once.
The homelab-friendly stack for this is Promtail + Loki + Grafana. Think of it as:
- Promtail - the collector that grabs logs from all your containers
- Loki - the storage engine that indexes and stores them
- Grafana - the dashboard where you search and visualize everything
If you've used Prometheus and Grafana for monitoring container health, this stack follows the same pattern - just for logs instead of metrics.
Docker Compose Setup
Here's a complete docker-compose.yml for the logging stack. Create a new directory for it:
mkdir -p ~/logging-stack
cd ~/logging-stack
nano docker-compose.yml
Paste this configuration:
services:
loki:
image: grafana/loki:3.4.2
container_name: loki
ports:
- "3100:3100"
volumes:
- ./loki-config.yml:/etc/loki/local-config.yaml
- loki_data:/loki
logging:
driver: json-file
options:
max-size: "5m"
max-file: "2"
promtail:
image: grafana/promtail:3.4.2
container_name: promtail
volumes:
- ./promtail-config.yml:/etc/promtail/config.yml
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
logging:
driver: json-file
options:
max-size: "5m"
max-file: "2"
grafana:
image: grafana/grafana:11.6.0
container_name: grafana-logging
ports:
- "3001:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=changeme
volumes:
- grafana_data:/var/lib/grafana
logging:
driver: json-file
options:
max-size: "5m"
max-file: "2"
volumes:
loki_data:
grafana_data:
Notice that even the logging stack containers have log rotation configured - practice what you preach.
Loki Configuration
Create the Loki config file:
nano loki-config.yml
auth_enabled: false
server:
http_listen_port: 3100
common:
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
schema_config:
configs:
- from: "2024-01-01"
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
limits_config:
reject_old_samples: true
reject_old_samples_max_age: 168h
Promtail Configuration
Create the Promtail config that scrapes Docker container logs:
nano promtail-config.yml
server:
http_listen_port: 9080
positions:
filename: /tmp/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
- job_name: docker
docker_sd_configs:
- host: unix:///var/run/docker.sock
refresh_interval: 5s
relabel_configs:
- source_labels: ['__meta_docker_container_name']
regex: '/(.*)'
target_label: 'container'
- source_labels: ['__meta_docker_container_log_stream']
target_label: 'logstream'
- source_labels: ['__meta_docker_container_label_com_docker_compose_service']
target_label: 'service'
This configuration automatically discovers all running Docker containers and starts collecting their logs. When you add or remove containers, Promtail picks up the changes within 5 seconds.
Start the Stack
docker compose up -d
After a minute or two, open Grafana at http://your-server-ip:3001 (login with admin / changeme - change this immediately).
Add Loki as a data source:
- Go to Settings > Data Sources > Add data source
- Select Loki
- Set the URL to
http://loki:3100 - Click Save & Test
Now go to Explore and run a query like:
{container=~".+"}
You should see logs from all your containers in one searchable view. From here, you can filter by container name, search for error messages, and set up alerts.
Common Docker Logging Mistakes
After helping dozens of homelabbers with logging issues, here are the mistakes I see most often:
- Not configuring log rotation from day one. Every new homelabber learns this lesson the hard way. Set up
daemon.jsonbefore you create your first container. - Using Docker Compose without logging options. If you're writing
docker-compose.ymlfiles, always include theloggingsection. It takes 4 lines and saves you from disk emergencies. - Restarting containers to "clear" logs. Restarting doesn't clear logs - the log file persists. Use
truncate -s 0instead, or configure proper rotation. - Ignoring stderr output. Many people only look at stdout logs and miss error messages written to stderr. When using
grepto search logs, always include both:docker logs <container> 2>&1 | grep error. - Centralized logging too early (or too late). You don't need Promtail + Loki for 3 containers, but you absolutely need it for 30. The sweet spot is around 10-15 containers when manual checking becomes tedious.
- Not monitoring disk usage. Even with log rotation configured, 3 containers writing 10MB logs with 3 files each = 90MB. With 30 containers, that's 900MB - not huge, but worth monitoring. Set up a simple disk usage alert in Grafana or Uptime Kuma.
Practical Docker Log Commands Cheat Sheet
Here's a quick reference you can bookmark:
# View all logs
docker logs <container>
# Follow logs in real-time
docker logs -f <container>
# Last 50 lines
docker logs --tail 50 <container>
# Last hour with timestamps
docker logs --since 1h -t <container>
# Follow last 100 lines with timestamps
docker logs -f -t --tail 100 <container>
# Search for errors
docker logs <container> 2>&1 | grep -i error
# Check log file size
docker inspect --format='{{.LogPath}}' <container> | xargs du -sh
# Truncate a container's logs
truncate -s 0 $(docker inspect --format='{{.LogPath}}' <container>)
# Truncate ALL container logs
sudo truncate -s 0 /var/lib/docker/containers/*/*-json.log
# Check which containers use the most disk
docker system df -v
What to Learn Next
Now that you understand Docker logging basics, here's a natural progression for your homelab journey:
- Docker health checks - Learn how containers report their health status, and use it to automate restarts when something goes wrong.
- Docker backup strategies - Logs tell you what went wrong. Backups let you recover. Learn the backup patterns that actually work.
- Docker volumes vs bind mounts - Understand where your container data actually lives, including log files.
- Docker for homelabs - A complete beginner's guide if you want to strengthen your Docker fundamentals.
- Homelab backups - Beyond Docker-specific backups, learn how to protect your entire homelab infrastructure.
FAQ
Where are Docker container logs stored on disk?
With the default json-file logging driver, Docker stores container logs at /var/lib/docker/containers/<container_id>/<container_id>-json.log. You can find the exact path for any container with docker inspect --format='{{.LogPath}}' <container_name>.
How do I stop Docker logs from filling up my disk?
Configure log rotation in /etc/docker/daemon.json by setting "log-opts": {"max-size": "10m", "max-file": "3"}. This limits each log file to 10MB and keeps only 3 rotated files. For existing large logs, use truncate -s 0 on the log file to immediately free space.
Do Docker logs survive a container restart?
Yes, Docker logs persist across restarts because the log file lives on the host filesystem, not inside the container. However, logs are deleted when you remove a container with docker rm. To preserve logs, use a centralized logging system or copy them before removal.
What's the difference between the json-file and local logging drivers?
The json-file driver stores logs as plain JSON files - human-readable but larger. The local driver stores compressed binary logs - smaller and faster, but you need docker logs to read them. The local driver also has built-in rotation, making it the better choice for most homelabs.
How do I search Docker logs for specific errors?
Pipe the output through grep: docker logs <container> 2>&1 | grep -i "error". The 2>&1 part combines both stdout and stderr so you don't miss error messages. For more advanced searching across multiple containers, set up a centralized logging stack with Grafana and Loki.
