Getting started with Docker-based Prometheus
Running Prometheus with Docker for monitoring developers reduces setup friction from hours to minutes. Instead of managing binaries, dependencies, and system configurations, you spin up a container, drop in a config file, and start collecting metrics. This approach scales beautifully across your local environment, CI/CD pipelines, and staging clusters.
In this guide, we'll walk through setting up Prometheus in Docker, configuring scrape targets, exploring the query interface, and extending the stack with Grafana and exporters. By the end, you'll have a monitoring foundation that mirrors what runs in production—without the operational overhead.
Why Docker matters for monitoring setup
Docker eliminates version mismatches and platform drift. A developer on macOS, another on Linux, and a CI runner all execute identical Prometheus code inside the same container image. No "works on my machine" surprises when metrics stop flowing. You also gain instant repeatability: tear down and rebuild your entire monitoring stack in seconds.
For teams using Kubernetes or container orchestration, Docker-based Prometheus becomes your local test bed. You validate scrape configs, alert rules, and Grafana dashboards before pushing to staging. The muscle memory transfers directly: a docker-compose file is nearly identical to a Kubernetes StatefulSet configuration in terms of intent.
Step 1: Creating a Prometheus configuration file
Prometheus reads a YAML configuration file that defines global settings and scrape targets. This file is the single source of truth for what your monitoring system observes. Let's build a practical config that targets a local API server.
global:
scrape_interval: 15s
scrape_timeout: 10s
evaluation_interval: 15s
scrape_configs:
- job_name: 'api'
static_configs:
- targets: ['api:3000']
labels:
environment: 'local'
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']What each section does:
globalsets defaults for all jobs.scrape_interval: 15smeans Prometheus pulls metrics every 15 seconds from each target.scrape_timeout: 10sterminates slow scrapes after 10 seconds to prevent hanging.scrape_configslists all monitoring targets. The first job targets your API on port 3000; the second monitors Prometheus itself.static_configspairs target addresses with optional labels. Labels help you filter and organize metrics in queries.
Save this file as prometheus.yml in your project root. When you run the container, you'll mount this file into the Prometheus container so it reads your configuration on startup.
A real-world scenario: your team develops a NestJS API that exposes Prometheus metrics on /metrics at port 3000. Your microservice orchestrator (Docker Compose, for now) runs it as a service named api. Prometheus uses Docker's internal DNS to resolve api:3000 and scrapes metrics every 15 seconds. No hardcoded IPs; the container network handles service discovery.
Step 2: Running Prometheus in Docker
With your config file ready, launch Prometheus using the official image:
docker run \
-p 9090:9090 \
-v ./prometheus.yml:/etc/prometheus/prometheus.yml \
prom/prometheus:latestBreaking down the command:
-p 9090:9090maps the container's port 9090 (Prometheus default) to your host, so you access the UI atlocalhost:9090.-v ./prometheus.yml:/etc/prometheus/prometheus.ymlbinds your local config file into the container. Any changes you save locally are immediately visible inside the container on the next scrape cycle (no restart required for config reloads in some cases).prom/prometheus:latestspecifies the official Prometheus image.
Run this command in your terminal. Within a few seconds, you'll see Prometheus start up and begin scraping. Check the logs—they'll show:
msg="Server is ready to receive web requests."If your API isn't running yet, Prometheus won't fail; it logs a scrape error and retries after 15 seconds. This resilience is intentional: monitoring systems must tolerate brief target downtime.
To verify startup, check the Prometheus UI at http://localhost:9090. You should see the web interface with the graph explorer and status pages.
Step 3: Exploring metrics with the Prometheus UI
The Prometheus web UI is your playground for understanding what metrics are available and how to query them. Navigate to http://localhost:9090/graph to open the expression browser.
Start with the simplest possible query: up. This built-in metric tracks scrape status for each job. Enter it in the text box and click Execute.
Interpreting the result: Prometheus returns a table with rows for each scrape target. If your API is running, you'll see:
up{instance="api:3000", job="api"} 1
up{instance="localhost:9090", job="prometheus"} 1A value of 1 means the target is healthy and reachable. 0 means the scrape failed. This single metric is invaluable for alerting: if up == 0, your target is down.
Next, try querying actual metrics from your API. If you're running a Node.js or .NET app with Prometheus instrumentation, common metrics include:
http_requests_total— cumulative HTTP request count by method and status.http_request_duration_seconds— histogram of request latencies.process_resident_memory_bytes— process memory usage.
Query rate(http_requests_total[5m]) to see the request rate over the last 5 minutes. Prometheus calculates the derivative automatically. This pattern—rate() over a time window—is foundational for alerting on trends rather than absolute values.
Use the Graph tab to visualize metrics over time. Zoom, pan, and inspect individual data points. This interactive exploration builds your intuition for metric behavior before you commit to dashboards and alerts.
Step 4: Building a multi-service stack with Docker Compose
Running Prometheus alone is useful; adding Grafana and exporters creates a complete observability platform. Docker Compose orchestrates multiple services with shared networking, volumes, and environment variables—all in one declarative file.
version: '3.8'
services:
prometheus:
image: prom/prometheus:latest
container_name: prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
networks:
- monitoring
depends_on:
- api
grafana:
image: grafana/grafana:latest
container_name: grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana_data:/var/lib/grafana
networks:
- monitoring
depends_on:
- prometheus
api:
build: ./api
container_name: api
ports:
- "8080:3000"
networks:
- monitoring
environment:
- NODE_ENV=development
volumes:
prometheus_data:
grafana_data:
networks:
monitoring:
driver: bridgeKey features of this setup:
volumesat the service level persist data across container restarts.prometheus_datastores time-series data;grafana_datastores dashboards and configurations.networkscreate an isolated network calledmonitoringwhere all services resolve each other by name. Prometheus talks toapi:3000without exposing it to your host.depends_onensures startup order, though it doesn't guarantee readiness. (A more robust approach uses health checks, but this is sufficient for local development.)environmentconfigures service behavior. Grafana's default admin password is set toadmin; change this in production.
Start the stack with:
docker-compose up -dThis builds and runs all three services in the background. Check status with:
docker-compose psAccess Grafana at localhost:3000 (user: admin, password: admin). Add Prometheus as a data source pointing to http://prometheus:9090, then create dashboards that visualize your API's metrics.
When you're done, shut down cleanly with:
docker-compose downYour volumes persist, so next time you run docker-compose up, historical data and dashboards are restored.
Adding exporters for system-level monitoring
Exporters bridge the gap between applications that don't expose Prometheus metrics natively and Prometheus itself. The Node Exporter collects CPU, memory, disk, and network metrics from Linux systems. Add it to your compose file:
node-exporter:
image: prom/node-exporter:latest
container_name: node-exporter
ports:
- "9100:9100"
networks:
- monitoring
command:
- '--path.rootfs=/'
- '--path.procfs=/proc'
- '--path.sysfs=/sys'Then update your Prometheus config to scrape it:
- job_name: 'node'
static_configs:
- targets: ['node-exporter:9100']Reload Prometheus, and you'll immediately see metrics like node_cpu_seconds_total, node_memory_MemAvailable_bytes, and node_disk_io_reads_completed_total. These complement application-level metrics and help diagnose resource contention.
Common mistakes
- Mounting the config file at the wrong path: Always mount to
/etc/prometheus/prometheus.yml. Prometheus looks for this path by default. Mounting to the wrong location causes silent failures. - Forgetting the
networkingsection: If services are on different networks, they can't resolve each other by name. Docker Compose creates a bridge network by default, but be explicit to avoid confusion. - Using
localhostinside containers: A container'slocalhostis its own loopback interface, not your host machine. Service-to-service communication uses service names (e.g.,prometheus:9090, notlocalhost:9090). - Not persisting data: If you don't attach volumes, restarting containers loses all historical metrics and Grafana dashboards. Always define persistent volumes for stateful services.
- Setting scrape intervals too low: A 5-second interval generates four times more data than a 20-second interval. Start with 15-30 seconds and adjust based on query needs, not on perceived urgency.
- Ignoring resource limits: In production, Prometheus can consume significant memory. Docker Compose doesn't set limits by default. Add
mem_limitto prevent a runaway Prometheus from starving other services.
Key takeaways
- Docker eliminates configuration drift and makes monitoring setup reproducible across your team and CI/CD pipelines.
- A Prometheus configuration file is a simple YAML document that defines global settings and scrape targets; mount it into the container to customize behavior.
- The Prometheus web UI is a powerful exploration tool—use
upto check target health andrate()functions to derive actionable signals from raw metrics. - Docker Compose orchestrates multi-service stacks with shared networking and persistent storage, mirroring production-like setups locally.
- Service-to-service communication inside containers uses DNS names, not
localhost; rely on the service name defined in compose to resolve addresses. - Start with basic metrics (
up, request rate, latency histograms) and expand your observability as your team's needs grow. Monitoring is iterative, not a one-time setup.






Comments
Loading comments…