Modern Prometheus exporter for Hetzner Storage Box with comprehensive metrics
Quick Start • Metrics • Installation • Grafana Dashboard • Configuration
A Prometheus exporter for Hetzner Storage Box using the modern Hetzner Cloud API.
- ✅ 15+ comprehensive metrics - Storage usage, access settings, snapshots, and protection status
- 🐳 Multi-architecture Docker - Images for amd64 and arm64
- ☸️ Kubernetes ready - Pre-built manifests included
- 🎯 Lightweight - Under 50MB memory usage
- 📈 Grafana dashboard - 21-panel dashboard ready to import (download)
- 🔒 Bearer token auth - Secure API authentication
- All-in-one solution - Includes Docker images, K8s manifests, and Grafana dashboard
- Additional metrics - Access settings (SSH, Samba, WebDAV, ZFS), snapshot plans, and delete protection
- Production ready - Health checks, structured logging, and comprehensive error handling
You need a Hetzner API token with read permissions:
- Log in to Hetzner Cloud Console
- Navigate to Security → API Tokens
- Create a new token with Read permissions
- Copy the token for configuration
docker-compose up -ddocker run -d \
--name storagebox-exporter \
-p 9509:9509 \
-e HETZNER_TOKEN="your-api-token" \
ghcr.io/crstian19/prometheus-storagebox-exporter:latest
# With token file (recommended for NixOS)
echo "your-api-token" > /run/secrets/hetzner-token
docker run -d \
--name storagebox-exporter \
-p 9509:9509 \
-v /run/secrets/hetzner-token:/run/secrets/hetzner-token:ro \
-e HETZNER_TOKEN_FILE="/run/secrets/hetzner-token" \
ghcr.io/crstian19/prometheus-storagebox-exporter:latest🐳 Multi-Architecture Docker Images
Our Docker images are built for multiple architectures with automatic platform detection:
# Automatically pulls the right image for your platform:
docker pull ghcr.io/crstian19/prometheus-storagebox-exporter:latest
# Available architectures:
# - Linux amd64 (Intel/AMD)
# - Linux arm64 (ARM 64-bit)
# - macOS amd64 (Intel Mac)
# - macOS arm64 (Apple Silicon)🏷️ Available Tags:
latest- Latest release (multi-arch)v0.x.x- Specific version (multi-arch)latest-amd64- Intel/AMD specificlatest-arm64- ARM specific
# Linux amd64
wget https://github.com/crstian19/prometheus-storagebox-exporter/releases/latest/download/prometheus-storagebox-exporter_linux_x86_64.tar.gz
tar xzf prometheus-storagebox-exporter_linux_x86_64.tar.gz
# Run with environment variable
export HETZNER_TOKEN="your-api-token"
./prometheus-storagebox-exporter
# Or with token file (recommended for NixOS)
echo "your-api-token" > /run/secrets/hetzner-token
export HETZNER_TOKEN_FILE="/run/secrets/hetzner-token"
./prometheus-storagebox-exporterOpen http://localhost:9509/metrics to view the exported metrics.
| Variable | Default | Description |
|---|---|---|
HETZNER_TOKEN |
required | Hetzner API token (mutually exclusive with HETZNER_TOKEN_FILE) |
HETZNER_TOKEN_FILE |
optional | Path to file containing Hetzner API token (mutually exclusive with HETZNER_TOKEN) |
LISTEN_ADDRESS |
:9509 |
Address to listen on |
METRICS_PATH |
/metrics |
Path for metrics endpoint |
LOG_LEVEL |
info |
Log level (debug, info, warn, error) |
CACHE_TTL |
0 |
Cache TTL in seconds, 0 to disable (default: disabled) |
CACHE_MAX_SIZE |
0 |
Cache maximum size in bytes, 0 for unlimited |
CACHE_CLEANUP_INTERVAL |
0 |
Cache cleanup interval in seconds, 0 for 10s default |
CACHE_STORAGE_TYPE |
memory |
Cache storage type (memory, redis) |
./prometheus-storagebox-exporter --help
Flags:
--hetzner-token string Hetzner API token (can also be set via HETZNER_TOKEN env var)
--hetzner-token-file string Path to file containing Hetzner API token (can also be set via HETZNER_TOKEN_FILE env var)
--listen-address string Address to listen on for HTTP requests (default ":9509")
--metrics-path string Path under which to expose metrics (default "/metrics")
--log-level string Log level (debug, info, warn, error) (default "info")
--cache-ttl int Cache TTL in seconds, 0 to disable (can also be set via CACHE_TTL env var, default: 0 - disabled)
--cache-max-size int64 Cache maximum size in bytes, 0 for unlimited (can also be set via CACHE_MAX_SIZE env var, default: 0 - unlimited)
--cache-cleanup-interval int Cache cleanup interval in seconds, 0 for default (can also be set via CACHE_CLEANUP_INTERVAL env var, default: 0 - 10s)
--cache-storage-type string Cache storage type (memory, redis) (can also be set via CACHE_STORAGE_TYPE env var, default: memory)
--version Show version information and exit
⚠️ Cache is disabled by default following Prometheus best practices. Usescrape_intervalin Prometheus instead of caching for most use cases.
- Multiple Prometheus instances: Prevent duplicate API calls
- Rate limiting concerns: Reduce API request frequency
- Development/Testing: Minimize API calls during testing
# Enable cache with 60s TTL, 1MB size limit
export CACHE_TTL=60
export CACHE_MAX_SIZE=1048576
export CACHE_CLEANUP_INTERVAL=30
# Enable with memory limit only (10s cleanup interval)
export CACHE_TTL=120
export CACHE_MAX_SIZE=5242880
# Enable with custom cleanup interval
export CACHE_TTL=300
export CACHE_CLEANUP_INTERVAL=60# prometheus.yml
scrape_configs:
- job_name: 'storagebox-exporter'
scrape_interval: 5m # Control scraping frequency
scrape_timeout: 30s
static_configs:
- targets: ['localhost:9509']The exporter exposes 15+ metrics organized in 4 categories:
| Metric | Type | Description | Labels |
|---|---|---|---|
storagebox_disk_quota_bytes |
Gauge | Total storage quota in bytes | id, name, server, location |
storagebox_disk_usage_bytes |
Gauge | Total used diskspace in bytes | id, name, server, location |
storagebox_disk_usage_data_bytes |
Gauge | Diskspace used by files in bytes | id, name, server, location |
storagebox_disk_usage_snapshots_bytes |
Gauge | Diskspace used by snapshots in bytes | id, name, server, location |
| Metric | Type | Description | Labels |
|---|---|---|---|
storagebox_info |
Info | Storage box information (value always 1) | id, name, username, server, location, storage_type, system |
storagebox_status |
Gauge | Current status (1=active, 0=inactive) | id, name, status |
storagebox_created_timestamp |
Gauge | Unix timestamp of creation | id, name |
| Metric | Type | Description | Labels |
|---|---|---|---|
storagebox_access_ssh_enabled |
Gauge | SSH access enabled (1=yes, 0=no) | id, name |
storagebox_access_samba_enabled |
Gauge | Samba/CIFS access enabled (1=yes, 0=no) | id, name |
storagebox_access_webdav_enabled |
Gauge | WebDAV access enabled (1=yes, 0=no) | id, name |
storagebox_access_zfs_enabled |
Gauge | ZFS access enabled (1=yes, 0=no) | id, name |
storagebox_reachable_externally |
Gauge | External reachability (1=yes, 0=no) | id, name |
| Metric | Type | Description | Labels |
|---|---|---|---|
storagebox_snapshot_plan_enabled |
Gauge | Automatic snapshots configured (1=yes, 0=no) | id, name |
storagebox_protection_delete |
Gauge | Delete protection status (1=protected, 0=no) | id, name |
| Metric | Type | Description |
|---|---|---|
storagebox_exporter_scrape_duration_seconds |
Gauge | Duration of the scrape in seconds |
storagebox_exporter_scrape_errors_total |
Counter | Total number of scrape errors |
storagebox_exporter_up |
Gauge | Exporter health status (1=healthy, 0=unhealthy) |
storagebox_exporter_cache_hits_total |
Counter | Total number of cache hits (0 when cache disabled) |
storagebox_exporter_cache_misses_total |
Counter | Total number of cache misses (increments every scrape when cache disabled) |
- 📊 Overview Section: Gauges for disk usage percentage and disk space distribution
- 📈 Time Series Graphs:
- Disk usage over time with quota visualization
- Usage breakdown (Data vs Snapshots) with dual Y-axes
- Disk usage percentage trends
- Storage growth rate analysis (1h intervals)
- 📋 Detailed Table: Complete storage box details with all metrics
- 🔧 Access Status: Visual indicators for SSH, Samba, WebDAV, and ZFS access
- 🛡️ Configuration Info: Snapshot plan and delete protection status
- 📊 Multi-box Support: Variable to filter by specific storage box or view all
The repository includes a complete Docker Compose test with pre-configured dashboard:
# Start all services (Exporter + Prometheus + Grafana)
./test-env.sh
# Or manually:
docker-compose -f docker-compose.dev.yml up -dAccess points:
- 🎯 Grafana Dashboard: http://localhost:3000 (admin/admin) - Dashboard is pre-imported
- 📈 Prometheus: http://localhost:9090
- 🔧 Exporter: http://localhost:9509/metrics
💡 For production: Import the dashboard manually using grafana-dashboard.json
The dashboard includes:
|
|
Access Settings Panels:
- SSH Access Status
- Samba Access Status
- WebDAV Access Status
- ZFS Access Status
- External Reachability Status
Configuration Panels:
- Storage Box Status
- Snapshot Plan Status
- Delete Protection Status
Details Table:
- Storage Box Details (comprehensive table)
Complete docker-compose.yml example with Prometheus and Grafana:
Click to expand Docker Compose
version: '3.8'
services:
# Storage Box Exporter
storagebox-exporter:
image: ghcr.io/crstian19/prometheus-storagebox-exporter:latest
container_name: storagebox-exporter
restart: unless-stopped
ports:
- "9509:9509"
environment:
- HETZNER_TOKEN=${HETZNER_TOKEN}
# Optional cache configuration (uncomment to enable)
# - CACHE_TTL=60
# - CACHE_MAX_SIZE=1048576
# - CACHE_CLEANUP_INTERVAL=30
networks:
- monitoring
# Prometheus
prometheus:
image: prom/prometheus:latest
container_name: prometheus
restart: unless-stopped
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/usr/share/prometheus/console_libraries'
- '--web.console.templates=/usr/share/prometheus/consoles'
- '--web.enable-lifecycle'
networks:
- monitoring
depends_on:
- storagebox-exporter
# Grafana
grafana:
image: grafana/grafana:10.2.0
container_name: grafana
restart: unless-stopped
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=admin
- GF_USERS_ALLOW_SIGN_UP=false
volumes:
- grafana-data:/var/lib/grafana
- ./grafana-provisioning:/etc/grafana/provisioning:ro
networks:
- monitoring
depends_on:
- prometheus
networks:
monitoring:
driver: bridge
volumes:
prometheus-data:
grafana-data:Add to your prometheus.yml:
scrape_configs:
- job_name: 'hetzner-storagebox'
static_configs:
- targets: ['storagebox-exporter:9509']
scrape_interval: 60s
scrape_timeout: 30s# Apply all manifests
kubectl apply -f k8s/
# Check status
kubectl get pods -n monitoringClick to expand Kubernetes YAML
apiVersion: v1
kind: Secret
metadata:
name: storagebox-exporter-secret
namespace: monitoring
type: Opaque
stringData:
hetzner-token: "your-api-token-here"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: storagebox-exporter
namespace: monitoring
labels:
app: storagebox-exporter
spec:
replicas: 1
selector:
matchLabels:
app: storagebox-exporter
template:
metadata:
labels:
app: storagebox-exporter
spec:
containers:
- name: storagebox-exporter
image: ghcr.io/crstian19/prometheus-storagebox-exporter:latest
ports:
- containerPort: 9509
name: metrics
env:
- name: HETZNER_TOKEN
valueFrom:
secretKeyRef:
name: storagebox-exporter-secret
key: hetzner-token
livenessProbe:
httpGet:
path: /health
port: metrics
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /health
port: metrics
initialDelaySeconds: 5
periodSeconds: 10
resources:
requests:
memory: "32Mi"
cpu: "50m"
limits:
memory: "64Mi"
cpu: "100m"
---
apiVersion: v1
kind: Service
metadata:
name: storagebox-exporter
namespace: monitoring
labels:
app: storagebox-exporter
spec:
type: ClusterIP
ports:
- port: 9509
targetPort: metrics
name: metrics
selector:
app: storagebox-exporter
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: storagebox-exporter
namespace: monitoring
labels:
app: storagebox-exporter
spec:
selector:
matchLabels:
app: storagebox-exporter
endpoints:
- port: metrics
interval: 60s
scrapeTimeout: 30s# Build binary
go build -o prometheus-storagebox-exporter .
# Build Docker image
docker build -t prometheus-storagebox-exporter .
# Run tests
go test -v ./...
# Run linter
golangci-lint run.
├── main.go # Application entry point
├── internal/
│ ├── collector/ # Prometheus collector implementation
│ ├── hetzner/ # Hetzner API client
│ └── config/ # Configuration handling
├── grafana-provisioning/ # Grafana dashboard provisioning
├── k8s/ # Kubernetes manifests
├── .github/workflows/ # CI/CD pipelines
├── Dockerfile # Multi-stage Docker build
├── docker-compose.yml # Docker Compose configuration
├── docker-compose.dev.yml # Development environment
└── DESIGN.md # Architecture documentation
Error: "HETZNER_TOKEN or HETZNER_TOKEN_FILE environment variable is required"
Make sure you've set either the HETZNER_TOKEN environment variable or the HETZNER_TOKEN_FILE environment variable.
# Option 1: Direct token
export HETZNER_TOKEN="your-token-here"
./prometheus-storagebox-exporter
# Option 2: Token from file (recommended for NixOS)
echo "your-token-here" > /run/secrets/hetzner-token
export HETZNER_TOKEN_FILE="/run/secrets/hetzner-token"
./prometheus-storagebox-exporterError: "cannot specify both HETZNER_TOKEN and HETZNER_TOKEN_FILE"
You cannot specify both token methods simultaneously. Choose either HETZNER_TOKEN or HETZNER_TOKEN_FILE, not both.
# Correct: Use only one method
export HETZNER_TOKEN="your-token-here"
# or
export HETZNER_TOKEN_FILE="/path/to/token/file"Error: "API request failed with status 401"
Your API token is invalid or has expired. Generate a new token from the Hetzner Cloud Console with Read permissions.
Error: "API request failed with status 403"
Your API token doesn't have sufficient permissions. Ensure the token has at least Read permissions.
No metrics appearing in Prometheus
- Check exporter health:
curl http://localhost:9509/health - Check metrics endpoint:
curl http://localhost:9509/metrics - Verify Prometheus configuration
- Check exporter logs:
docker logs storagebox-exporter
Grafana dashboard shows "No data"
- Verify Prometheus is scraping the exporter
- Check the data source URL in Grafana
- Ensure the storage box variable has values
- Check the time range in Grafana
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Update the collector in
internal/collector/storagebox.go - Add metric definitions
- Update the Grafana dashboard (grafana-dashboard.json) if needed
- Update this README
This project is licensed under the MIT License - see the LICENSE file for details.
- Hetzner Storage Box - Backup storage solution
- Hetzner Cloud API - Modern API infrastructure
- Prometheus - Monitoring toolkit and time series database
- Grafana - Analytics and monitoring platform
- Go - Programming language
- fleaz/prometheus-storagebox-exporter - Original implementation
- Prometheus exporter best practices
- Issues: Report a bug
- Features: Request a feature
- Security: Report a vulnerability
- Documentation: DESIGN.md for architecture details
If you find this project useful and want to support its development, you can donate via PayPal:
Your support helps keep this project maintained and improved! 🙏
⭐ If this project helped you, consider giving it a star!
Made with ❤️ from 🇪🇸 for the Prometheus and Hetzner communities