Optimizing Docker Logs with Loki and Grafana

Implementing Loki across 20+ bare-metal or VM servers running Docker Compose requires an architectural choice. You have two main approaches to collect logs and forward them to a central Loki instance:

  1. The Modern Agent Approach (Grafana Alloy via Docker Bind-Mounts): Run Grafana Alloy as a standalone container on each of your 20 servers. It automatically sniffs out the log files from /var/lib/docker/containers/ and ships them to Loki. (Recommended for stability and filtering power).
  2. The Plugin Approach (Loki Docker Driver): Install a Docker plugin on all 20 hosts to direct the Docker engine itself to stream logs directly to Loki over HTTP.

Here is the step-by-step blueprint for Approach 1 (Grafana Alloy), which is the most reliable production setup because it won’t crash or lock up your Docker daemon if your central Loki server goes down temporarily.

Step 1: Set Up the Central Loki + Grafana Server

Pick one of your 20 servers (or a dedicated monitoring server) to act as your Central Observability Hub.

Create a docker-compose.yml file on that central machine:

YAML

version: "3.8"
services:
loki:
image: grafana/loki:3.0.0
ports:
- "3100:3100"
volumes:
- loki-data:/loki
command: -config.file=/etc/loki/local-config.yaml
restart: unless-stopped
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
volumes:
- grafana-data:/var/lib/grafana
environment:
- GF_SECURITY_ADMIN_PASSWORD=your_secure_password
restart: unless-stopped
volumes:
loki-data:
grafana-data:

Run docker compose up -d on this central server. Ensure port 3100 is reachable over your internal network from the other 19 servers.

Step 2: Deploy Grafana Alloy on the 19 Remote Servers

On each of your other servers, you will deploy a tiny Grafana Alloy container. Its job is to read the standard JSON logs Docker naturally writes to the host disk.

1. Create the Alloy Config File

Create a directory /etc/alloy/ on the host, and save the following as config.alloy. This configuration file auto-discovers all running Docker containers and forwards their streams to your central Loki instance.

Code snippet

// Discover all local running Docker containers
discovery.docker "local_containers" {
host = "unix:///var/run/docker.sock"
}
// Read the log files of those discovered containers
loki.source.docker "docker_logs" {
targets = discovery.docker.local_containers.targets
forward_to = [loki.write.central_loki.receiver]
}
// Send the logs to your central Loki instance
loki.write "central_loki" {
endpoint {
url = "http://<CENTRAL_LOKI_IP>:3100/loki/api/v1/push"
}
}

(Replace <CENTRAL_LOKI_IP> with the actual internal network IP of your central monitoring server).

2. Launch Alloy via Docker Compose

On each server, deploy Alloy using this lightweight compose file. It maps the host’s Docker socket and log paths into the Alloy container:

YAML

version: "3.8"
services:
alloy:
image: grafana/alloy:latest
container_name: alloy-logger
volumes:
- /etc/alloy/config.alloy:/etc/alloy/config.alloy:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
command: run --storage.path=/var/lib/alloy/data /etc/alloy/config.alloy
network_mode: host
restart: unless-stopped

Run docker compose up -d. Alloy will immediately start streaming logs to your central hub.

Step 3: View the Logs in Grafana

  1. Open your browser and head to http://<CENTRAL_SERVER_IP>:3000 (Log in using the admin password you set in Step 1).
  2. Navigate to Connections -> Data Sources and click Add data source.
  3. Select Loki.
  4. In the URL field, type http://loki:3100 (or http://localhost:3100) and click Save & test.
  5. Go to the Explore tab on the left sidebar, select your Loki data source, and use the label browser to query your logs:

Code snippet

# Find logs from a specific container name across your fleet
{container_name="authentik-worker"}

Pro-Tips for Managing 20+ Servers

  • Automate with Ansible: Don’t configure 20 servers manually! Write a quick Ansible playbook to drop the config.alloy and docker-compose.yml onto all 19 target machines and run docker compose up -d.
  • Add a host Label: If you want to differentiate which logs came from which physical server, add an environment variable to Alloy or adjust the config.alloy file to inject a static host="server-01" metric label.
  • Keep json-file Rotation Enabled: Because Alloy reads the raw logs from /var/lib/docker/containers, make sure your existing application compose files don’t have log rotation disabled. Set a global limit in /etc/docker/daemon.json on your hosts to prevent logs from filling up local disks:JSON{ "log-driver": "json-file", "log-opts": { "max-size": "50m", "max-file": "3" } }

Leave a Reply