Introduction
When you manage multiple Docker containers, log viewing quickly turns into a nightmare:
- Switching between separate
docker logs -fterminals - Historical logs lost after container restart
- No cross-container keyword search
- No timeline, visualization, or alerting
Loki is Grafana Labs’ log aggregation system, designed specifically for Kubernetes and Docker. It uses the same label mechanism as Prometheus. The key difference from Elasticsearch is that Loki indexes labels, not log content — making storage costs extremely low while keeping queries fast.
This guide walks you through setting up Loki + Promtail + Grafana on your VPS for a complete Docker container log management solution.
Architecture Overview
Log data flow:
Container stdout/stderr → Promtail (log collector) → Loki (storage + indexing) → Grafana (query + visualization)
- Promtail: Runs on each Docker host, reads container log files, adds labels (container name, image name, etc.), pushes to Loki
- Loki: Log storage backend, indexes logs by labels, supports LogQL query language
- Grafana: Log query and visualization dashboard, supports alerting
All components are part of the Grafana ecosystem — 100% open-source, free, and resource-efficient.
Prerequisites
Assuming you already have Docker and Docker Compose installed on your VPS. Minimum requirements:
- 1 CPU core / 1 GB RAM (handles 20+ containers’ logs easily)
- 20 GB disk (for log storage; adjust retention period as needed)
- Docker Compose v2+
Step 1: Create the Docker Compose Deployment
We’ll manage Loki, Promtail, and Grafana together in one Compose file.
# docker-compose.yml
version: "3.8"
networks:
loki:
services:
# ── Loki Log Storage ──
loki:
image: grafana/loki:2.9.6
container_name: loki
ports:
- "3100:3100"
volumes:
- ./loki-config.yaml:/etc/loki/local-config.yaml
- ./loki-data:/loki
command: -config.file=/etc/loki/local-config.yaml
networks:
- loki
restart: unless-stopped
# ── Promtail Log Collector ──
promtail:
image: grafana/promtail:2.9.6
container_name: promtail
volumes:
- ./promtail-config.yaml:/etc/promtail/config.yaml
- /var/log:/var/log
- /var/lib/docker/containers:/var/lib/docker/containers:ro
command: -config.file=/etc/promtail/config.yaml
networks:
- loki
restart: unless-stopped
depends_on:
- loki
# ── Grafana Visualization ──
grafana:
image: grafana/grafana:10.4.1
container_name: grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin # Change on first login!
- GF_INSTALL_PLUGINS=
volumes:
- ./grafana-data:/var/lib/grafana
networks:
- loki
restart: unless-stopped
depends_on:
- loki
Step 2: Configure Loki
Create loki-config.yaml:
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
table_manager:
retention_deletes_enabled: false
retention_period: 0s
limits_config:
reject_old_samples: true
reject_old_samples_max_age: 168h
max_query_series: 5000
Tip: For single-VPS setups, filesystem storage is sufficient. If disk space is tight, configure
retention_period— e.g.,720hfor 30 days.
Step 3: Configure Promtail
Create promtail-config.yaml:
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /etc/promtail/positions.yaml # tracks already-read positions
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
pipeline_stages:
- docker: {} # auto-parse Docker log format
- labels:
container_name: "" # container name as label
container_image: "" # image name as label
- regex:
expression: "^(?P<level>\\w+)\\s" # extract log level
- labels:
level: "" # log level as label
relabel_configs:
- source_labels: ["__meta_docker_container_name"]
regex: "/(.*)"
target_label: "container"
replacement: "$1"
This configuration auto-discovers all running Docker containers and forwards their stdout/stderr logs to Loki.
Key point:
/var/run/docker.sockmust be mounted into the Promtail container for auto-discovery to work.
Step 4: Start the Services
# Create data directories
mkdir -p loki-data grafana-data
# Start everything
docker compose up -d
# Verify status
docker compose ps
docker compose logs -f promtail # confirm log collection works
On first startup, navigate to http://your-vps-ip:3000 in your browser and log in with admin / the password you set.
Step 5: Add Loki as a Grafana Data Source
- In Grafana, go to Connections → Data sources
- Click Add data source, select Loki
- Set URL to
http://loki:3100 - Click Save & Test — you should see “Data source connected and labels found.”
If you see labels like container_name, container_image, and level, everything is working.
Step 6: Explore Logs with LogQL
Grafana’s Explore panel (🔍 icon) is the primary interface for querying logs.
Basic Queries
# View all logs
{container_name=~".+"}
# Filter by specific container
{container_name="nginx"}
# Multiple containers at once
{container_name=~"nginx|traefik|app-web"}
# Filter by image name
{container_image=~"nginx:.*"}
Content-aware Search
# Search for errors
{container_name=~".+"} |= "error"
# Exclude health check noise
{container_name="nginx"} != "/health"
# Regex match
{container_name="app"} |~ "Exception|Timeout|Fatal"
# Parse JSON logs and filter by field
{container_name="api"} | json | status >= 400
Metric-style Queries (PromQL-like)
# Logs per minute per container
rate({container_name=~".+"}[1m]) by (container_name)
# Error rate trend
sum by (container_name) (rate({container_name=~".+"} |= "error"[5m]))
Step 7: Set Up Log Alerts
Loki log alerts work similarly to Prometheus metric alerts. Suppose you want to be notified when any container produces too many 5xx errors:
- Go to Grafana Alerting → Alert rules → New alert rule
- Select the Loki data source
- Enter the query:
sum by (container_name) (
count_over_time(
{container_name=~".+"} | json | status >= 500
[5m]
)
) > 10
- Set evaluation interval to
1m, conditionWHEN avg() OF query IS ABOVE 10 - Configure notification channels (Email, Telegram, Slack, Webhook, etc.)
Now you’ll be alerted whenever any container exceeds 10 5xx errors in a 5-minute window.
Step 8: Add a Domain with Your Reverse Proxy
If you already run Traefik or Nginx, add a subdomain for Grafana:
# Traefik labels example
services:
grafana:
labels:
- "traefik.enable=true"
- "traefik.http.routers.grafana.rule=Host(`logs.yourdomain.com`)"
- "traefik.http.routers.grafana.entrypoints=https"
- "traefik.http.services.grafana.loadbalancer.server.port=3000"
Optimization & Maintenance Tips
Log Retention
Loki doesn’t have built-in log deletion, but you can control retention via config. Or use a cron job to clean old data:
# Keep only the last 7 days of log data
find /opt/loki/loki-data -mtime +7 -delete
Resource Usage Reference
| Containers | Log Volume | Loki Memory | Promtail Memory |
|---|---|---|---|
| 5 | Light | ~150 MB | ~30 MB |
| 20 | Medium | ~400 MB | ~80 MB |
| 50 | Heavy | ~1 GB | ~150 MB |
Security Hardening
- Set a strong Grafana password via
GF_SECURITY_ADMIN_PASSWORD - Always use HTTPS in production (via reverse proxy)
- Promtail’s HTTP port (9080) doesn’t need to be public-facing
- Consider enabling OAuth/OIDC for Grafana authentication
Alternative Solutions Comparison
| Feature | Loki + Promtail | Elasticsearch + Filebeat | Self-hosted Graylog |
|---|---|---|---|
| Resource Usage | ⭐ Very low | 🟡 High | 🟡 Medium |
| Setup Complexity | ⭐ Minimal (2 YAMLs) | 🔴 Complex (JVM tuning) | 🟡 Moderate |
| Query Language | LogQL (PromQL-like) | Query DSL (JSON) | Lucene queries |
| Grafana Integration | ✅ Perfect | ✅ Supported | ⚠️ Plugin needed |
| VPS Suitability | ⭐ Highly recommended | ❌ Too resource-heavy | 🟡 Feasible |
Summary
With Loki + Promtail + Grafana, you can set up a complete Docker container log management system in minutes:
- Loki handles log storage and indexing with minimal resource usage
- Promtail auto-discovers all Docker containers and collects their logs
- Grafana provides powerful LogQL queries and visualization dashboards
- Built-in alerting for anomalies and error spikes
Best of all — everything is free and open-source. No Elastic license restrictions, no log-volume-based billing, no user limits. For lightweight VPS deployments, this is the most cost-effective log management solution available today.
Next steps: Try linking Loki with Prometheus metrics data — view metrics and logs side by side in the same Grafana dashboard for full observability.
