Learn Observability with the LGTM Stack - Setting Up the Development Environment - Local LGTM Stack
Episode 4 of 36

Learn Observability with the LGTM Stack - Setting Up the Development Environment - Local LGTM Stack

This episode turns theory into practice: running Grafana, Loki, Tempo, and Mimir along with the OpenTelemetry Collector and Grafana Alloy with Docker Compose. You'll also connect data sources and send the first telemetry from a sample application.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Enough theory. Starting this episode, you'll build a local LGTM Stack that actually runs — Grafana, Loki, Tempo, and Mimir, plus the OpenTelemetry Collector and Grafana Alloy as telemetry collectors. Everything runs via Docker Compose so it's easy to reproduce and tear down.

This episode is the practical foundation for almost every following episode. When you're done, you'll have a complete observability environment on localhost, connected as data sources in Grafana, and sending the first telemetry from a sample application. Make sure Docker is running before you start.

Docker Compose Setup

Project Structure

Create the project directory and the compose.yaml file. The component layout:

  • grafana: the main UI on port 3000. loki: log aggregation on port 3100.
  • tempo: the trace backend on port 3200. mimir: the metrics backend on port 9009.
  • otel-collector: the OTLP receiver on ports 4317 and 4318.
  • alloy: log collector from files and Docker containers.
Create the project directory
mkdir -p lgtm-stack
cd lgtm-stack

The cd lgtm-stack command takes you to the main working directory; all configuration files are created here.

Writing the Compose File

compose.yaml - main services
services:
  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    volumes:
      - grafana-data:/var/lib/grafana
  loki:
    image: grafana/loki:latest
    ports:
      - "3100:3100"
  tempo:
    image: grafana/tempo:latest
    ports:
      - "3200:3200"
      - "4317:4317"
  mimir:
    image: grafana/mimir:latest
    command: ["-config.file=/etc/mimir.yaml"]
    ports:
      - "9009:9009"
  otel-collector:
    image: otel/opentelemetry-collector-contrib:latest
    ports:
      - "4317:4317"
      - "4318:4318"
  alloy:
    image: grafana/alloy:latest
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /var/lib/docker/containers:/var/lib/docker/containers
      - ./config.alloy:/etc/alloy/config.alloy
volumes:
  grafana-data:

Important note: the volumes: block at the bottom defines a named volume to store Grafana data. Mimir needs a config file, so prepare a simple mimir.yaml that enables single-binary mode.

Basic Configuration

Mimir Configuration

mimir.yaml - single-binary mode
target: all
multitenancy_enabled: false
blocks_storage:
  backend: filesystem
  filesystem:
    dir: /data/blocks

The configuration above disables multi-tenancy to keep things simple locally and stores data blocks on the filesystem. The target: all concept means one process runs all Mimir components at once — you'll learn how to split them in episode 22.

Connecting Data Sources in Grafana

Grafana provides a provisioning mechanism so data sources are available automatically:

provisioning/datasources.yaml
apiVersion: 1
datasources:
  - name: Mimir
    type: prometheus
    url: http://mimir:9009/prometheus
    access: proxy
  - name: Loki
    type: loki
    url: http://loki:3100
    access: proxy
  - name: Tempo
    type: tempo
    url: http://tempo:3200
    access: proxy

The data source names Mimir, Loki, and Tempo are used as references in the following episodes. Mimir is exposed as type: prometheus because it is Prometheus-compatible.

First Stack Deployment

Running and Verifying

Run the entire stack and wait for the containers to become healthy:

Run the stack
docker compose up -d
docker compose ps

Make sure all services are in Up status. Then verify the health of each backend via the API:

Check health endpoints
curl -s http://localhost:3000/api/health
curl -s http://localhost:3100/ready
curl -s http://localhost:3200/ready
curl -s "http://localhost:9009/ready"

If curl -s http://localhost:3100/ready returns ready, Loki is ready to receive logs. Do the same for the other three endpoints.

Accessing the Grafana UI

Open http://localhost:3000, log in with admin / admin, then open the Connections → Data sources menu. You'll see Mimir, Loki, and Tempo already registered. If not, recheck the provisioning and run docker compose restart grafana.

Tip

Use the Docker service name as the hostname when connecting between containers, for example http://mimir:9009. This name is only valid inside the Compose network, while from the host you use localhost.

Sample Application and First Telemetry

To send the first telemetry, create a small Python application that exports traces via OTLP to the Collector:

Pythonapp.py - sample application
from opentelemetry import trace
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
 
provider = TracerProvider(resource=Resource.create({"service.name": "checkout"}))
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(endpoint="http://localhost:4317")))
trace.set_tracer_provider(provider)
 
tracer = trace.get_tracer("checkout")
with tracer.start_as_current_span("checkout.process"):
    pass
Run the sample application
pip install opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp-proto-grpc
python3 app.py

Run the application several times, then open Grafana → Explore, select the Tempo data source, and you'll find a trace named checkout.process. This is your first telemetry successfully flowing: app → OTLP → Collector → Tempo → Grafana.

Closing

In episode 4 you successfully built a complete LGTM Stack environment locally: running six containers with Docker Compose, configuring single-binary Mimir, connecting Grafana data sources via provisioning, verifying backend health, and sending the first trace from a sample application.

The key takeaways:

  • Docker Compose is the fastest way to run the stack locally.
  • Between containers use the service name, from the host use localhost.
  • Grafana provisioning makes data sources available automatically.
  • Mimir, Loki, and Tempo health endpoints are ready for checks.
  • First telemetry flow: app → OTLP → Collector → Tempo → Grafana.

In the next episode 5 we'll discuss Grafana fundamentals — server and plugin architecture, UI navigation, creating your first dashboard and panels, variables and templating, up to the differences between the PromQL, LogQL, and TraceQL query editors. Your environment is now alive, so let's learn how to read it correctly.

Learn Observability with the LGTM Stack - Setting Up the Development Environment - Local LGTM Stack | Learn Observability with the LGTM Stack