Shipping Docker logs to Loki from a Raspberry Pi with Fluent Bit
I have been moving log collection in my homelab away from Grafana Promtail. Promtail has been deprecated for a while, and in a previous post I migrated some systems to Grafana Alloy. That has worked well on normal Linux servers and in Kubernetes, but one small host in my homelab needed a different solution: an older Raspberry Pi 3 running 32-bit ARM.
This Pi runs a small Docker Compose stack for services like AdGuard Home, cAdvisor, and a log forwarder. The logging target is still Grafana Loki, but the collector needed to run on armv7 and I did not want to mount the Docker socket just to collect container logs.
Why not Alloy here?
Grafana Alloy is the recommended successor for Promtail and it is what I use in other parts of my homelab. The problem is architecture support. The Pi 3 can run a 64-bit OS, but this particular system is still on a 32-bit Raspberry Pi OS install. Grafana Alloy container images support amd64 and arm64, but not armv7.
I briefly looked at Vector (a Datadog telemetry collector) because it has an ARMv7 image and a clean Docker logs source. The catch is that the Docker logs source expects access to the Docker socket. That works technically, but it is more access than I want to give a log collector on this host.
The setup I had with Promtail only mounted Docker’s JSON log files:
volumes:
- /var/lib/docker/containers:/var/lib/docker/containers:ro
I was able to keep this existing configuration and swap in Fluent Bit.
Fluent Bit
Fluent Bit is a lightweight log and metrics processor. It has official container images for arm32v7, can tail files directly, can parse Docker JSON logs, and has a built-in Loki output plugin.
That combination makes it a good fit for this Raspberry Pi:
- no Docker socket mount
- official ARMv7 image support
- persistent file offsets
- Docker JSON log parsing
- direct push to Loki over HTTPS
Docker logging tag
My Docker Compose files already configure the json-file logging driver with a custom tag. This gives the collector enough metadata to attach useful Loki labels without talking to the Docker daemon.
logging:
driver: 'json-file'
options:
tag: '{{.ImageName}}|{{.Name}}|{{.ImageFullID}}|{{.FullID}}'
Docker stores that value in the JSON log record under attrs.tag. Fluent Bit can parse the JSON log line, then a tiny Lua filter can split the tag into fields for Loki labels.
Docker Compose
I replaced my old promtail/ service directory with fluent-bit/ so my existing deployment script will pick it up automatically.
docker-compose.yml
services:
fluent-bit:
container_name: fluent-bit
image: cr.fluentbit.io/fluent/fluent-bit:5.0.5
restart: unless-stopped
logging:
driver: 'json-file'
options:
tag: '{{.ImageName}}|{{.Name}}|{{.ImageFullID}}|{{.FullID}}'
volumes:
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- ./fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf:ro
- ./parsers.conf:/fluent-bit/etc/parsers.conf:ro
- ./docker-labels.lua:/fluent-bit/etc/docker-labels.lua:ro
- fluent-bit-data:/var/lib/fluent-bit
volumes:
fluent-bit-data:
The fluent-bit-data volume stores the tail database so Fluent Bit remembers where it left off after restarts.
Fluent Bit config
This customizes Fluent Bit to fit our needs. Create these files next to the docker-compose.yml.
fluent-bit.conf
[SERVICE]
Flush 5
Log_Level info
Parsers_File /fluent-bit/etc/parsers.conf
storage.path /var/lib/fluent-bit
storage.sync normal
storage.checksum off
[INPUT]
Name tail
Tag docker.*
Path /var/lib/docker/containers/*/*-json.log
Parser docker
DB /var/lib/fluent-bit/docker-containers.db
Mem_Buf_Limit 10MB
Skip_Long_Lines On
Refresh_Interval 10
storage.type filesystem
[FILTER]
Name lua
Match docker.*
script /fluent-bit/etc/docker-labels.lua
call docker_labels
[OUTPUT]
Name loki
Match docker.*
Host loki.example.com
Port 443
TLS On
URI /loki/api/v1/push
Labels job=containerlogspi, stream=$stream, image_name=$image_name, container_name=$container_name, image_id=$image_id, container_id=$container_id
Line_Format json
Remove_Keys stream,attrs,tag,image_name,container_name,image_id,container_id
Drop_Single_Key raw
Replace loki.example.com with the hostname for your Loki server.
Parser config
The Docker json-file driver writes records like this:
{ "log": "message\n", "stream": "stdout", "time": "2026-05-20T12:00:00.000000000Z" }
This parser tells Fluent Bit to parse the log line as JSON and use Docker’s time field as the event timestamp.
parsers.conf
[PARSER]
Name docker
Format json
Time_Key time
Time_Format %Y-%m-%dT%H:%M:%S.%L
Time_Keep Off
Extract Docker labels
Fluent Bit has record accessors for labels, but I wanted to keep the existing Docker tag format and split it into separate fields. For this step I used the solution suggested by AI coding agent Claude Code. A small Lua script is used to attach metadata to the logs so we can query in Loki based on labels like the container name, image, etc.
docker-labels.lua
function docker_labels(tag, timestamp, record)
local attrs = record["attrs"]
if type(attrs) ~= "table" then
return 1, timestamp, record
end
local docker_tag = attrs["tag"]
if docker_tag == nil then
return 1, timestamp, record
end
local image_name, container_name, image_id, container_id =
string.match(docker_tag, "^([^|]*)|([^|]*)|([^|]*)|([^|]*)$")
record["tag"] = docker_tag
record["image_name"] = image_name or ""
record["container_name"] = container_name or ""
record["image_id"] = image_id or ""
record["container_id"] = container_id or ""
return 1, timestamp, record
end
The Loki output then uses those fields as labels.
Validate the config
Fluent Bit has a dry run option that validates the configuration without starting the full pipeline:
docker compose run --rm fluent-bit --dry-run -c /fluent-bit/etc/fluent-bit.conf
On my system this reported:
configuration test is successful
I also validated the Compose file:
docker compose config
Start the collector
Start Fluent Bit with Compose:
docker compose up -d
After a few seconds, logs should be available in Grafana with labels such as:
jobstreamimage_namecontainer_nameimage_idcontainer_id
For example:
{job="containerlogspi", container_name="adguard-pi"}
Closing thoughts
This is a small change, but it is exactly the kind of boring homelab maintenance that future me appreciates. Promtail was a great tool for a long time, but it is deprecated. Alloy is the path I prefer on systems where it fits. For this older Raspberry Pi 3, Fluent Bit keeps the setup lightweight, supported on ARMv7, and limited to read-only access to Docker’s log files. I actually kind of prefer this over the heavy config required for Grafana Alloy.