How to Run Grafana Loki with Docker and Docker Compose

Run Grafana Loki with Docker and Docker Compose: a tested Loki 3.7, Grafana Alloy and Grafana stack that ships container logs you can query with LogQL.

Grafana Loki is a horizontally scalable log aggregation system inspired by Prometheus: it indexes only labels per log stream instead of full text, so it uses less storage and is easier to run—especially if you already use Prometheus or Kubernetes. Grafana Alloy collects logs (Docker container output and files under /var/log) and ships them to Loki; Grafana is the UI where you query logs with LogQL. This guide walks you through running Grafana Loki, Alloy, and Grafana with Docker and Docker Compose for local or dev use, then querying logs in Grafana Explore.

The stack below was tested with Loki 3.7.9, Alloy 1.21.3 and Grafana 13.3.0. The image tags are pinned; check the Loki, Alloy and Grafana release pages for newer versions.

In this guide you’ll:

  • Verify Docker and Docker Compose, then run Grafana in a container
  • Run Loki and Alloy with Docker using loki-config.yaml and config.alloy
  • Run the full stack, with healthchecks and a provisioned Loki data source, with a single docker compose up -d
  • Query container and system logs with LogQL in Explore
  • Convert an existing Promtail config to Alloy

For production, use Loki in distributed mode with Helm or Tanka and persistent storage (e.g. S3, GCS).

Promtail is end of life as of March 2, 2026 and gets no further updates. Earlier versions of this guide used it; it now uses Grafana Alloy, its replacement. If you still run Promtail, see Migrating from Promtail below.

Related guides:

Prerequisites: Docker and Docker Compose

You need Docker and Docker Compose installed. On Ubuntu, see How to Install and Use Docker Engine on Ubuntu 26.04 LTS.

Confirm that Docker is working as expected:

docker version

Docker Compose is now included with Docker Desktop and is available on Linux as a Docker plugin. Confirm it’s available with:

docker compose version

This guide uses the docker compose (v2) plugin. The old docker-compose v1 binary is no longer maintained.

Run Grafana in Docker

Grafana is an open source analytics and visualization platform. For this setup, Grafana is the UI for Loki: you’ll use it to add Loki as a data source, run LogQL queries in Explore, and build dashboards from your logs.

Create a Docker network that Grafana, Loki and Alloy will share, so they can reach each other by container name:

docker network create grafana_loki

Run Grafana OSS in a container with:

docker run -d --name grafana \
  --network grafana_loki \
  -p 3000:3000 \
  grafana/grafana:13.3.0

The -d flag runs the container in the background. When it’s up, open Grafana at http://localhost:3000 (or http://your_server_ip:3000). The grafana/grafana image is the OSS edition; the older grafana/grafana-oss image name is no longer updated.

Run Loki with Docker

Loki is the log backend; Alloy is the agent that collects logs and sends them to Loki. Together they give you log aggregation and search that fits well with Prometheus and Kubernetes.

First, create a working directory:

mkdir -p loki && cd loki

Next, create a configuration file required to start and run Loki. Save this as loki-config.yaml:

auth_enabled: false

server:
  http_listen_port: 3100
  grpc_listen_port: 9096

common:
  instance_addr: 127.0.0.1
  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-04-01
      store: tsdb
      object_store: filesystem
      schema: v13
      index:
        prefix: index_
        period: 24h

limits_config:
  retention_period: 744h

compactor:
  working_directory: /loki/compactor
  retention_enabled: true
  delete_request_store: filesystem

analytics:
  reporting_enabled: false

A few notes on this config:

  • store: tsdb with schema: v13 is the schema Loki 3.x expects. Older guides (including earlier versions of this one) use boltdb-shipper with v11, which does not support newer features such as structured metadata.
  • retention_period: 744h (31 days) plus retention_enabled: true on the compactor makes Loki delete logs older than a month. Without it Loki keeps everything forever.
  • analytics.reporting_enabled: false turns off the anonymous usage report Loki sends to Grafana Labs by default.

You can check the file before you start Loki:

docker run --rm -v "$(pwd)/loki-config.yaml":/etc/loki/config.yaml:ro \
  grafana/loki:3.7.9 -config.file=/etc/loki/config.yaml -verify-config

It should print msg="config is valid". Now run the Loki instance with this command:

docker run -d --name loki \
  --network grafana_loki \
  -v "$(pwd)/loki-config.yaml":/etc/loki/config.yaml:ro \
  -v loki_data:/loki \
  -p 3100:3100 \
  grafana/loki:3.7.9 -config.file=/etc/loki/config.yaml

Once Loki is running, run Alloy to ship logs to it. This config collects the output of every container on the Docker host, plus the files matching /var/log/*log. Create a file named config.alloy:

// Find every container on this Docker host
discovery.docker "containers" {
  host             = "unix:///var/run/docker.sock"
  refresh_interval = "5s"
}

// Turn Docker metadata into Loki labels
discovery.relabel "containers" {
  targets = []

  rule {
    source_labels = ["__meta_docker_container_name"]
    regex         = "/(.*)"
    target_label  = "container"
  }
  rule {
    source_labels = ["__meta_docker_container_label_com_docker_compose_service"]
    target_label  = "service"
  }
}

// Ship container stdout/stderr
loki.source.docker "containers" {
  host          = "unix:///var/run/docker.sock"
  targets       = discovery.docker.containers.targets
  relabel_rules = discovery.relabel.containers.rules
  labels        = {job = "docker"}
  forward_to    = [loki.write.local.receiver]
}

// Ship plain-text log files from the host's /var/log
local.file_match "varlogs" {
  path_targets = [{
    __path__ = "/var/log/*log",
    job      = "varlogs",
  }]
}

loki.source.file "varlogs" {
  targets    = local.file_match.varlogs.targets
  forward_to = [loki.write.local.receiver]
}

// Where the logs go
loki.write "local" {
  endpoint {
    url = "http://loki:3100/loki/api/v1/push"
  }
}

discovery.docker checks for new containers every 60 seconds by default; refresh_interval = "5s" picks them up faster, so short-lived containers are much less likely to be missed. The container and service labels come from the container name and its Compose service name.

Start Alloy with this command:

docker run -d --name alloy \
  --network grafana_loki \
  -v "$(pwd)/config.alloy":/etc/alloy/config.alloy:ro \
  -v /var/run/docker.sock:/var/run/docker.sock:ro \
  -v /var/log:/var/log:ro \
  -v alloy_data:/var/lib/alloy/data \
  -p 12345:12345 \
  grafana/alloy:v1.21.3 \
  run /etc/alloy/config.alloy \
  --storage.path=/var/lib/alloy/data \
  --server.http.listen-addr=0.0.0.0:12345

When Loki and Alloy are running, check Loki’s metrics at http://localhost:3100/metrics and readiness at http://localhost:3100/ready (Loki takes about 15 seconds to report ready). Alloy’s UI at http://localhost:12345 shows each component and whether it is healthy.

Tip: Alloy needs the Docker socket to discover containers, and access to the socket is root-equivalent on the host. The :ro flag only stops the container replacing the socket file; it does not restrict the Docker API, so anything that can reach the socket can control the host. For a narrower grant, put a socket proxy such as tecnativa/docker-socket-proxy (with CONTAINERS=1) in front of it. Also do not expose port 12345 beyond your machine: the Alloy UI has no login.

Run the full stack with Docker Compose

A Docker Compose file lets you start and manage Grafana, Loki, and Alloy with one command. If you started the containers from the previous sections, remove them first so the names and ports are free:

docker rm -f grafana loki alloy

Use the same loki-config.yaml and config.alloy as above. Grafana can add the Loki data source for you at startup, so create a provisioning file:

mkdir -p grafana/provisioning/datasources

Save this as grafana/provisioning/datasources/loki.yaml:

apiVersion: 1

datasources:
  - name: Loki
    type: loki
    access: proxy
    url: http://loki:3100
    isDefault: true
    editable: false

Then add the following to a docker-compose.yaml file in the same directory:

networks:
  grafana_loki:

volumes:
  loki_data:
  alloy_data:
  grafana_data:

services:
  loki:
    image: grafana/loki:3.7.9
    command: -config.file=/etc/loki/config.yaml
    ports:
      - "3100:3100"
    volumes:
      - ./loki-config.yaml:/etc/loki/config.yaml:ro
      - loki_data:/loki
    networks:
      - grafana_loki
    restart: unless-stopped

  alloy:
    image: grafana/alloy:v1.21.3
    command:
      - run
      - /etc/alloy/config.alloy
      - --storage.path=/var/lib/alloy/data
      - --server.http.listen-addr=0.0.0.0:12345
    ports:
      - "12345:12345"
    volumes:
      - ./config.alloy:/etc/alloy/config.alloy:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /var/log:/var/log:ro
      - alloy_data:/var/lib/alloy/data
    healthcheck:
      test: ["CMD", "bash", "-c", "exec 3<>/dev/tcp/127.0.0.1/12345 && printf 'GET /-/ready HTTP/1.0\\r\\n\\r\\n' >&3 && grep -q ' 200 ' <&3"]
      interval: 10s
      timeout: 5s
      retries: 6
    depends_on:
      - loki
    networks:
      - grafana_loki
    restart: unless-stopped

  grafana:
    image: grafana/grafana:13.3.0
    ports:
      - "3000:3000"
    volumes:
      - ./grafana/provisioning:/etc/grafana/provisioning:ro
      - grafana_data:/var/lib/grafana
    healthcheck:
      test: ["CMD", "curl", "-fsS", "http://localhost:3000/api/health"]
      interval: 10s
      timeout: 5s
      retries: 6
    depends_on:
      - loki
    networks:
      - grafana_loki
    restart: unless-stopped

The directory should now look like this:

loki/
├── config.alloy
├── docker-compose.yaml
├── grafana/provisioning/datasources/loki.yaml
└── loki-config.yaml

From that directory, start everything and wait for the healthchecks to pass:

docker compose up -d --wait
docker compose ps --format 'table {{.Service}}\t{{.Image}}\t{{.Status}}'
SERVICE   IMAGE                    STATUS
alloy     grafana/alloy:v1.21.3    Up 20 seconds (healthy)
grafana   grafana/grafana:13.3.0   Up 20 seconds (healthy)
loki      grafana/loki:3.7.9       Up 20 seconds

A few notes on the Compose file:

  • The top-level version: key is obsolete in Compose v2 and is left out.
  • The Loki image is distroless: it has no shell, wget or curl, so it cannot run a Docker healthcheck. Check it from the host with curl http://localhost:3100/ready. The Alloy image has no curl either, so its healthcheck uses Bash’s /dev/tcp to call Alloy’s /-/ready endpoint.
  • Named volumes keep Loki’s chunks, Alloy’s read positions and Grafana’s database across restarts. docker compose down -v deletes them.

Grafana stays at port 3000, Loki at 3100, and the Alloy UI at 12345.

Check that logs reach Loki

Start a throwaway container that prints a line, then ask Loki’s API for it:

docker run -d --name log-test busybox:1.37 sh -c 'echo hello-loki; sleep 30'
sleep 15
curl -sG http://localhost:3100/loki/api/v1/query_range \
  --data-urlencode 'query={container="log-test"} |= "hello-loki"' \
  --data-urlencode 'limit=5'
docker rm -f log-test

The JSON response should contain a stream with "container":"log-test" and "job":"docker", and the value hello-loki.

Add Loki as a data source and query logs

  1. Log in to Grafana at http://localhost:3000 with the default user admin and password admin. Change the password when prompted.
  2. Add Loki: With the Docker Compose stack, this is already done: the provisioning file adds a Loki data source pointing at http://loki:3100. If you started Grafana with docker run instead, open the menu (Grafana icon) → Connections → Data sources → Add data source → Loki. Set URL to http://loki:3100 and click Save & test.
  3. Query logs: Go to Explore, select Loki, then use the label browser to pick job → docker (container logs) or job → varlogs (for /var/log/*log). Run a query such as:
{job="docker", service="grafana"}

To see logs from one container that contain the word “error”:

{container="loki-loki-1"} |= "error"

Compose names containers <project>-<service>-<n>, where the project is the directory name, so adjust the container name to match docker compose ps. For system logs, parse logfmt lines and hide lines that fail parsing with:

{job="varlogs"} | logfmt | __error__=""

You should see logs from your containers and system streaming in.

Migrating from Promtail

If you already run Promtail in Docker, Alloy can convert its config for you. For example, with the promtail-config.yaml that earlier versions of this guide used, in the current directory:

docker run --rm -v "$(pwd)":/work -w /work grafana/alloy:v1.21.3 \
  convert --source-format=promtail --output=/work/promtail.alloy /work/promtail-config.yaml

Writing to promtail.alloy keeps your existing config.alloy intact. The output for a config that tails /var/log/*log looks like this:

loki.source.file "system" {
	targets = [{
		__address__ = "localhost",
		__path__    = "/var/log/*log",
		job         = "varlogs",
	}]
	forward_to = [loki.write.default.receiver]

	file_match {
		enabled = true
	}
	legacy_positions_file = "/positions/positions.yaml"
}

loki.write "default" {
	endpoint {
		url = "http://loki:3100/loki/api/v1/push"
	}
	external_labels = {}
}

The converted config has only what Promtail did (here, the /var/log file source); it has no Docker discovery. Merge its blocks into the config.alloy above rather than replacing it, or the job="docker" queries return nothing. legacy_positions_file makes Alloy start from where Promtail stopped reading: add Promtail’s positions volume to the alloy service (for example - promtail_positions:/positions, and declare promtail_positions: under the top-level volumes:) to avoid shipping duplicate lines. Then replace the promtail service with the alloy service from the Compose file above, and run docker compose up -d --remove-orphans. See the official Promtail migration guide for the conversion’s limits, and How to Ship Logs to Loki With Grafana Alloy on Debian 12/13 for the same migration on a host without Docker.

Frequently Asked Questions (FAQ)

What is Grafana Loki?

Grafana Loki is a log aggregation system that indexes only label metadata for log streams (similar to how Prometheus indexes metrics), not full log content. That reduces storage and operational cost. You query logs with LogQL in Grafana after adding Loki as a data source.

What is Grafana Alloy, and what happened to Promtail?

Grafana Alloy is Grafana Labs’ collector for logs, metrics and traces. For Loki it does what Promtail used to do: it discovers log sources (Docker containers, files under /var/log, the systemd journal), adds labels, and pushes log lines to Loki. Promtail reached end of life on March 2, 2026 and gets no further updates, so new setups should use Alloy. alloy convert --source-format=promtail converts an existing Promtail config.

How do I run Loki with Docker Compose?

Create a docker-compose.yaml with services for loki, alloy, and grafana on a shared network. Mount loki-config.yaml and config.alloy, mount /var/run/docker.sock into Alloy, use named volumes for Loki, Alloy and Grafana data, and provision the Loki data source in Grafana. Then run docker compose up -d --wait. Loki listens on port 3100, Grafana on 3000.

Can I use this Loki Docker setup in production?

This guide uses single-binary Loki with filesystem storage and in-memory ring, which is suitable for local or dev. For production, run Loki in distributed mode with persistent object storage (S3, GCS) and deploy via Helm or Tanka.

What is LogQL?

LogQL is Loki’s query language (inspired by PromQL). In Grafana Explore with Loki selected, you use it to filter by labels (e.g. {job="docker"}) and optionally parse or filter log lines (e.g. |= "error" or | logfmt).

Conclusion

You ran Grafana, Loki, and Grafana Alloy with Docker and Docker Compose, provisioned Loki as a Grafana data source, and queried container and system logs in Grafana Explore using LogQL. For next steps, add dashboards and alerts, or pair this stack with Prometheus in Docker for metrics and logs together.

comments powered by Disqus
Citizix Ltd
Built with Hugo
Theme Stack designed by Jimmy