Grafana Setup
Overview¶
Grafana is deployed as a Podman container in the test/development environment and managed programmatically via HTTP API. This enables debugging and creating dashboard panels without manual UI interaction.
Note: This documentation focuses on concepts and patterns, not specific IP addresses. Examples use localhost:3000 for consistency.
Environment Setup¶
Local Grafana Container¶
Container Details:
- Container name: grafana-infra or grafana-svc
- Internal endpoint: http://127.0.0.1:3000
- Optional proxy: HTTPS access via Traefik reverse proxy
- Admin password: ~/.secrets/grafana.admin.pass
API Authentication:
Data Source Endpoints¶
InfluxDB: - Internal: http://localhost:8086 - Remote: Use WireGuard tunnel or direct hostname
Loki: - Internal: http://localhost:3100 - Remote: Use WireGuard tunnel or direct hostname
Useful Grafana API Endpoints¶
List All Dashboards¶
curl -s -u admin:$(cat ~/.secrets/grafana.admin.pass) \
http://localhost:3000/api/search?type=dash-db | python3 -m json.tool
Output:
[
{
"id": 1,
"uid": "fail2ban",
"title": "Fail2ban Activity",
"url": "/d/fail2ban/fail2ban-activity",
"type": "dash-db"
}
]
List Data Sources¶
curl -s -u admin:$(cat ~/.secrets/grafana.admin.pass) \
http://localhost:3000/api/datasources | python3 -m json.tool
Output:
[
{
"id": 1,
"uid": "influxdb-uid",
"name": "InfluxDB",
"type": "influxdb",
"url": "http://localhost:8086",
"isDefault": false
},
{
"id": 2,
"uid": "loki-uid",
"name": "Loki",
"type": "loki",
"url": "http://localhost:3100",
"isDefault": true
}
]
Get Dashboard by UID¶
curl -s -u admin:$(cat ~/.secrets/grafana.admin.pass) \
http://localhost:3000/api/dashboards/uid/fail2ban | jq '.dashboard.title'
Output:
Get Organization Info¶
Output:
{
"id": 1,
"name": "Main Org.",
"address": {
"address1": "",
"address2": "",
"city": "",
"zipCode": "",
"state": "",
"country": ""
}
}
Create Dashboard¶
curl -s -X POST \
-u admin:$(cat ~/.secrets/grafana.admin.pass) \
-H "Content-Type: application/json" \
-d @dashboard.json \
http://localhost:3000/api/dashboards/db
Response:
{
"id": 5,
"uid": "new-dashboard-uid",
"url": "/d/new-dashboard-uid/dashboard-name",
"status": "success",
"version": 1,
"slug": "dashboard-name"
}
Update Dashboard¶
curl -s -X POST \
-u admin:$(cat ~/.secrets/grafana.admin.pass) \
-H "Content-Type: application/json" \
-d @dashboard-updated.json \
http://localhost:3000/api/dashboards/db
Payload format:
Delete Dashboard¶
curl -s -X DELETE \
-u admin:$(cat ~/.secrets/grafana.admin.pass) \
http://localhost:3000/api/dashboards/uid/DASHBOARD_UID
Connection Verification¶
Test that Grafana and data sources are accessible before creating or modifying dashboards.
Verify Grafana API¶
Verify InfluxDB Connection¶
Verify Loki Connection¶
Test Data Source Query¶
InfluxDB:
curl -s -X POST http://localhost:8086/api/v2/query \
-H "Authorization: Token YOUR_TOKEN" \
-H "Content-Type: application/vnd.flux" \
-d 'from(bucket: "telegraf") |> range(start: -5m) |> limit(n: 1)'
Loki:
curl -s -G http://localhost:3100/loki/api/v1/query \
--data-urlencode 'query={hostname="ispconfig3"}' \
--data-urlencode 'limit=1'
Python Helper Functions¶
Grafana API Client¶
#!/usr/bin/env python3
from pathlib import Path
import subprocess
import json
class GrafanaAPI:
def __init__(self, url="http://localhost:3000"):
self.url = url
self.password = Path.home() / '.secrets' / 'grafana.admin.pass'
self.auth = f"admin:{self.password.read_text().strip()}"
def get_dashboards(self):
"""List all dashboards"""
cmd = ['curl', '-s', '-u', self.auth,
f'{self.url}/api/search?type=dash-db']
result = subprocess.run(cmd, capture_output=True, text=True)
return json.loads(result.stdout)
def get_dashboard(self, uid):
"""Get dashboard by UID"""
cmd = ['curl', '-s', '-u', self.auth,
f'{self.url}/api/dashboards/uid/{uid}']
result = subprocess.run(cmd, capture_output=True, text=True)
return json.loads(result.stdout)
def create_dashboard(self, dashboard_json, message="Created via API"):
"""Create or update dashboard"""
payload = {
"dashboard": dashboard_json,
"message": message,
"overwrite": True
}
# Write payload to temp file
payload_file = Path('/tmp/grafana-payload.json')
payload_file.write_text(json.dumps(payload, indent=2))
cmd = ['curl', '-s', '-X', 'POST',
'-u', self.auth,
'-H', 'Content-Type: application/json',
'-d', f'@{payload_file}',
f'{self.url}/api/dashboards/db']
result = subprocess.run(cmd, capture_output=True, text=True)
return json.loads(result.stdout)
# Usage
api = GrafanaAPI()
dashboards = api.get_dashboards()
for d in dashboards:
print(f"{d['uid']}: {d['title']}")
Data Source Tester¶
#!/usr/bin/env python3
import subprocess
import json
def test_loki_query(query, limit=5):
"""Test Loki query and return results"""
cmd = [
'curl', '-s', '-G', 'http://localhost:3100/loki/api/v1/query',
'--data-urlencode', f'query={query}',
'--data-urlencode', f'limit={limit}'
]
result = subprocess.run(cmd, capture_output=True, text=True)
data = json.loads(result.stdout)
if data['status'] == 'success' and data['data']['result']:
print(f"✅ Query works! {len(data['data']['result'])} results")
return data['data']['result']
else:
print(f"❌ Query failed: {data.get('error', 'no results')}")
return None
# Usage
test_loki_query('{service_type="fail2ban"}')
Deployment Considerations¶
Container vs. Production¶
Development/Test (Podman container):
- Grafana runs as local container
- Data sources on localhost or via WireGuard
- Admin password in ~/.secrets/
- HTTP only (no TLS)
Production (Optional proxy): - Same Grafana container - Optional Traefik reverse proxy for HTTPS - DNS name points to proxy - TLS certificates managed by Traefik
Key Point: The API access pattern is the same - always use http://localhost:3000 for API calls, regardless of external access method.
Data Source Configuration¶
Local Development:
Distributed Deployment:
# Remote data sources via WireGuard
influxdb_url: "http://10.10.0.11:8086" # monitor11 via VPN
loki_url: "http://10.10.0.11:3100"
Concepts Apply to Both: - Token-based authentication - HTTP API access - Dashboard JSON structure - Query syntax (Flux, LogQL)
Reference¶
For programmatic dashboard creation: - See CLAUDE.md "Creating Grafana Dashboards Programmatically" - See CLAUDE.md "Grafana Dashboard Development Workflow"
For troubleshooting blank graphs: - See CLAUDE.md "Troubleshooting Blank Grafana Graphs"