The first time I had more than five containers running on a single host, docker logs container-name stopped being a workflow and became a chore. Tailing logs one container at a time, across multiple hosts, during an incident is exactly the kind of thing that turns a 10-minute debugging session into an hour. Logspout solved that for me — it’s a lightweight log router that attaches to the Docker socket, tails stdout/stderr from every running container, and ships it wherever you want: syslog, a HTTP endpoint, Logstash, Elasticsearch, or a custom adapter. This guide covers setting it up from scratch, understanding how it works internally, and routing logs to a real aggregation backend.
What Logspout Actually Does
Logspout is not a log storage system — it’s a router. It:
- Connects to the Docker daemon’s Unix socket (
/var/run/docker.sock). - Discovers all running containers and attaches to their log streams (the same stream
docker logsreads from). - Watches for new containers starting/stopping and attaches/detaches dynamically — no restart needed.
- Forwards each log line, tagged with container metadata, to one or more configured destinations (“routes”).
Because it reads directly from the Docker log driver, it works with any container without requiring you to modify application code to log to a specific place.
Prerequisites
- Docker Engine installed and running.
- Root or a user in the
dockergroup (Logspout needs access to/var/run/docker.sock).
Check your Docker version:
docker version --format '{{.Server.Version}}'
Step 1: Run Logspout With a Basic Route
The simplest setup forwards all container logs to a syslog endpoint. Let’s start with an actual syslog server for testing — papertrail-style or a local syslog-ng container works, but for a first test we’ll just print to a local UDP listener.
docker run -d --name logspout \
--volume=/var/run/docker.sock:/var/run/docker.sock \
--publish=127.0.0.1:8000:80 \
gliderlabs/logspout
This starts Logspout with its HTTP API exposed on port 8000 (useful for live-tailing without a destination configured yet).
Verify it’s running:
docker ps --filter name=logspout
Expected output:
CONTAINER ID IMAGE STATUS PORTS NAMES
a1b2c3d4e5f6 gliderlabs/logspout Up 5 seconds 0.0.0.0:8000->80/tcp logspout
Step 2: Live-Tail All Container Logs Over HTTP
Logspout exposes a /logs streaming endpoint out of the box:
curl http://127.0.0.1:8000/logs
Start another container to generate some log output:
docker run -d --name test-app alpine sh -c "while true; do echo 'hello from test-app'; sleep 2; done"
Back in your curl terminal, expected output:
test-app|hello from test-app
test-app|hello from test-app
This confirms Logspout picked up the new container automatically — no configuration change or restart required, which is the core value proposition versus manually configuring log shipping per container.
Step 3: Route Logs to Syslog (Real Aggregation)
Most production setups forward to a centralized syslog receiver, Logstash, or a managed log service. The route is passed as a command argument using the syslog:// scheme:
docker run -d --name logspout \
--volume=/var/run/docker.sock:/var/run/docker.sock \
gliderlabs/logspout \
syslog://logs.example.com:514
For local testing, stand up a syslog receiver container:
docker run -d --name syslog-server -p 514:514/udp balabit/syslog-ng:latest
Then point Logspout at it via the Docker network (assuming both are on the same user-defined network, logging-net):
docker network create logging-net
docker run -d --name syslog-server \
--network logging-net \
-p 514:514/udp \
balabit/syslog-ng:latest
docker run -d --name logspout \
--network logging-net \
--volume=/var/run/docker.sock:/var/run/docker.sock \
gliderlabs/logspout \
syslog://syslog-server:514
Step 4: Filter Which Containers/Logs Get Shipped
By default Logspout ships stdout+stderr from every container, including itself. You almost always want to exclude some containers (e.g., the log shipper itself, or noisy health-check sidecars). Use environment variables or route-level filters:
docker run -d --name logspout \
--network logging-net \
--volume=/var/run/docker.sock:/var/run/docker.sock \
-e EXCLUDE_LABEL=logspout.exclude \
gliderlabs/logspout \
syslog://syslog-server:514
Then label containers you want excluded:
docker run -d --name noisy-healthcheck \
--label logspout.exclude=true \
alpine sh -c "while true; do echo ping; sleep 1; done"
You can also filter by source stream:
gliderlabs/logspout syslog://syslog-server:514?filter.sources=stdout
This ships only stdout, excluding stderr — useful if you route error logs somewhere different.
Step 5: Route to Multiple Destinations at Once
Logspout supports multiple simultaneous routes, comma-separated:
docker run -d --name logspout \
--network logging-net \
--volume=/var/run/docker.sock:/var/run/docker.sock \
gliderlabs/logspout \
syslog://syslog-server:514,http://logstash.example.com:8080
This is useful during a migration — ship to your old syslog target and a new ELK pipeline simultaneously while you validate the new one.
Step 6: Docker Compose Setup for a Real Stack
A realistic logging stack pairs Logspout with Logstash/Elasticsearch/Kibana (the classic ELK approach) or a lighter Loki-based stack. Here’s Logspout feeding Logstash:
# docker-compose.yml
version: "3.8"
networks:
logging-net:
driver: bridge
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.14.0
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- ES_JAVA_OPTS=-Xms512m -Xmx512m
networks:
- logging-net
ports:
- "9200:9200"
logstash:
image: docker.elastic.co/logstash/logstash:8.14.0
volumes:
- ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf
networks:
- logging-net
depends_on:
- elasticsearch
ports:
- "5000:5000"
kibana:
image: docker.elastic.co/kibana/kibana:8.14.0
networks:
- logging-net
ports:
- "5601:5601"
depends_on:
- elasticsearch
logspout:
image: gliderlabs/logspout
volumes:
- /var/run/docker.sock:/var/run/docker.sock
networks:
- logging-net
command: logstash://logstash:5000
depends_on:
- logstash
app:
image: nginx:alpine
networks:
- logging-net
logstash.conf:
input {
tcp {
port => 5000
codec => json_lines
}
}
output {
elasticsearch {
hosts => ["http://elasticsearch:9200"]
index => "docker-logs-%{+YYYY.MM.dd}"
}
}
docker compose up -d
After a minute, check that logs landed in Elasticsearch:
curl -s http://localhost:9200/_cat/indices?v
Expected output:
health status index uuid pri rep docs.count
yellow open docker-logs-2026.07.29 xxxxxx 1 1 142
Open Kibana at http://localhost:5601 and create a data view over docker-logs-* to search and visualize.
How Logspout Works Internally
Under the hood, Logspout uses the Docker Engine API’s container attach/logs endpoint with follow=true, which streams multiplexed stdout/stderr frames over the socket connection. Docker’s log driver framing includes a stream-type byte (stdout vs stderr) and a length-prefixed payload — Logspout demultiplexes this, attaches container metadata (name, ID, image, labels), and hands each line off to whichever output adapter matches your configured route scheme (syslog://, http://, logstash://, or a custom Go adapter you register at build time).
Because it uses the logs API rather than reading log files directly, it works regardless of which logging driver the container itself is configured with (json-file, local, etc.), as long as that driver supports the read-back API — note that drivers like none or some remote-only drivers won’t have anything for Logspout to read.
Security Considerations
- Docker socket access is root-equivalent. Mounting
/var/run/docker.sockinto Logspout gives it the same power as root on the host. Only run trusted, pinned images (use a specific tag/digest, notlatest, in production), and don’t expose Logspout’s HTTP API (8000) publicly. - Encrypt log transport in production — plain
syslog://over UDP is unauthenticated and unencrypted; usesyslog+tls://where supported, or route through a TLS-terminating aggregator. - Scrub sensitive data (tokens, PII) at the application log-emission level — Logspout ships whatever containers print; it does not redact.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| No logs appearing at destination | Route URL/scheme typo, or destination unreachable | Check docker logs logspout for connection errors |
| Logspout can’t start / permission denied | Docker socket not mounted or wrong permissions | Confirm -v /var/run/docker.sock:/var/run/docker.sock and that the user has socket access |
| Only some containers’ logs show up | EXCLUDE_LABEL or filter.sources misconfigured | Remove filters temporarily to confirm baseline behavior |
| Duplicate log lines at destination | Multiple Logspout instances or restart loops | Ensure only one Logspout container per host; check docker ps -a for crash-looping instances |
Check Logspout’s own logs for adapter errors:
docker logs logspout --tail 50
Real-World Deployment Notes
I’ve run Logspout as a single instance per host in a multi-host fleet, each one shipping to a shared aggregation cluster rather than trying to centralize the shipper itself — it’s stateless, so there’s no coordination needed between instances on different hosts. One thing worth planning for early: log volume grows faster than people expect once every container is shipping unfiltered stdout/stderr, so set up index/retention policies on your Elasticsearch (or equivalent) cluster from day one rather than after a disk-full incident. If you’re on Kubernetes rather than plain Docker hosts, Logspout isn’t the typical choice — Fluent Bit or Vector as a DaemonSet is more common there, since they integrate more natively with the Kubernetes API for pod metadata enrichment. Logspout shines specifically in plain Docker/Compose/Swarm environments where you don’t already have a Kubernetes-native logging pipeline.
Summary
Logspout earns its place in a Docker logging stack by doing one thing well: dynamically discovering containers and routing their log streams without requiring per-container configuration. It’s not a replacement for a full observability stack — you still need something to store, index, and query logs (ELK, Loki, a SaaS platform) — but as the collection layer, it removes the operational burden of manually wiring log shipping into every container you run.