Loki: ship and search logs
Run Loki, Grafana Alloy and Grafana with Docker Compose, collect the systemd journal and /var/log files with a short Alloy configuration, and search them with LogQL in Grafana Explore and logcli.
Drafted with AI from official documentation. Scripts were run in a test environment and repository links verified. Awaiting a human check. Did it work for you? Last edited Outdated?
Loki is a log store built by the Grafana team that indexes only labels (host, unit, job), not the text of every line. That makes it cheap to run and simple to query: pick a label set, then filter and parse the lines. An agent ships the logs; the current one is Grafana Alloy, which replaced Promtail. This guide gets all three pieces running on one host and shipping that host’s own logs.
Step 1: the compose file
Create a directory, save this as compose.yaml:
services:
loki:
image: grafana/loki:latest
command: -config.file=/etc/loki/local-config.yaml
ports:
- "3100:3100"
volumes:
- loki-data:/loki
alloy:
image: grafana/alloy:latest
command: run --server.http.listen-addr=0.0.0.0:12345 --storage.path=/var/lib/alloy/data /etc/alloy/config.alloy
ports:
- "12345:12345"
volumes:
- ./config.alloy:/etc/alloy/config.alloy:ro
- /var/log:/var/log:ro
- /run/log/journal:/run/log/journal:ro
- /etc/machine-id:/etc/machine-id:ro
- alloy-data:/var/lib/alloy/data
depends_on:
- loki
grafana:
image: grafana/grafana-oss:latest
ports:
- "3000:3000"
volumes:
- grafana-data:/var/lib/grafana
depends_on:
- loki
volumes:
loki-data:
alloy-data:
grafana-data:
Loki uses its built-in single-binary configuration from the image. Alloy gets read-only access to /var/log and the journal; /etc/machine-id is needed so the journal reader can find the right journal directory.
Step 2: the Alloy configuration
Save this as config.alloy in the same directory:
local.file_match "varlog" {
path_targets = [{
__path__ = "/var/log/*.log",
job = "varlog",
host = constants.hostname,
}]
}
loki.source.file "varlog" {
targets = local.file_match.varlog.targets
forward_to = [loki.write.default.receiver]
}
loki.relabel "journal" {
forward_to = []
rule {
source_labels = ["__journal__systemd_unit"]
target_label = "unit"
}
}
loki.source.journal "journal" {
max_age = "12h"
relabel_rules = loki.relabel.journal.rules
labels = {job = "systemd-journal", host = constants.hostname}
forward_to = [loki.write.default.receiver]
}
loki.write "default" {
endpoint {
url = "http://loki:3100/loki/api/v1/push"
}
}
local.file_matchexpands the glob and attaches static labels;loki.source.filetails those files and remembers positions across restarts in the storage path.loki.source.journalreads the systemd journal. Journal fields arrive as internal labels starting with__journal_; theloki.relabelblock copies the unit name into a realunitlabel so you can query by service.loki.writeis the destination. Everyforward_topoints at its receiver, which is how Alloy pipelines are wired together.- Keep the label set small: host, job, unit. Labels with thousands of values (request IDs, user IDs) belong in the line, not in labels, or Loki slows down.
Step 3: start it and check
docker compose up -d
docker compose logs alloy --tail 20
curl -s http://localhost:3100/ready
curl -s -G http://localhost:3100/loki/api/v1/labels | python3 -m json.tool
/ready answers ready once Loki accepts writes (it may take a few seconds after start). The labels call should list host, job and unit. Alloy’s own UI at http://localhost:12345 shows every component, its health and the targets it discovered, which is the first place to look if nothing arrives.
Step 4: search in Grafana
Open http://localhost:3000, add a Loki data source with URL http://loki:3100, then go to Explore, choose Loki, switch the editor to Code and try these LogQL queries:
{job="systemd-journal", unit="sshd.service"}
{job="systemd-journal"} |= "Failed password"
{job="varlog"} |~ "(?i)error|fail" != "health"
{job="systemd-journal"} | json | level="error"
sum by (unit) (count_over_time({job="systemd-journal"} |= "error" [5m]))
rate({job="varlog"} |= "error" [1m])
- The part in braces is the stream selector and must contain at least one label; it decides which chunks Loki reads.
|=and!=filter lines by substring,|~and!~by regular expression.| jsonand| logfmtparse structured lines into fields you can filter on withfield="value".count_over_timeandrateturn log lines into metrics; switch Explore to a graph to see error counts per unit over time, and use the same expressions in dashboards and alert rules.
Step 5: the same from the terminal
logcli ships with Loki’s releases and is handy on servers without a browser:
export LOKI_ADDR=http://localhost:3100
logcli labels job
logcli query '{job="systemd-journal", unit="sshd.service"}' --since=1h --limit=50
logcli query '{job="varlog"} |= "error"' --tail
Adding more hosts
Run only the alloy service on every other machine with the same config.alloy, changing the loki.write URL to the central Loki host. Because the host label comes from constants.hostname, streams from different machines stay separate automatically. Put Loki behind a reverse proxy with TLS and authentication before exposing it beyond a trusted network; Loki itself has no built-in authentication.
Official documentation
Did this work for you?
No votes yet. Be the first.
If it did not work, the report form below tells the editors what went wrong.
See also
- 0193 Linux logs reference: journalctl, journald storage, logrotate and centralized logging Read and filter systemd journal logs, keep their disk use in check, rotate classic log files with logrotate, and understand when to… Intermediate · Cheat sheet · Linux Article Linux administration · Logs 15 Oct 2026
- 0291 What is a log? A log is a running record of what a program did and when, written to a file so you can look back… Beginner · Explains an idea · Any system Article Logs 15 Oct 2026
- 0323 Fix: Failed to start service, how to read systemctl status and journalctl Job for x.service failed tells you only that the service did not start. The real reason is in systemctl status and journalctl… Beginner · Fixes an error · Linux Article Logs · Services & systemd 15 Oct 2026
- 0371 Where are the logs? journalctl, /var/log and container logs A practical map of where Linux programs write their logs and the commands to read them: journalctl filters, the classic files under… Beginner · Step by step · Linux Article Logs 15 Oct 2026
- 0432 Try Sigma rules against your own logs Install the sigma-cli converter, take a rule from the public SigmaHQ library, convert it into a query for a log tool you… Intermediate · Step by step · Linux, macOS Article Logs · Security resources & detection 15 Oct 2026
- 0443 Security monitoring explained: IDS, SIEM, EDR and where the logs go A plain language map of the security monitoring tool families, what each one watches, how they feed one another, and where the… Beginner · Explains an idea · Any system Article Detection & threat hunting · Logs 15 Oct 2026
Repos in the wild
- Loki: horizontally scalable log aggregation inspired by Prometheus grafana/loki The Grafana Loki repository: a log store that indexes labels instead of full text, queried with LogQL from Grafana or logcli, with the single-binary mode for one host and the microservices mode for clusters. GitHub
- Wazuh: an open source security platform with agents, SIEM and XDR wazuh/wazuh Wazuh installs a small agent on each server and sends its logs, file changes and package lists to a central manager that detects intrusions, checks configurations and reports vulnerabilities. GitHub
- Zeek: network security monitoring that turns traffic into logs zeek/zeek Zeek, formerly Bro, watches network traffic and writes structured logs of every connection, DNS query, HTTP request and certificate it sees. Analysts search those logs to understand what happened on the network. GitHub
- pySigma: the Python library that converts Sigma rules into queries SigmaHQ/pySigma pySigma parses Sigma detection rules and, with backend plugins, converts them into native queries for Splunk, Elastic, Loki, Sentinel and other log platforms. It is the engine inside the sigma-cli tool. GitHub
History
Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.
- Dominik +5,603 chars