Engineering

Docker Compose Logging: Collect Logs from Multi-Container Apps

Learn how to configure logging in Docker Compose — JSON file driver, log rotation, structured output, and shipping logs to a centralized service.

LogFlow TeamAugust 22, 20268 min read

Docker Compose is the standard way to run multi-container applications locally and in production. But the default logging setup — dumping everything to JSON files with no rotation — fills your disk and makes debugging impossible across services.

Here's how to set up production-ready logging for Docker Compose.

The Default Problem

Docker's default logging driver (json-file) writes every container's stdout/stderr to a JSON file at /var/lib/docker/containers/<id>/<id>-json.log. These files:

  • Grow forever — no size limit by default
  • Are hard to search — spread across directories named by container ID
  • Disappear when you run docker compose down (if using anonymous volumes)
  • Can't be correlated across services
# Finding logs for a specific container
docker compose logs api | grep error

# This works for one container. With 8 services, good luck.

Step 1: Configure Log Rotation

Always set log rotation. Without it, a busy container will fill your disk:

# docker-compose.yml
services:
  api:
    image: myapp/api
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

This keeps at most 30 MB of logs per container (3 files × 10 MB). Apply it to every service.

To set a default for all services, add to your Docker daemon config (/etc/docker/daemon.json):

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

Step 2: Use Structured Logging in Your Apps

Docker captures stdout/stderr. If your app prints structured JSON, Docker preserves it:

// Node.js — output JSON to stdout
const { LogFlow } = require('@getlogflow/js')
const logger = new LogFlow({ apiKey: process.env.LOGFLOW_API_KEY, service: 'api' })

logger.info('request.handled', { path: '/api/users', status: 200, duration_ms: 45 })
# Python — JSON to stdout
import json, sys

def log(level, message, **kwargs):
    entry = {"level": level, "message": message, "service": "worker", **kwargs}
    print(json.dumps(entry), file=sys.stdout, flush=True)

log("info", "job.completed", job_id="JOB-123", duration_ms=1200)

See our guides for structured logging and framework-specific setup: Express, FastAPI, Django.

Step 3: Add Labels for Service Identification

Docker Compose automatically sets labels that identify the service. Use them for filtering:

services:
  api:
    image: myapp/api
    labels:
      com.logflow.service: "api"
      com.logflow.environment: "production"

  worker:
    image: myapp/worker
    labels:
      com.logflow.service: "worker"
      com.logflow.environment: "production"

Log shippers like Fluent Bit and Vector can read these labels and add them to every log line.

Step 4: Ship Logs to a Central Service

Local docker compose logs doesn't scale. You need a centralized system. Three approaches:

Option A: Direct SDK (Simplest)

If you control the application code, send logs directly from inside the container:

const { LogFlow } = require('@getlogflow/js')
const logger = new LogFlow({
  apiKey: process.env.LOGFLOW_API_KEY,
  service: 'api',
})

Pass the API key as an environment variable in docker-compose.yml:

services:
  api:
    image: myapp/api
    environment:
      - LOGFLOW_API_KEY=${LOGFLOW_API_KEY}

This is the fastest path. See the quickstart guide.

Option B: Fluent Bit Sidecar

Add Fluent Bit as a service in your Compose file to collect logs from all containers:

services:
  fluent-bit:
    image: fluent/fluent-bit:latest
    volumes:
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
      - ./fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf:ro
    depends_on:
      - api
      - worker
# fluent-bit.conf
[INPUT]
    Name              tail
    Path              /var/lib/docker/containers/*/*.log
    Parser            docker
    Docker_Mode       On
    Refresh_Interval  5

[OUTPUT]
    Name              http
    Match             *
    Host              api.getlogflow.com
    Port              443
    URI               /v1/logs
    Header            Authorization Bearer lf_your_api_key
    Format            json
    tls               On

Option C: Docker Logging Driver

Use Docker's built-in fluentd or syslog logging driver to forward logs:

services:
  api:
    image: myapp/api
    logging:
      driver: fluentd
      options:
        fluentd-address: "localhost:24224"
        tag: "docker.api"

This redirects stdout/stderr at the Docker level — no application changes needed.

Step 5: Add Health Checks

Health checks ensure Docker restarts unhealthy containers and help you track uptime:

services:
  api:
    image: myapp/api
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

Health check events appear in Docker logs. Combined with anomaly detection, you get automatic alerts when services go unhealthy.

Step 6: Use docker compose logs Effectively

While setting up centralized logging, docker compose logs is your local debugging tool:

# Follow logs from all services
docker compose logs -f

# Follow specific service
docker compose logs -f api

# Last 100 lines from all services
docker compose logs --tail=100

# Since a specific time
docker compose logs --since="2026-08-22T10:00:00"

# Filter with grep
docker compose logs api 2>&1 | grep "error"

Production Checklist

Before deploying a Docker Compose application:

  • Log rotation configured (max-size, max-file)
  • All applications output structured JSON
  • Logs shipped to centralized service (LogFlow, ELK, etc.)
  • Health checks defined for all services
  • Sensitive data excluded from logs (PII masking guide)
  • Alerts configured for error rate spikes

Related Guides

Start monitoring your logs today

Free plan available. No credit card required. Up and running in 2 minutes.

Get started free