Advanced Observability

SSO, Tracing, Alerting & Long-Term Storage

SSO Alertmanager Tempo Thanos Project F
0

What Are We Building?

Enterprise-grade observability enhancements

🔗 Requires: Project A (Authentik) + Project E (Grafana Stack)
🚀
Level Up Your Monitoring

Add SSO, distributed tracing, advanced alerting, and unlimited retention!

What We're Adding

🔐
Authentik SSO
Login to Grafana with your Authentik account
📤
More Exporters
Metrics for Vault, Authentik, PostgreSQL
📊
Project Dashboards
Visualize A, B, C, D in Grafana
🚨
Alertmanager
Advanced routing, grouping, silencing
🔗
Tempo Tracing
Distributed request tracing
♾️
Thanos Storage
Unlimited metric retention

Enhanced Architecture

🔐
Authentik
SSO
📊
Grafana
OIDC
🔥
Prometheus
Metrics
♾️
Thanos
Sidecar
💾
S3/Minio
Storage
🔥
Prometheus
Alerts
🚨
Alertmanager
Route
📱
Slack/Email
Notify
⏱️

Time Investment

Component Time Difficulty
Authentik SSO for Grafana 30-45 min Medium
Additional Exporters 30-45 min Easy
Project Dashboards 45-60 min Medium
Alertmanager Setup 30-45 min Medium
Tempo Tracing 45-60 min Advanced
Thanos Long-Term Storage 60-90 min Advanced

Total: 5-7 hours

1

Authentik SSO for Grafana

Login to Grafana with your Authentik account

🔐
Single Sign-On

One login for everything - Grafana, Vault, Teleport, and more!

1
Create OAuth2 Provider in Authentik
⏱️ 5 min
  1. Open Authentik Admin: https://authentik.yourdomain.com/if/admin
  2. Go to Applications → Providers → Create
  3. Select: OAuth2/OpenID Provider
  4. Fill in:
    Field Value
    Name Grafana OIDC Provider
    Authorization flow default-provider-authorization-implicit-consent
    Client type Confidential
    Client ID grafana
    Redirect URIs https://grafana.yourdomain.com/login/generic_oauth
  5. Click Create and copy the Client Secret
2
Create Application in Authentik
⏱️ 2 min
  1. Go to Applications → Applications → Create
  2. Fill in:
    Name Grafana
    Slug grafana
    Provider Grafana OIDC Provider
    Launch URL https://grafana.yourdomain.com
  3. Click Create
3
Configure Grafana OIDC
⏱️ 10 min
⚙️ Update docker-compose.yml
# Update Grafana service in docker-compose.yml

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      # Basic settings
      - GF_SERVER_ROOT_URL=https://grafana.yourdomain.com
      - GF_SERVER_DOMAIN=grafana.yourdomain.com
      
      # Disable default login (optional)
      - GF_AUTH_DISABLE_LOGIN_FORM=false
      - GF_AUTH_DISABLE_SIGNOUT_MENU=false
      
      # OAuth2 / OIDC Configuration
      - GF_AUTH_GENERIC_OAUTH_ENABLED=true
      - GF_AUTH_GENERIC_OAUTH_NAME=Authentik
      - GF_AUTH_GENERIC_OAUTH_CLIENT_ID=grafana
      - GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET=YOUR_CLIENT_SECRET_HERE
      - GF_AUTH_GENERIC_OAUTH_SCOPES=openid profile email
      - GF_AUTH_GENERIC_OAUTH_AUTH_URL=https://authentik.yourdomain.com/application/o/authorize/
      - GF_AUTH_GENERIC_OAUTH_TOKEN_URL=https://authentik.yourdomain.com/application/o/token/
      - GF_AUTH_GENERIC_OAUTH_API_URL=https://authentik.yourdomain.com/application/o/userinfo/
      - GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH=contains(groups[*], 'Grafana Admins') && 'Admin' || contains(groups[*], 'Grafana Editors') && 'Editor' || 'Viewer'
      - GF_AUTH_GENERIC_OAUTH_ALLOW_SIGN_UP=true
      
      # Sign out redirect
      - GF_AUTH_SIGNOUT_REDIRECT_URL=https://authentik.yourdomain.com/application/o/grafana/end-session/
    volumes:
      - ./grafana/data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning
    networks:
      - monitoring
📝
Replace Values!

Replace yourdomain.com with your actual domain and YOUR_CLIENT_SECRET_HERE with the secret from Authentik.

4
Create Groups in Authentik (RBAC)
⏱️ 5 min

Create groups in Authentik for role mapping:

  1. Go to Directory → Groups → Create
  2. Create these groups:
    Group Name Grafana Role
    Grafana Admins Admin (full access)
    Grafana Editors Editor (create dashboards)
    Grafana Viewers Viewer (read-only)
  3. Add yourself to Grafana Admins
5
Restart and Test SSO
⏱️ 3 min
🔄 Restart Grafana
cd ~/monitoring

# Restart Grafana
docker compose up -d grafana

# Check logs
docker logs grafana --tail=50
🎉
SSO Active!

Open Grafana and click "Sign in with Authentik". You'll be redirected to Authentik for login!

2

Additional Exporters

Collect metrics from all your services

📤
Metrics Everywhere

Monitor Authentik, Vault, PostgreSQL, Traefik, and more!

1
PostgreSQL Exporter
⏱️ 10 min

Monitor your PostgreSQL databases (Authentik, etc.):

🐘 Docker Compose Service
# Add to docker-compose.yml

  postgres-exporter:
    image: prometheuscommunity/postgres-exporter:latest
    container_name: postgres-exporter
    restart: unless-stopped
    environment:
      - DATA_SOURCE_NAME=postgresql://username:password@postgres:5432/authentik?sslmode=disable
    ports:
      - "9187:9187"
    networks:
      - monitoring

# Add to prometheus.yml scrape_configs:
  - job_name: 'postgres'
    static_configs:
      - targets: ['postgres-exporter:9187']
2
Blackbox Exporter (HTTP/TCP Probes)
⏱️ 10 min

Monitor endpoint availability and response times:

🌐 Blackbox Configuration
# Create blackbox.yml config
cat > ~/monitoring/blackbox/blackbox.yml << 'EOF'
modules:
  http_2xx:
    prober: http
    timeout: 5s
    http:
      valid_http_versions: ["HTTP/1.1", "HTTP/2.0"]
      valid_status_codes: [200, 201, 301, 302]
      method: GET
      follow_redirects: true
      preferred_ip_protocol: "ip4"
  
  tcp_connect:
    prober: tcp
    timeout: 5s
EOF

# Docker Compose service
  blackbox-exporter:
    image: prom/blackbox-exporter:latest
    container_name: blackbox-exporter
    restart: unless-stopped
    ports:
      - "9115:9115"
    volumes:
      - ./blackbox/blackbox.yml:/etc/blackbox_exporter/config.yml:ro
    command:
      - '--config.file=/etc/blackbox_exporter/config.yml'
    networks:
      - monitoring

# Prometheus scrape config for HTTP probes
  - job_name: 'blackbox-http'
    metrics_path: /probe
    params:
      module: [http_2xx]
    static_configs:
      - targets:
          - https://authentik.yourdomain.com
          - https://vault.yourdomain.com
          - https://grafana.yourdomain.com
          - https://teleport.yourdomain.com
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - source_labels: [__param_target]
        target_label: instance
      - target_label: __address__
        replacement: blackbox-exporter:9115
3
Traefik Metrics
⏱️ 5 min
🚦 Enable Traefik Metrics
# Add to traefik.yml or command line:
metrics:
  prometheus:
    buckets:
      - 0.1
      - 0.3
      - 1.2
      - 5.0
    addEntryPointsLabels: true
    addRoutersLabels: true
    addServicesLabels: true
    entryPoint: metrics

entryPoints:
  metrics:
    address: ":8082"

# Prometheus scrape config
  - job_name: 'traefik'
    static_configs:
      - targets: ['traefik:8082']
📊

Exporter Summary

Exporter Monitors Port
node-exporter Linux system (CPU, memory, disk) 9100
cAdvisor Docker containers 8080
postgres-exporter PostgreSQL databases 9187
blackbox-exporter HTTP/TCP endpoints 9115
traefik Reverse proxy metrics 8082
3

Project Dashboards

Visualize all your projects in Grafana

📊
Unified Visibility

One dashboard to rule them all - see Authentik, Vault, Teleport, and Wazuh!

1
Home Lab Overview Dashboard
⏱️ 15 min

Create a master dashboard showing all projects:

📈 Dashboard JSON (Import)
# Create this file and import into Grafana
# Dashboards → Import → Upload JSON

{
  "title": "Home Lab Overview",
  "uid": "homelab-overview",
  "tags": ["homelab", "overview"],
  "panels": [
    {
      "title": "Service Status",
      "type": "stat",
      "gridPos": { "x": 0, "y": 0, "w": 24, "h": 4 },
      "targets": [{
        "expr": "up{job=~\"blackbox-http\"}",
        "legendFormat": "{{instance}}"
      }],
      "fieldConfig": {
        "defaults": {
          "mappings": [
            { "type": "value", "options": { "0": { "text": "DOWN", "color": "red" } } },
            { "type": "value", "options": { "1": { "text": "UP", "color": "green" } } }
          ]
        }
      }
    },
    {
      "title": "CPU Usage",
      "type": "gauge",
      "gridPos": { "x": 0, "y": 4, "w": 6, "h": 6 },
      "targets": [{
        "expr": "100 - (avg(irate(node_cpu_seconds_total{mode=\"idle\"}[5m])) * 100)"
      }]
    },
    {
      "title": "Memory Usage",
      "type": "gauge",
      "gridPos": { "x": 6, "y": 4, "w": 6, "h": 6 },
      "targets": [{
        "expr": "(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100"
      }]
    },
    {
      "title": "Disk Usage",
      "type": "gauge",
      "gridPos": { "x": 12, "y": 4, "w": 6, "h": 6 },
      "targets": [{
        "expr": "(1 - (node_filesystem_avail_bytes{mountpoint=\"/\"} / node_filesystem_size_bytes{mountpoint=\"/\"})) * 100"
      }]
    },
    {
      "title": "Container Count",
      "type": "stat",
      "gridPos": { "x": 18, "y": 4, "w": 6, "h": 6 },
      "targets": [{
        "expr": "count(container_last_seen{name!=\"\"})"
      }]
    }
  ]
}
2
Authentik Monitoring Dashboard
⏱️ 10 min

Authentik exposes Prometheus metrics natively:

🔑 Enable Authentik Metrics
# Add to Authentik docker-compose environment:
AUTHENTIK_METRICS__ENABLED=true

# Prometheus scrape config
  - job_name: 'authentik'
    static_configs:
      - targets: ['authentik-server:9300']

# Key metrics to monitor:
# - authentik_flows_execution_count
# - authentik_outpost_connection_count
# - authentik_policies_execution_time
# - authentik_stages_execution_time
3
HashiCorp Vault Dashboard
⏱️ 10 min
🔐 Vault Telemetry Configuration
# Add to vault config (vault-config.hcl):
telemetry {
  prometheus_retention_time = "60s"
  disable_hostname = true
}

# Prometheus scrape config
  - job_name: 'vault'
    metrics_path: '/v1/sys/metrics'
    params:
      format: ['prometheus']
    scheme: https
    tls_config:
      insecure_skip_verify: true
    bearer_token: YOUR_VAULT_TOKEN
    static_configs:
      - targets: ['vault:8200']

# Key metrics:
# - vault_core_unsealed (1 = unsealed)
# - vault_token_count
# - vault_secret_lease_count
# - vault_audit_log_request_count
4
Wazuh Security Dashboard
⏱️ 15 min

Send Wazuh alerts to Loki for correlation:

🛡️ Wazuh to Loki Integration
# Add Wazuh alerts to Promtail config:

scrape_configs:
  - job_name: wazuh-alerts
    static_configs:
      - targets:
          - localhost
        labels:
          job: wazuh
          __path__: /var/ossec/logs/alerts/alerts.json

    pipeline_stages:
      - json:
          expressions:
            level: rule.level
            description: rule.description
            agent: agent.name
      - labels:
          level:
          agent:

# Sample LogQL queries for Wazuh:
# High severity alerts
{job="wazuh"} | json | level >= 10

# Authentication failures
{job="wazuh"} |= "authentication" |= "failed"
4

Alertmanager

Advanced alert routing and management

🚨
Smart Alert Routing

Route critical alerts to Slack, warnings to email, group by service!

1
Deploy Alertmanager
⏱️ 10 min
🐳 Docker Compose Service
# Add to docker-compose.yml

  alertmanager:
    image: prom/alertmanager:latest
    container_name: alertmanager
    restart: unless-stopped
    ports:
      - "9093:9093"
    volumes:
      - ./alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
      - ./alertmanager/data:/alertmanager
    command:
      - '--config.file=/etc/alertmanager/alertmanager.yml'
      - '--storage.path=/alertmanager'
    networks:
      - monitoring
2
Configure Alert Routing
⏱️ 15 min
⚙️ alertmanager.yml
cat > ~/monitoring/alertmanager/alertmanager.yml << 'EOF'
global:
  resolve_timeout: 5m
  slack_api_url: 'YOUR_SLACK_WEBHOOK_URL'

route:
  receiver: 'default-receiver'
  group_by: ['alertname', 'severity', 'service']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  
  routes:
    # Critical alerts -> Slack immediately
    - match:
        severity: critical
      receiver: 'slack-critical'
      group_wait: 10s
      repeat_interval: 1h
    
    # High alerts -> Slack
    - match:
        severity: high
      receiver: 'slack-high'
    
    # Warning alerts -> Email
    - match:
        severity: warning
      receiver: 'email-warnings'
    
    # Security alerts -> Dedicated channel
    - match:
        service: wazuh
      receiver: 'slack-security'

receivers:
  - name: 'default-receiver'
    slack_configs:
      - channel: '#alerts'
        send_resolved: true
        title: '{{ .Status | toUpper }}: {{ .CommonAnnotations.summary }}'
        text: '{{ range .Alerts }}{{ .Annotations.description }}{{ end }}'

  - name: 'slack-critical'
    slack_configs:
      - channel: '#alerts-critical'
        send_resolved: true
        color: '{{ if eq .Status "firing" }}danger{{ else }}good{{ end }}'
        title: '🚨 CRITICAL: {{ .CommonAnnotations.summary }}'
        text: |
          *Alert:* {{ .CommonLabels.alertname }}
          *Description:* {{ .CommonAnnotations.description }}
          *Severity:* {{ .CommonLabels.severity }}
          {{ range .Alerts }}
          *Details:*
          {{ range .Labels.SortedPairs }} - {{ .Name }}: {{ .Value }}
          {{ end }}
          {{ end }}

  - name: 'slack-high'
    slack_configs:
      - channel: '#alerts'
        send_resolved: true
        color: 'warning'
        title: '⚠️ HIGH: {{ .CommonAnnotations.summary }}'

  - name: 'slack-security'
    slack_configs:
      - channel: '#security-alerts'
        send_resolved: true
        color: 'danger'
        title: '🛡️ Security: {{ .CommonAnnotations.summary }}'

  - name: 'email-warnings'
    email_configs:
      - to: 'alerts@yourdomain.com'
        from: 'alertmanager@yourdomain.com'
        smarthost: 'smtp.gmail.com:587'
        auth_username: 'your-email@gmail.com'
        auth_password: 'your-app-password'
        send_resolved: true

inhibit_rules:
  - source_match:
      severity: 'critical'
    target_match:
      severity: 'warning'
    equal: ['alertname', 'service']
EOF
3
Create Alert Rules
⏱️ 15 min
📜 alert-rules.yml
cat > ~/monitoring/prometheus/alert-rules.yml << 'EOF'
groups:
  - name: infrastructure
    rules:
      # High CPU
      - alert: HighCPU
        expr: 100 - (avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
        for: 5m
        labels:
          severity: warning
          service: infrastructure
        annotations:
          summary: "High CPU usage on {{ $labels.instance }}"
          description: "CPU usage is {{ $value | printf \"%.1f\" }}%"

      # Critical CPU
      - alert: CriticalCPU
        expr: 100 - (avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 95
        for: 2m
        labels:
          severity: critical
          service: infrastructure
        annotations:
          summary: "Critical CPU on {{ $labels.instance }}"
          description: "CPU at {{ $value | printf \"%.1f\" }}% - immediate attention needed"

      # High Memory
      - alert: HighMemory
        expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 85
        for: 5m
        labels:
          severity: warning
          service: infrastructure
        annotations:
          summary: "High memory usage on {{ $labels.instance }}"
          description: "Memory usage is {{ $value | printf \"%.1f\" }}%"

      # Disk Almost Full
      - alert: DiskAlmostFull
        expr: (1 - (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"})) * 100 > 85
        for: 5m
        labels:
          severity: critical
          service: infrastructure
        annotations:
          summary: "Disk almost full on {{ $labels.instance }}"
          description: "Disk usage is {{ $value | printf \"%.1f\" }}%"

  - name: services
    rules:
      # Service Down
      - alert: ServiceDown
        expr: up{job=~"blackbox-http"} == 0
        for: 1m
        labels:
          severity: critical
          service: "{{ $labels.instance }}"
        annotations:
          summary: "Service {{ $labels.instance }} is DOWN"
          description: "The service has been unreachable for more than 1 minute"

      # High Response Time
      - alert: HighResponseTime
        expr: probe_http_duration_seconds{phase="transfer"} > 2
        for: 5m
        labels:
          severity: warning
          service: "{{ $labels.instance }}"
        annotations:
          summary: "Slow response from {{ $labels.instance }}"
          description: "Response time is {{ $value | printf \"%.2f\" }}s"

  - name: containers
    rules:
      # Container Down
      - alert: ContainerDown
        expr: absent(container_last_seen{name=~".+"})
        for: 1m
        labels:
          severity: critical
          service: docker
        annotations:
          summary: "Container {{ $labels.name }} is down"
          description: "Container has stopped running"

      # High Container Memory
      - alert: ContainerHighMemory
        expr: (container_memory_usage_bytes{name!=""} / container_spec_memory_limit_bytes{name!=""}) * 100 > 80
        for: 5m
        labels:
          severity: warning
          service: docker
        annotations:
          summary: "Container {{ $labels.name }} high memory"
          description: "Memory usage is {{ $value | printf \"%.1f\" }}%"
EOF

# Update prometheus.yml to include rules:
rule_files:
  - /etc/prometheus/alert-rules.yml

alerting:
  alertmanagers:
    - static_configs:
        - targets: ['alertmanager:9093']
5

Tempo Distributed Tracing

Track requests across services

🔗
End-to-End Visibility

See exactly where time is spent as requests flow through your stack!

1
Deploy Grafana Tempo
⏱️ 15 min
📦 Tempo Configuration
# Create tempo config
mkdir -p ~/monitoring/tempo

cat > ~/monitoring/tempo/tempo.yml << 'EOF'
server:
  http_listen_port: 3200

distributor:
  receivers:
    jaeger:
      protocols:
        thrift_http:
          endpoint: 0.0.0.0:14268
        grpc:
          endpoint: 0.0.0.0:14250
    otlp:
      protocols:
        http:
          endpoint: 0.0.0.0:4318
        grpc:
          endpoint: 0.0.0.0:4317

ingester:
  trace_idle_period: 10s
  max_block_bytes: 1_000_000
  max_block_duration: 5m

compactor:
  compaction:
    compaction_window: 1h
    max_block_bytes: 100_000_000
    block_retention: 48h
    compacted_block_retention: 1h

storage:
  trace:
    backend: local
    local:
      path: /tmp/tempo/blocks
    wal:
      path: /tmp/tempo/wal
EOF

# Docker Compose service
  tempo:
    image: grafana/tempo:latest
    container_name: tempo
    restart: unless-stopped
    command: ["-config.file=/etc/tempo/tempo.yml"]
    ports:
      - "3200:3200"   # Tempo API
      - "4317:4317"   # OTLP gRPC
      - "4318:4318"   # OTLP HTTP
      - "14268:14268" # Jaeger HTTP
    volumes:
      - ./tempo/tempo.yml:/etc/tempo/tempo.yml:ro
      - ./tempo/data:/tmp/tempo
    networks:
      - monitoring
2
Add Tempo Data Source to Grafana
⏱️ 5 min
📊 Grafana Datasource Config
# Add to datasources.yml

  - name: Tempo
    type: tempo
    access: proxy
    url: http://tempo:3200
    editable: false
    jsonData:
      httpMethod: GET
      tracesToLogs:
        datasourceUid: loki
        tags: ['job', 'instance']
        mappedTags: [{ key: 'service.name', value: 'service' }]
        mapTagNamesEnabled: true
        filterByTraceID: true
      tracesToMetrics:
        datasourceUid: prometheus
        tags: [{ key: 'service.name', value: 'service' }]
      serviceMap:
        datasourceUid: prometheus
      nodeGraph:
        enabled: true
💡
Tracing Requires Instrumentation

Your applications need to send traces to Tempo. Use OpenTelemetry SDKs or Jaeger clients to instrument your code.

6

Thanos Long-Term Storage

Unlimited metric retention with object storage

♾️
Store Metrics Forever

Keep years of metrics in cheap object storage (S3/MinIO)

1
Deploy MinIO (Object Storage)
⏱️ 10 min
💾 MinIO Docker Service
# Add MinIO to docker-compose.yml

  minio:
    image: minio/minio:latest
    container_name: minio
    restart: unless-stopped
    ports:
      - "9000:9000"
      - "9001:9001"
    environment:
      - MINIO_ROOT_USER=minioadmin
      - MINIO_ROOT_PASSWORD=YOUR_SECURE_PASSWORD
    command: server /data --console-address ":9001"
    volumes:
      - ./minio/data:/data
    networks:
      - monitoring

# Create bucket for Thanos
# Access MinIO console at http://SERVER_IP:9001
# Create bucket named "thanos"
2
Deploy Thanos Sidecar
⏱️ 15 min
📤 Thanos Sidecar Configuration
# Create bucket config
cat > ~/monitoring/thanos/bucket.yml << 'EOF'
type: S3
config:
  bucket: thanos
  endpoint: minio:9000
  access_key: minioadmin
  secret_key: YOUR_SECURE_PASSWORD
  insecure: true
EOF

# Update Prometheus to allow Thanos access
# Add to prometheus command:
  prometheus:
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--storage.tsdb.retention.time=2h'  # Short retention, Thanos handles long-term
      - '--storage.tsdb.min-block-duration=2h'
      - '--storage.tsdb.max-block-duration=2h'
      - '--web.enable-lifecycle'
      - '--web.enable-admin-api'

# Add Thanos Sidecar
  thanos-sidecar:
    image: quay.io/thanos/thanos:latest
    container_name: thanos-sidecar
    restart: unless-stopped
    command:
      - sidecar
      - --tsdb.path=/prometheus
      - --prometheus.url=http://prometheus:9090
      - --objstore.config-file=/etc/thanos/bucket.yml
      - --grpc-address=0.0.0.0:10901
      - --http-address=0.0.0.0:10902
    volumes:
      - ./prometheus/data:/prometheus:ro
      - ./thanos/bucket.yml:/etc/thanos/bucket.yml:ro
    depends_on:
      - prometheus
      - minio
    networks:
      - monitoring
3
Deploy Thanos Query & Store
⏱️ 15 min
🔍 Thanos Query & Store Services
# Add to docker-compose.yml

  thanos-store:
    image: quay.io/thanos/thanos:latest
    container_name: thanos-store
    restart: unless-stopped
    command:
      - store
      - --data-dir=/tmp/thanos/store
      - --objstore.config-file=/etc/thanos/bucket.yml
      - --grpc-address=0.0.0.0:10901
      - --http-address=0.0.0.0:10902
    volumes:
      - ./thanos/bucket.yml:/etc/thanos/bucket.yml:ro
      - ./thanos/store:/tmp/thanos/store
    depends_on:
      - minio
    networks:
      - monitoring

  thanos-query:
    image: quay.io/thanos/thanos:latest
    container_name: thanos-query
    restart: unless-stopped
    ports:
      - "10902:10902"
    command:
      - query
      - --http-address=0.0.0.0:10902
      - --grpc-address=0.0.0.0:10901
      - --store=thanos-sidecar:10901
      - --store=thanos-store:10901
    depends_on:
      - thanos-sidecar
      - thanos-store
    networks:
      - monitoring

  thanos-compactor:
    image: quay.io/thanos/thanos:latest
    container_name: thanos-compactor
    restart: unless-stopped
    command:
      - compact
      - --data-dir=/tmp/thanos/compact
      - --objstore.config-file=/etc/thanos/bucket.yml
      - --wait
    volumes:
      - ./thanos/bucket.yml:/etc/thanos/bucket.yml:ro
      - ./thanos/compact:/tmp/thanos/compact
    depends_on:
      - minio
    networks:
      - monitoring
4
Point Grafana to Thanos
⏱️ 5 min
📊 Update Grafana Datasource
# Update datasources.yml - change Prometheus URL to Thanos

  - name: Prometheus
    type: prometheus
    access: proxy
    url: http://thanos-query:10902  # Changed from prometheus:9090
    isDefault: true
    editable: false
    jsonData:
      httpMethod: POST
      manageAlerts: true
      prometheusType: Thanos
      prometheusVersion: 0.31.0
🎉
Unlimited Retention!

Metrics are now stored in MinIO/S3. Query years of data seamlessly through Grafana!

7

Testing & Verification

Verify all enhancements work

🧪
Final Verification

Test every component of your advanced observability stack

Verification Checklist

  • SSO: Login to Grafana via Authentik
  • SSO: Correct role assigned (Admin/Editor/Viewer)
  • Exporters: All new targets UP in Prometheus
  • Dashboards: Home Lab Overview displays data
  • Alertmanager: Test alert routes to correct channel
  • Tempo: Traces visible in Grafana Explore
  • Thanos: Query returns data older than 2 hours
  • MinIO: Thanos bucket has data
Quick Health Checks
🔍 Test Commands
# Check all services
docker compose ps

# Test Alertmanager
curl http://localhost:9093/-/healthy

# Test Tempo
curl http://localhost:3200/ready

# Test Thanos Query
curl http://localhost:10902/-/healthy

# Check Thanos stores
curl http://localhost:10902/api/v1/stores

# Test MinIO
curl http://localhost:9000/minio/health/live

# Send test alert
curl -X POST http://localhost:9093/api/v1/alerts \
  -H "Content-Type: application/json" \
  -d '[{"labels":{"alertname":"TestAlert","severity":"warning"},"annotations":{"summary":"Test alert"}}]'
🎉

Congratulations!

Enterprise-grade observability achieved!

🏆
Advanced Observability Expert

You've built a production-ready monitoring stack with SSO, tracing, and unlimited retention!

📊

Skills Demonstrated

  • OIDC/OAuth2 SSO integration
  • Multi-exporter Prometheus deployment
  • Custom dashboard creation
  • Advanced alert routing with Alertmanager
  • Distributed tracing with Tempo
  • Long-term storage with Thanos
  • Object storage (S3/MinIO)
🏗️

Complete Portfolio Summary

Project Component Skills
A Authentik SSO Identity, MFA, OIDC/SAML
B HashiCorp Vault Secrets, JIT Access, Dynamic Creds
C Teleport ZTNA, SSH Certificates, RBAC
D Wazuh SIEM Security Monitoring, XDR, Compliance
E Grafana Stack Metrics, Logs, Dashboards
F Advanced Observability SSO, Tracing, Long-Term Storage