Work in Progress: This page is under development. Use the feedback button on the bottom right to help us improve it.

Docker Compose

Run Laminar's observability stack locally using Docker Compose for development and testing.

Overview

The Docker Compose setup provides the observability infrastructure for Laminar:

  • GrepTimeDB - Time-series database for logs and metrics
  • Vector - Log and metrics pipeline
  • Grafana - Dashboards and visualization
  • NGINX - Reverse proxy

Laminar itself runs natively via cargo run and connects to this observability stack.

Prerequisites

  • Docker Desktop or Docker Engine 20.10+
  • Docker Compose v2.0+
  • 2GB RAM available for observability stack

Quick Start

cd setup
docker compose up -d

docker-compose.yml

services:
  # GrepTimeDB - Time-series database for logs and metrics
  greptimedb:
    image: greptime/greptimedb:v1.0.0-beta.4
    container_name: laminar-greptimedb-local
    command: >
      standalone start
      --http-addr 0.0.0.0:4000
      --rpc-addr 0.0.0.0:4001
      --mysql-addr 0.0.0.0:4002
      --postgres-addr 0.0.0.0:4003
    ports:
      - "${GREPTIMEDB_HTTP_PORT:-4000}:4000"   # HTTP API + Prometheus remote write
      - "${GREPTIMEDB_GRPC_PORT:-4001}:4001"   # gRPC
      - "${GREPTIMEDB_MYSQL_PORT:-4002}:4002"  # MySQL protocol
      - "${GREPTIMEDB_PG_PORT:-4003}:4003"     # PostgreSQL protocol
    volumes:
      - greptimedb_data:/tmp/greptimedb
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:4000/health"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    networks:
      - laminar-local
 
  # Database initialization
  greptimedb-init:
    image: curlimages/curl:8.11.1
    depends_on:
      greptimedb:
        condition: service_healthy
    entrypoint: ["/bin/sh", "-c"]
    command:
      - |
        echo "Creating databases with TTL..."
        curl -s "http://greptimedb:4000/v1/sql" -d "sql=CREATE DATABASE IF NOT EXISTS laminar_logs WITH (ttl = '1h')"
        curl -s "http://greptimedb:4000/v1/sql" -d "sql=CREATE DATABASE IF NOT EXISTS laminar_metrics WITH (ttl = '1h')"
        echo "Setup completed successfully"
    networks:
      - laminar-local
 
  # Vector - Log and metrics pipeline
  vector:
    image: timberio/vector:0.42.0-alpine
    container_name: laminar-vector-local
    command: ["--config", "/etc/vector/vector.toml"]
    volumes:
      - ./vector.toml:/etc/vector/vector.toml:ro
      - /tmp/laminar:/tmp/laminar:ro
    extra_hosts:
      - "host.docker.internal:host-gateway"
    depends_on:
      greptimedb:
        condition: service_healthy
      greptimedb-init:
        condition: service_completed_successfully
    restart: unless-stopped
    networks:
      - laminar-local
 
  # Grafana - Observability dashboards
  grafana:
    image: grafana/grafana:12.4.0-react19
    container_name: laminar-grafana-local
    ports:
      - "${GRAFANA_PORT:-3001}:3000"
    environment:
      - GF_SERVER_DOMAIN=localhost
      - GF_SERVER_ROOT_URL=%(protocol)s://%(domain)s/grafana
      - GF_SERVER_SERVE_FROM_SUB_PATH=true
      - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USER:-laminar}
      - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-laminar}
      - GF_AUTH_ANONYMOUS_ENABLED=false
    volumes:
      - ./grafana/provisioning:/etc/grafana/provisioning:ro
      - ./grafana/dashboards:/var/lib/grafana/dashboards:ro
      - grafana_data:/var/lib/grafana
    depends_on:
      greptimedb:
        condition: service_healthy
    restart: unless-stopped
    networks:
      - laminar-local
 
  # NGINX - Reverse proxy
  nginx:
    image: nginx:1.27-alpine
    container_name: laminar-nginx-local
    ports:
      - "${NGINX_PORT:-80}:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    extra_hosts:
      - "host.docker.internal:host-gateway"
    depends_on:
      - grafana
    restart: unless-stopped
    networks:
      - laminar-local
 
networks:
  laminar-local:
    name: laminar-local
 
volumes:
  greptimedb_data:
  grafana_data:

Commands

# Start all services
docker compose up -d
 
# View logs
docker compose logs -f
 
# View logs for specific service
docker compose logs -f greptimedb
 
# Stop services
docker compose down
 
# Stop and remove volumes
docker compose down -v
 
# Restart a service
docker compose restart vector

Services

GrepTimeDB

Time-series database for storing logs and metrics.

PortProtocolPurpose
4000HTTPREST API, Prometheus remote write
4001gRPCgRPC API
4002MySQLMySQL protocol (for queries)
4003PostgreSQLPostgreSQL protocol

Vector

Collects logs and metrics from Laminar and forwards to GrepTimeDB.

Configuration in vector.toml:

  • Reads logs from /tmp/laminar
  • Scrapes metrics from Laminar's metrics endpoint
  • Writes to GrepTimeDB

Grafana

Dashboards for monitoring Laminar.

SettingDefault
URLhttp://localhost/grafana
Usernamelaminar
Passwordlaminar
Port3001 (direct), 80 (via nginx)

NGINX

Reverse proxy that routes:

  • /grafana → Grafana
  • /api → Laminar API (on host)

Environment Variables

Create a .env file to customize ports:

# GrepTimeDB ports
GREPTIMEDB_HTTP_PORT=4000
GREPTIMEDB_GRPC_PORT=4001
GREPTIMEDB_MYSQL_PORT=4002
GREPTIMEDB_PG_PORT=4003
 
# Grafana
GRAFANA_PORT=3001
GRAFANA_ADMIN_USER=laminar
GRAFANA_ADMIN_PASSWORD=laminar
 
# NGINX
NGINX_PORT=80

Running Laminar

After starting the observability stack, run Laminar separately:

# From laminar root directory
cargo run

Laminar will automatically send logs and metrics to the observability stack via Vector.


Adding External Services

With Kafka

Create docker-compose.override.yml:

services:
  zookeeper:
    image: confluentinc/cp-zookeeper:7.5.0
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
    networks:
      - laminar-local
 
  kafka:
    image: confluentinc/cp-kafka:7.5.0
    ports:
      - "9092:9092"
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
    depends_on:
      - zookeeper
    networks:
      - laminar-local

With MinIO (S3-compatible)

services:
  minio:
    image: minio/minio
    ports:
      - "9000:9000"
      - "9001:9001"
    environment:
      MINIO_ROOT_USER: minioadmin
      MINIO_ROOT_PASSWORD: minioadmin
    command: server /data --console-address ":9001"
    volumes:
      - minio_data:/data
    networks:
      - laminar-local
 
volumes:
  minio_data:

Troubleshooting

Services not starting

# Check service health
docker compose ps
 
# View detailed logs
docker compose logs --tail=100 greptimedb
 
# Check resource usage
docker stats

GrepTimeDB not healthy

# Check GrepTimeDB logs
docker compose logs greptimedb
 
# Test health endpoint
curl http://localhost:4000/health

Vector not collecting logs

# Check Vector logs
docker compose logs vector
 
# Verify log directory exists
ls -la /tmp/laminar

Port conflicts

# Check what's using a port
lsof -i :4000
 
# Use different ports via .env file
echo "GREPTIMEDB_HTTP_PORT=4100" >> .env
docker compose up -d

Next Steps