Monitoring Docker Logs with Promtail and Loki

What You’ll Need
Before setting up your log aggregation infrastructure, ensure you have access to the following hosting services and tools:
- A Linux server running Ubuntu 22.04/24.04. You can provision high-performance virtual private servers on a Hetzner VPS or a Contabo VPS .
- Docker and Docker Compose installed on your server.
- DigitalOcean droplets if you prefer an alternative cloud provider.
- A registered domain name pointed to your server IP via Namecheap if you intend to expose Grafana securely over HTTPS.
- Basic familiarity with YAML configuration files and the Linux command-line interface.
Table of Contents
- Understanding the Loki and Promtail Log Aggregation Stack
- Deploying Grafana, Loki, and Promtail with Docker Compose
- Configuring Promtail for Docker Container Scraping
- Querying Docker Logs in Grafana using LogQL
- Setting Up Automated Log Alerts and Workflow Integrations
- Getting Started
Understanding the Loki and Promtail Log Aggregation Stack
Managing log output across dozens of isolated Docker containers quickly becomes unmanageable if you rely solely on docker logs. While traditional ELK (Elasticsearch, Logstash, Kibana) stacks provide powerful search capabilities, they are notoriously resource-intensive, requiring substantial RAM and CPU overhead just to index plain text.
Grafana Loki offers a lightweight, horizontally scalable, multi-tenant log aggregation system designed specifically to be cost-effective and easy to operate. Unlike traditional search engines, Loki does not index the full text of your logs. Instead, it only indexes the metadata labels assigned to your log streams (such as container names, environments, or log levels). This architectural design reduces index size exponentially and lowers memory overhead.
Promtail acts as the log shipping agent. Running as a lightweight daemon, Promtail discovers local log files, attaches labels based on container attributes pulled directly from the Docker engine, and ships those formatted logs to the Loki instance via HTTP API endpoints.
When running production microservices or custom data extraction jobs, like those in our guide on Scheduling Python Scripts for Automated Tasks
, Promtail automatically tags stdout and stderr output from your containers so you can debug failing background jobs instantly without needing SSH access to the host machine.
Deploying Grafana, Loki, and Promtail with Docker Compose
Let’s start by building a unified deployment stack using Docker Compose. We will spin up Loki, Promtail, Grafana, and an example Nginx container that generates standard access and error logs.
If you are running your server infrastructure on a Hetzner VPS , create a dedicated project directory on your host system:
mkdir -p /opt/docker-logging
cd /opt/docker-logging
Create a file named docker-compose.yml using your preferred text editor and add the complete configuration below:
version: "3.8"
networks:
logging-network:
driver: bridge
volumes:
loki-data:
grafana-data:
services:
loki:
image: grafana/loki:2.9.2
container_name: logging-loki
ports:
- "3100:3100"
command: -config.file=/etc/loki/local-config.yaml
volumes:
- loki-data:/loki
networks:
- logging-network
restart: unless-stopped
promtail:
image: grafana/promtail:2.9.2
container_name: logging-promtail
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- ./promtail-config.yaml:/etc/promtail/promtail-config.yaml:ro
command: -config.file=/etc/promtail/promtail-config.yaml
networks:
- logging-network
depends_on:
- loki
restart: unless-stopped
grafana:
image: grafana/grafana:10.2.0
container_name: logging-grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=SecureMasterPassword123!
- GF_USERS_ALLOW_SIGN_UP=false
volumes:
- grafana-data:/var/lib/grafana
networks:
- logging-network
depends_on:
- loki
restart: unless-stopped
sample-app:
image: nginx:alpine
container_name: web-sample-app
ports:
- "8080:80"
networks:
- logging-network
restart: unless-stopped
Notice that we mount /var/run/docker.sock and /var/lib/docker/containers into the Promtail container with read-only permissions (:ro). This gives Promtail direct visibility into container runtime metadata and log files written by Docker’s default JSON-file log driver.
💡 Fast-Track Your Project: Don’t want to configure this yourself? I build custom n8n pipelines and bots. Message me with code SYS3-HUGO.
Configuring Promtail for Docker Container Scraping
Promtail relies on a dedicated configuration file to locate target log sources, transform raw JSON logs, extract dynamic labels, and forward processed records to Loki.
Create promtail-config.yaml inside your /opt/docker-logging directory:
server:
http_listen_port: 9080
grpc_listen_port: 0
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: 'stream'
pipeline_stages:
- json:
expressions:
output: log
stream: stream
attrs: attrs
time: time
- timestamp:
source: time
format: RFC3339Nano
- output:
source: output
Breaking Down the Configuration Architecture
docker_sd_configs: Uses Promtail’s native Service Discovery mechanism to query the Docker daemon socket every 5 seconds, automatically detecting newly started or stopped containers without restarting Promtail.relabel_configs: Docker container names returned by the API include a leading slash (e.g.,/web-sample-app). The regular expression/(.*)extracts the clean container name and maps it to a readablecontainerlabel.pipeline_stages:json: Docker’s default engine wraps log lines into JSON objects containinglog,stream,attrs, andtimeproperties. This stage parses those fields.timestamp: Ensures Loki uses the exact timestamp generated when the container emitted the log line, rather than the timestamp when Promtail scraped it.output: Sets the payload extracted from thelogJSON field as the actual log line passed into Loki.
Start the complete environment by running:
docker compose up -d
Verify that all four services are active using docker compose ps.
Querying Docker Logs in Grafana using LogQL
With your infrastructure active, open your web browser and navigate to http://<YOUR_SERVER_IP>:3000. Login using credentials defined in your Docker Compose file (admin / SecureMasterPassword123!).
Adding Loki as a Data Source
- Click Connections -> Data Sources in the left sidebar.
- Select Add data source and choose Loki.
- Set the Connection URL to
http://loki:3100. - Click Save & Test. You should see a green success notification stating that the data source is connected and receiving data.
Writing LogQL Queries
Navigate to Explore mode in Grafana and select Loki from the dropdown menu at the top. Loki uses LogQL (Log Query Language), which feels similar to Prometheus querying semantics.
To filter logs by container, use label selectors:
{container="web-sample-app"}
To search for specific string occurrences inside that stream, chain line filter expressions using formatting operators:
{container="web-sample-app"} |= "GET / HTTP/1.1"
To filter out noise, such as health check requests, use the negative line filter operator (!=):
{container="web-sample-app"} != "GET /healthz"
Parsing JSON Logs and Metrics Extraction
If your microservices produce structured JSON logs, you can parse fields dynamically directly within LogQL. For example, if a custom backend writes log output formatted as {"level":"error","user_id":402,"status":500,"msg":"Database connection failed"}:
{container="api-service"} | json | status >= 500
You can even convert raw log events into real-time metrics, such as calculating the per-second rate of HTTP 500 errors over a 5-minute rolling window:
sum(rate({container="api-service"} | json | status >= 500 [5m])) by (container)
When exposing public-facing services, monitor log trends closely. If you are serving public API routes, review our guide on Protecting Self Hosted Endpoints with Rate Limiting to prevent anomalous spikes from saturating your server resources and polluting your log stores.
Setting Up Automated Log Alerts and Workflow Integrations
Log collection provides the most value when combined with proactive alerting. Grafana enables you to create unified alert rules directly from LogQL metrics expressions.
Creating a Log-Based Alert Rule
- Open Grafana and navigate to Alerting -> Alert rules.
- Click Create alert rule.
- Define the query condition based on log rate aggregation:
sum(count_over_time({container="web-sample-app"} |= "error" [1m])) > 5 - Set the evaluation behavior to trigger if the threshold is breached for more than 2 minutes.
- Configure your Contact Point (Webhook, Discord, Slack, or Email).
When building log alert notifications that post directly to webhooks, you might couple Grafana alerts with The Best Free APIs for Building Automation Workflows in 2026 to route incident management alerts to Discord, Telegram, or custom webhook receivers seamlessly.
Getting Started
To get started, provision your core infrastructure on a cloud provider like a Hetzner VPS or a Contabo VPS . If you prefer managed offerings, consider a DigitalOcean droplet.
- Ensure Docker Engine and Docker Compose are installed on your instance.
- Configure DNS A records for your domain with Namecheap if running Grafana behind a reverse proxy like Nginx or Traefik with Let’s Encrypt TLS certificates.
- Save the
docker-compose.ymlandpromtail-config.yamlfiles outlined above to/opt/docker-logging. - Execute
docker compose up -dto initialize your stack. - Access Grafana at port
3000, configure Loki as your primary data source, and begin monitoring container logs in real time.
Outsource Your Automation
Don’t have time? I build production n8n workflows, WhatsApp bots, and fully automated YouTube Shorts pipelines. Hire me on Fiverr — mention SYS3-HUGO for priority. Or DM at chasebot.online .
Want to automate this yourself?
Start with n8n Cloud (free tier available) or self-host on a Hetzner VPS for full control.
Want this engine running on your own VPS?
This blog publishes itself — daily, unattended, on free API tiers. The full engine, Hugo theme, and setup guide are available as System 3.
Get System 3