[Sub-issue #80-F]: Promtail + Loki + Grafana Stack #86

Closed
opened 2026-02-03 02:19:20 +00:00 by egullickson · 0 comments
Owner

Parent Issue

Refs #80 - Unified Debug Logging System

Scope

Add Promtail, Loki, and Grafana containers for centralized log aggregation.

Files to Create/Modify

  • Create: config/promtail/config.yml
  • Create: config/loki/config.yml
  • Create: config/grafana/datasources/loki.yml
  • Create: config/traefik/dynamic/grafana.yml - IP whitelist middleware
  • Modify: docker-compose.yml - Add 3 new services

Implementation Details

Traefik IP Whitelist (RFC1918 Only)

config/traefik/dynamic/grafana.yml

http:
  middlewares:
    grafana-ipwhitelist:
      ipWhiteList:
        sourceRange:
          - "10.0.0.0/8"
          - "172.16.0.0/12"
          - "192.168.0.0/16"

This restricts Grafana access to private network ranges only. External/public access is blocked.

docker-compose.yml additions

services:
  # ... existing services ...

  mvp-promtail:
    image: grafana/promtail:2.9.0
    container_name: mvp-promtail
    restart: unless-stopped
    volumes:
      - ./config/promtail/config.yml:/etc/promtail/config.yml:ro
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
    command: -config.file=/etc/promtail/config.yml
    networks:
      - backend
    depends_on:
      - mvp-loki
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  mvp-loki:
    image: grafana/loki:2.9.0
    container_name: mvp-loki
    restart: unless-stopped
    volumes:
      - ./config/loki/config.yml:/etc/loki/config.yml:ro
      - mvp_loki_data:/loki
    command: -config.file=/etc/loki/config.yml
    networks:
      - backend
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  mvp-grafana:
    image: grafana/grafana:10.0.0
    container_name: mvp-grafana
    restart: unless-stopped
    environment:
      GF_SECURITY_ADMIN_PASSWORD: admin
      GF_USERS_ALLOW_SIGN_UP: "false"
    volumes:
      - ./config/grafana/datasources:/etc/grafana/provisioning/datasources:ro
      - mvp_grafana_data:/var/lib/grafana
    networks:
      - backend
      - frontend
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.grafana.rule=Host(`logs.motovaultpro.com`)"
      - "traefik.http.routers.grafana.entrypoints=websecure"
      - "traefik.http.routers.grafana.tls=true"
      - "traefik.http.routers.grafana.middlewares=grafana-ipwhitelist@file"
      - "traefik.http.services.grafana.loadbalancer.server.port=3000"
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

volumes:
  # ... existing volumes ...
  mvp_loki_data:
    name: mvp_loki_data
  mvp_grafana_data:
    name: mvp_grafana_data

config/promtail/config.yml

server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /tmp/positions.yaml

clients:
  - url: http://mvp-loki:3100/loki/api/v1/push

scrape_configs:
  - job_name: containers
    docker_sd_configs:
      - host: unix:///var/run/docker.sock
        refresh_interval: 5s
    relabel_configs:
      - source_labels: ['__meta_docker_container_name']
        regex: '/(.*)'
        target_label: 'container'
      - source_labels: ['__meta_docker_container_label_com_docker_compose_service']
        target_label: 'service'

config/loki/config.yml

auth_enabled: false

server:
  http_listen_port: 3100

ingester:
  lifecycler:
    ring:
      kvstore:
        store: inmemory
      replication_factor: 1

schema_config:
  configs:
    - from: 2020-01-01
      store: boltdb-shipper
      object_store: filesystem
      schema: v11
      index:
        prefix: index_
        period: 24h

storage_config:
  boltdb_shipper:
    active_index_directory: /loki/boltdb-shipper-active
    cache_location: /loki/boltdb-shipper-cache
    shared_store: filesystem
  filesystem:
    directory: /loki/chunks

limits_config:
  retention_period: 720h  # 30 days

Security: RFC1918 IP Restriction

Grafana is restricted to private network access only via Traefik IP whitelist middleware:

CIDR Range Description
10.0.0.0/8 10.0.0.0 - 10.255.255.255 Class A private
172.16.0.0/12 172.16.0.0 - 172.31.255.255 Class B private
192.168.0.0/16 192.168.0.0 - 192.168.255.255 Class C private

Access requires:

  • VPN connection to private network, OR
  • Direct access from internal network

Blocked:

  • All public internet access
  • External attackers cannot reach Grafana even with valid credentials

Acceptance Criteria

  • Promtail scrapes all 6 application container logs
  • Loki stores logs with 30-day retention
  • Grafana accessible at https://logs.motovaultpro.com from RFC1918 IPs only
  • Public internet access to Grafana returns 403 Forbidden
  • Query logs by container, level, requestId
  • Container count: 6 → 9 (verified)
  • IP whitelist middleware created in config/traefik/dynamic/grafana.yml

Dependencies

Depends on #80-E (Docker logging config)

Milestone

Milestone 4: Log Aggregation Stack

## Parent Issue Refs #80 - Unified Debug Logging System ## Scope Add Promtail, Loki, and Grafana containers for centralized log aggregation. ## Files to Create/Modify - Create: `config/promtail/config.yml` - Create: `config/loki/config.yml` - Create: `config/grafana/datasources/loki.yml` - Create: `config/traefik/dynamic/grafana.yml` - IP whitelist middleware - Modify: `docker-compose.yml` - Add 3 new services ## Implementation Details ### Traefik IP Whitelist (RFC1918 Only) **config/traefik/dynamic/grafana.yml** ```yaml http: middlewares: grafana-ipwhitelist: ipWhiteList: sourceRange: - "10.0.0.0/8" - "172.16.0.0/12" - "192.168.0.0/16" ``` This restricts Grafana access to private network ranges only. External/public access is blocked. ### docker-compose.yml additions ```yaml services: # ... existing services ... mvp-promtail: image: grafana/promtail:2.9.0 container_name: mvp-promtail restart: unless-stopped volumes: - ./config/promtail/config.yml:/etc/promtail/config.yml:ro - /var/lib/docker/containers:/var/lib/docker/containers:ro - /var/run/docker.sock:/var/run/docker.sock:ro command: -config.file=/etc/promtail/config.yml networks: - backend depends_on: - mvp-loki logging: driver: json-file options: max-size: "10m" max-file: "3" mvp-loki: image: grafana/loki:2.9.0 container_name: mvp-loki restart: unless-stopped volumes: - ./config/loki/config.yml:/etc/loki/config.yml:ro - mvp_loki_data:/loki command: -config.file=/etc/loki/config.yml networks: - backend logging: driver: json-file options: max-size: "10m" max-file: "3" mvp-grafana: image: grafana/grafana:10.0.0 container_name: mvp-grafana restart: unless-stopped environment: GF_SECURITY_ADMIN_PASSWORD: admin GF_USERS_ALLOW_SIGN_UP: "false" volumes: - ./config/grafana/datasources:/etc/grafana/provisioning/datasources:ro - mvp_grafana_data:/var/lib/grafana networks: - backend - frontend labels: - "traefik.enable=true" - "traefik.http.routers.grafana.rule=Host(`logs.motovaultpro.com`)" - "traefik.http.routers.grafana.entrypoints=websecure" - "traefik.http.routers.grafana.tls=true" - "traefik.http.routers.grafana.middlewares=grafana-ipwhitelist@file" - "traefik.http.services.grafana.loadbalancer.server.port=3000" logging: driver: json-file options: max-size: "10m" max-file: "3" volumes: # ... existing volumes ... mvp_loki_data: name: mvp_loki_data mvp_grafana_data: name: mvp_grafana_data ``` ### config/promtail/config.yml ```yaml server: http_listen_port: 9080 grpc_listen_port: 0 positions: filename: /tmp/positions.yaml clients: - url: http://mvp-loki:3100/loki/api/v1/push scrape_configs: - job_name: containers docker_sd_configs: - host: unix:///var/run/docker.sock refresh_interval: 5s relabel_configs: - source_labels: ['__meta_docker_container_name'] regex: '/(.*)' target_label: 'container' - source_labels: ['__meta_docker_container_label_com_docker_compose_service'] target_label: 'service' ``` ### config/loki/config.yml ```yaml auth_enabled: false server: http_listen_port: 3100 ingester: lifecycler: ring: kvstore: store: inmemory replication_factor: 1 schema_config: configs: - from: 2020-01-01 store: boltdb-shipper object_store: filesystem schema: v11 index: prefix: index_ period: 24h storage_config: boltdb_shipper: active_index_directory: /loki/boltdb-shipper-active cache_location: /loki/boltdb-shipper-cache shared_store: filesystem filesystem: directory: /loki/chunks limits_config: retention_period: 720h # 30 days ``` ## Security: RFC1918 IP Restriction Grafana is restricted to private network access only via Traefik IP whitelist middleware: | CIDR | Range | Description | |------|-------|-------------| | `10.0.0.0/8` | 10.0.0.0 - 10.255.255.255 | Class A private | | `172.16.0.0/12` | 172.16.0.0 - 172.31.255.255 | Class B private | | `192.168.0.0/16` | 192.168.0.0 - 192.168.255.255 | Class C private | **Access requires:** - VPN connection to private network, OR - Direct access from internal network **Blocked:** - All public internet access - External attackers cannot reach Grafana even with valid credentials ## Acceptance Criteria - [ ] Promtail scrapes all 6 application container logs - [ ] Loki stores logs with 30-day retention - [ ] Grafana accessible at https://logs.motovaultpro.com **from RFC1918 IPs only** - [ ] Public internet access to Grafana returns 403 Forbidden - [ ] Query logs by container, level, requestId - [ ] Container count: 6 → 9 (verified) - [ ] IP whitelist middleware created in `config/traefik/dynamic/grafana.yml` ## Dependencies Depends on #80-E (Docker logging config) ## Milestone Milestone 4: Log Aggregation Stack
egullickson added the
status
backlog
type
feature
labels 2026-02-03 02:19:41 +00:00
egullickson added
status
in-progress
and removed
status
backlog
labels 2026-02-05 02:15:28 +00:00
egullickson added
status
review
and removed
status
in-progress
labels 2026-02-05 02:17:16 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: egullickson/motovaultpro#86