Monitoring Docker Logs with Promtail and Loki

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

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

  1. 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.
  2. 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 readable container label.
  3. pipeline_stages:
    • json: Docker’s default engine wraps log lines into JSON objects containing log, stream, attrs, and time properties. 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 the log JSON 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

  1. Click Connections -> Data Sources in the left sidebar.
  2. Select Add data source and choose Loki.
  3. Set the Connection URL to http://loki:3100.
  4. 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

  1. Open Grafana and navigate to Alerting -> Alert rules.
  2. Click Create alert rule.
  3. Define the query condition based on log rate aggregation:
    sum(count_over_time({container="web-sample-app"} |= "error" [1m])) > 5
    
  4. Set the evaluation behavior to trigger if the threshold is breached for more than 2 minutes.
  5. 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.

  1. Ensure Docker Engine and Docker Compose are installed on your instance.
  2. 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.
  3. Save the docker-compose.yml and promtail-config.yaml files outlined above to /opt/docker-logging.
  4. Execute docker compose up -d to initialize your stack.
  5. 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
system online