Prometheus runs almost anywhere, and that is exactly why choosing where to host it is confusing. The deciding factor is not CPU, it is how long you need to keep data and how many active series you generate, because a single Prometheus server is designed to hold weeks of data on local disk and nothing more. Once you need months of history or a view across several clusters, you stop asking about Prometheus hosting and start asking about remote write targets.
Prometheus 3.14.0 is the current stable release and 3.13.2 carries the LTS designation, per the official download page. Both are single Go binaries with no external dependencies, which is a large part of why the deployment story is so flexible. The six options below cover essentially every real world setup, roughly in order of how much operational work each one costs you.
The six realistic Prometheus hosting options
1. A single VPS with systemd
The simplest thing that works. Download the tarball, drop the binary in /usr/local/bin, and let systemd supervise it. This suits a handful of servers with node_exporter installed, and it costs whatever a small VPS costs.
useradd --no-create-home --shell /bin/false prometheus
mkdir -p /etc/prometheus /var/lib/prometheus
chown prometheus:prometheus /var/lib/prometheus
cat > /etc/systemd/system/prometheus.service <<'UNIT'
[Unit]
Description=Prometheus
After=network-online.target
[Service]
User=prometheus
Type=simple
ExecStart=/usr/local/bin/prometheus \
--config.file=/etc/prometheus/prometheus.yml \
--storage.tsdb.path=/var/lib/prometheus \
--storage.tsdb.retention.time=30d \
--storage.tsdb.retention.size=40GB \
--web.listen-address=127.0.0.1:9090
Restart=on-failure
[Install]
WantedBy=multi-user.target
UNIT
systemctl daemon-reload
systemctl enable --now prometheusTwo flags there deserve attention. Setting both a time and a size retention means whichever limit is reached first wins, which protects you from a full disk when cardinality spikes. Binding to 127.0.0.1 keeps the admin UI and the query API off the public internet, so put Nginx or Caddy in front if you need remote access.
2. Docker or Docker Compose
Docker gets you version pinning and a clean upgrade path. The container needs a config file mounted read only and a persistent volume for the TSDB. This is the setup most small teams end up on, usually alongside Grafana in the same compose file. We cover the Grafana half of that stack in detail in the guide on where to host Grafana.
services:
prometheus:
image: prom/prometheus:v3.14.0
restart: unless-stopped
user: "65534:65534"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ./rules:/etc/prometheus/rules:ro
- prom_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--storage.tsdb.retention.time=30d'
- '--web.enable-lifecycle'
ports:
- "127.0.0.1:9090:9090"
volumes:
prom_data:--web.enable-lifecycle flag lets you reload config with curl -X POST http://127.0.0.1:9090/-/reload instead of restarting the container, which avoids a gap in your scrape data.3. Kubernetes with kube-prometheus-stack
On Kubernetes, do not deploy a bare Prometheus. The kube-prometheus-stack Helm chart from the prometheus-community repo bundles the Prometheus Operator, Prometheus itself, Alertmanager, node_exporter, kube state metrics and a set of Grafana dashboards that already work. Installing it is three commands.
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm install monitoring prometheus-community/kube-prometheus-stack \
--namespace monitoring --create-namespace \
--set prometheus.prometheusSpec.retention=15d \
--set prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.resources.requests.storage=50Gi
kubectl -n monitoring get pods
kubectl -n monitoring port-forward svc/monitoring-grafana 3000:80The Operator model is the real win. You declare scrape targets as ServiceMonitor and PodMonitor objects that live next to your application manifests, so a new service starts being scraped when it is deployed rather than when someone edits a central config. If you are practicing on a local cluster first, our notes on installing Helm in Minikube and creating a namespace in Minikube cover the prerequisites.
kubectl get storageclass before you install, or set prometheus.prometheusSpec.storageSpec to an emptyDir for a throwaway test.4. Grafana Cloud Metrics
Grafana Cloud accepts Prometheus remote write and stores the data in Mimir on their side. The free plan covers 10,000 active series with 14 day retention and 3 users. You still run a scraper locally, either a small Prometheus in agent mode or Grafana Alloy, but you never operate a time series database. Billing above the free tier starts around $6.50 per 1,000 active series per month plus a platform fee, so cardinality control matters more than it does when you own the disk.
5. Amazon Managed Service for Prometheus
If your workloads already run on AWS, Amazon Managed Service for Prometheus is the least surprising option. It speaks the Prometheus remote write protocol and PromQL, integrates with IAM for auth, and bills on three dimensions rather than a per series subscription. The AWS pricing page lists metric samples ingested at $0.90 per 10 million samples for the first 2 billion per month, metrics stored at $0.03 per GB month, and query samples processed at $0.10 per billion.
That structure rewards long retention and punishes chatty scraping. Storing a year of data is cheap. Scraping 5,000 targets every 5 seconds is not. The lever you have is scrape interval and label cardinality, in that order.
6. Self hosted Grafana Mimir or Thanos
Mimir and Thanos both solve the same problem: turning a set of short retention Prometheus servers into one queryable long term store backed by object storage. Mimir takes remote write. Thanos uses a sidecar that ships TSDB blocks to a bucket. Either way your Prometheus instances become disposable and your S3 compatible bucket becomes the source of truth.
Be honest with yourself about the cost. These are distributed systems with compactors, store gateways, queriers and a rollout order that matters. Running one well is a real ongoing commitment. Choose this path when data residency, cost at very large scale, or an air gapped network rules out managed, not because it looks cleaner on an architecture diagram.
Disk sizing math you can actually use
The Prometheus storage documentation gives a formula and the one number you need to apply it:
needed_disk_space = retention_time_seconds * ingested_samples_per_second * bytes_per_samplePrometheus averages 1 to 2 bytes per sample after compression. Ingested samples per second is your active series divided by your scrape interval. So 50,000 active series scraped every 15 seconds is about 3,333 samples per second. Over 30 days that is roughly 8.6 billion samples, which at 2 bytes lands near 17 GB. Add headroom, because the docs recommend setting size based retention to at most 80 to 85 percent of the allocated disk so compaction has room to work.
| Active series | Scrape interval | Samples per second | 30 day disk at 2 B/sample | Provision |
|---|---|---|---|---|
| 10,000 | 15s | 667 | about 3.5 GB | 10 GB |
| 50,000 | 15s | 3,333 | about 17 GB | 40 GB |
| 250,000 | 30s | 8,333 | about 43 GB | 100 GB |
| 1,000,000 | 30s | 33,333 | about 173 GB | Remote write instead |
Memory is the other constraint and it scales with active series, not with disk. A rough working figure is a few kilobytes of RAM per active series once you account for the head block and query workspace, so 500,000 series wants a machine measured in tens of gigabytes. That is usually the point where teams stop scaling vertically.
Setting up remote write
Remote write is what connects the local and managed halves. Add a block like this to prometheus.yml, keep local retention short, and let the remote store handle history.
global:
scrape_interval: 30s
external_labels:
cluster: prod-us-east
replica: prom-a
remote_write:
- url: https://prometheus-prod.example.net/api/prom/push
basic_auth:
username: 123456
password_file: /etc/prometheus/remote_write_token
queue_config:
capacity: 10000
max_shards: 50
max_samples_per_send: 2000
write_relabel_configs:
- source_labels: [__name__]
regex: 'go_gc_duration_seconds.*|promhttp_.*'
action: dropThe write_relabel_configs block is where you save money. Dropping metrics you never query before they leave the box cuts both your ingest bill and your remote write bandwidth. The external_labels block is what lets a central store tell two clusters apart, and it is required for Thanos and useful everywhere else.
Comparing the six options
| Option | Best for | Practical retention | Ops burden |
|---|---|---|---|
| VPS with systemd | Under 20 hosts | 15 to 90 days | Low |
| Docker Compose | Small teams with Grafana alongside | 15 to 90 days | Low |
| kube-prometheus-stack | Any Kubernetes cluster | 7 to 30 days | Medium |
| Grafana Cloud Metrics | Teams with no ops headcount | 14 days free, longer on paid | Very low |
| Amazon Managed Prometheus | Workloads already on AWS | Months to years | Very low |
| Mimir or Thanos | Very large scale, data residency | Years | High |
Troubleshooting
Prometheus restarts and takes ten minutes to come back. It is replaying the write ahead log. Long replays mean a very large head block, which means too many active series. Find the offender with the topk(10, count by (__name__)({__name__=~".+"})) query and drop the labels feeding it.
Disk fills despite a retention setting. Time based retention only deletes whole blocks after they age out, and compaction needs free space to run. Set --storage.tsdb.retention.size as well, at roughly 80 percent of the volume, so the size limit trims blocks before the disk is gone.
Remote write queue keeps backing up. Watch prometheus_remote_storage_samples_pending. If it climbs steadily, raise max_shards and max_samples_per_send, but first check whether the remote endpoint is returning 429 responses, in which case you are being rate limited and need to drop series instead.
Targets show as DOWN with a context deadline exceeded error. The scrape timed out. Either the exporter is slow to render, which is common for an overloaded node_exporter, or the scrape_timeout is shorter than the response time. Raise the timeout, but treat a slow exporter as the real bug.
Frequently asked questions
How much does it cost to host Prometheus?
Self hosting costs only the server. A 2 vCPU and 4 GB instance with 50 GB of SSD runs comfortably in the low tens of dollars per month at most providers and handles tens of thousands of active series. Managed options bill on ingest and storage instead, so the total depends almost entirely on your cardinality.
How long can Prometheus retain data?
As long as your disk allows, but the project itself treats local storage as short term. Fifteen days is the default and 30 to 90 days is common on a single node. For anything longer, use remote write to a purpose built long term store rather than attaching a bigger volume.
Can I run Prometheus without Kubernetes?
Absolutely. Prometheus started as a standalone binary and works fine on a plain VPS with static scrape configs or file based service discovery. Kubernetes only adds value when the set of targets changes constantly, which is exactly what the Operator pattern was built for.
Do I need Prometheus if I use Grafana Cloud?
You need something that scrapes your targets, but it does not have to be a full Prometheus server. Prometheus in agent mode or Grafana Alloy scrape and forward without storing data locally, which uses far less memory and disk than a full instance.
What is the difference between Mimir and Thanos?
Both give you long term storage on object storage with a global query view. Mimir ingests through remote write and is closer to a hosted database in shape. Thanos attaches a sidecar to existing Prometheus servers and uploads their blocks. Mimir tends to be simpler to reason about at scale, Thanos is easier to bolt onto what you already run.
The bottom line
Prometheus hosting only looks complicated because the answer changes with scale. Under 20 hosts, a single VPS or a Docker Compose file with 30 day retention is the correct engineering decision and anything more is overhead. On Kubernetes, kube-prometheus-stack is the default for good reason and fighting it wastes time.
The moment you need history measured in months, or a single pane across clusters, stop scaling the local box and add remote write. Whether the far end is Grafana Cloud, Amazon Managed Prometheus or your own Mimir is a cost and control question, not a technical one, and you can change your mind later because they all speak the same protocol. Run the disk formula first, trim your cardinality second, and the hosting choice tends to make itself.
