A lightweight Prometheus exporter for CUPS print server metrics. Built out of necessity when I realized there was no simple way to monitor my homelab print server in Grafana — so I wrote one.
Exposes printer status, job queue depth, and scheduler health via a /metrics HTTP
endpoint on port 9628. Runs as a Docker container alongside your CUPS server using the
CUPS Unix socket directly, so no network config or credentials needed.
| Metric | Type | Description |
|---|---|---|
cups_up |
gauge | CUPS scheduler is reachable (0/1) |
cups_printer_status |
gauge | Printer state: 0=idle, 1=printing, 2=stopped/error |
cups_printer_accepting |
gauge | Printer is accepting jobs (0/1) |
cups_printer_enabled |
gauge | Printer is enabled (0/1) |
cups_jobs_active |
gauge | Active/pending jobs per printer |
cups_jobs_completed |
counter | Completed jobs per printer since CUPS start |
All per-printer metrics include a printer label with the printer name, e.g.
cups_printer_status{printer="Brother-MFC-L3770CDW"}.
- Docker + Docker Compose
- CUPS running on the same host with the Unix socket at
/var/run/cups/cups.sock - Prometheus (to scrape the metrics)
cups-prometheus-exporter/
├── cups_exporter.py # The exporter itself
├── Dockerfile # Builds the container image
├── docker-compose.yml # Deploy with Docker Compose
└── README.md
git clone https://github.com/variablenix/cups-prometheus-exporter.git
cd cups-prometheus-exporterThe image is published to GitHub Container Registry and is the recommended way to deploy:
docker pull ghcr.io/variablenix/cups-prometheus-exporter:latestdocker compose up -d cups-exportercurl http://localhost:9628/metricsYou should see Prometheus-formatted output like:
# HELP cups_up Whether the CUPS scheduler is running
# TYPE cups_up gauge
cups_up 1
# HELP cups_printer_status Printer state: 0=idle, 1=printing, 2=stopped
# TYPE cups_printer_status gauge
cups_printer_status{printer="Brother-MFC-L3770CDW"} 0
Only the cups-exporter service is required. The full docker-compose.yml in this repo
also includes node-exporter and cadvisor for host and container metrics — include or
drop those based on your setup.
The default compose config pulls from GHCR:
cups-exporter:
image: ghcr.io/variablenix/cups-prometheus-exporter:latest
container_name: cups-exporter
restart: unless-stopped
environment:
- CUPS_SERVER=/var/run/cups/cups.sock
volumes:
- /var/run/cups/cups.sock:/var/run/cups/cups.sock:ro
network_mode: host
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9628/metrics"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10sNote:
network_mode: hostis required so the container can reach the CUPS socket and expose metrics on the host network. Theports:directive has no effect in host network mode and can be omitted — Docker will warn you about this, which is expected.
If you want to build the image yourself instead of pulling from GHCR:
docker compose build cups-exporter
docker compose up -d cups-exporterWhen building locally, update the image: line in docker-compose.yml to use a local
tag instead of the GHCR reference:
cups-exporter:
build:
context: .
dockerfile: Dockerfile
image: cups-exporter:local
...FROM python:3.12-slim
RUN apt-get update -qq && \
apt-get install -y -qq --no-install-recommends \
cups-client \
curl && \
rm -rf /var/lib/apt/lists/*
COPY cups_exporter.py /app/cups_exporter.py
EXPOSE 9628
CMD ["python3", "/app/cups_exporter.py", "--port", "9628"]curl is included for the Docker healthcheck. cups-client provides lpstat which
is how the exporter talks to CUPS.
The image is published to ghcr.io/variablenix/cups-prometheus-exporter. To build and
push a new version manually:
# Authenticate with GHCR using a personal access token
echo $GITHUB_TOKEN | docker login ghcr.io -u variablenix --password-stdin
# Build and tag
docker build -t ghcr.io/variablenix/cups-prometheus-exporter:latest .
# Push
docker push ghcr.io/variablenix/cups-prometheus-exporter:latestTo tag a versioned release alongside latest:
docker build \
-t ghcr.io/variablenix/cups-prometheus-exporter:latest \
-t ghcr.io/variablenix/cups-prometheus-exporter:1.0.0 \
.
docker push ghcr.io/variablenix/cups-prometheus-exporter:latest
docker push ghcr.io/variablenix/cups-prometheus-exporter:1.0.0The GITHUB_TOKEN needs write:packages scope. For CI/CD, a GitHub Actions workflow
with packages: write permission can automate this on push to main.
Add this job to your prometheus.yml scrape config:
scrape_configs:
- job_name: 'cups-exporter'
scrape_interval: 30s
metrics_path: /metrics
static_configs:
- targets: ['192.168.70.10:9628']
labels:
role: 'print-server'
use: 'cups'
hostname: 'cups-print'Replace 192.168.70.10 with the IP of your CUPS host. If Prometheus is on the same
machine, use localhost:9628.
By default CUPS may not keep completed job history. To enable it add this to
/etc/cups/cupsd.conf:
MaxJobs 500
PreserveJobHistory Yes
PreserveJobFiles No
Then restart CUPS:
sudo systemctl restart cupsWithout this, cups_jobs_completed will always return 0.
Default port is 9628. Override in docker-compose.yml if needed:
command: ["python3", "/app/cups_exporter.py", "--port", "9999"]To pull the latest image from GHCR and restart:
docker compose pull cups-exporter
docker compose up -d cups-exporterIf you built locally and edited cups_exporter.py or the Dockerfile, rebuild instead:
docker rm -f cups-exporter
docker compose build cups-exporter
docker compose up -d cups-exporter