Uptime Kuma status and monitoring
Uptime Kuma status and monitoring
This pattern composes Compute, Network, and Block Storage into a self-hosted uptime-monitoring and status-page host you run on infrastructure you control.
What this template does#
Provisions a single instance running Uptime Kuma, an open-source uptime-monitoring tool (a self-hosted alternative to Statuspage, Pingdom, or Better Uptime). You add monitors that poll your services on a schedule, configure notifications, and publish public status pages:
- Compute instance that runs Uptime Kuma in Docker, sized for its single SQLite-backed container (2 vCPU and 2 GiB RAM)
- Private network, subnet, router, port, and security group; a floating IP for public access
- A block volume mounted at
/var/lib/docker, so the Uptime Kuma data (the SQLite database of monitors, check history, and status pages) lives on a volume you can grow rather than on the boot disk - cloud-init installs Docker Engine and starts Uptime Kuma from a compose file on first boot
Uptime Kuma watches the services you deploy: it polls HTTP endpoints, TCP ports, and pings, alerts you when a check fails, and exposes a public status page for your users.
No credential ships with this template. You set the admin account the first time you open the dashboard.
Parameters#
| Parameter | Description | Default |
|---|---|---|
key_name | SSH keypair name (must already exist) | No default |
flavor_name | Instance size (Uptime Kuma runs on 2 vCPU / 2 GiB) | s1a.small |
image_name | Operating system image | Ubuntu-24.04 |
app_name | Display name prefix for resources | uptime-kuma |
volume_size | Block volume size in GiB, mounted at /var/lib/docker | 10 |
external_network | External network for floating IP allocation | PublicStatic |
private_cidr | CIDR for the private subnet | 10.42.0.0/24 |
dashboard_allowed_cidr | CIDR allowed to reach the dashboard on port 3001 | 10.42.0.0/24 |
Dashboard access and security#
The dashboard listens on port 3001 over plain HTTP. The security group restricts 3001 to dashboard_allowed_cidr, which defaults to the private network only, so the raw dashboard stays off the public internet. Uptime Kuma sets the admin account on first visit. Reach the dashboard one of three ways:
- Put a reverse proxy (Caddy or Nginx) in front of Uptime Kuma and serve the dashboard over HTTPS on 443. Point the domain's DNS A record at the floating IP. This is the recommended path for routine access.
- Tunnel over SSH:
ssh -L 3001:localhost:3001 user@FLOATING_IP, then openhttp://localhost:3001. - Set
dashboard_allowed_cidrtoYOUR_IP/32to reach port 3001 directly from one address.
Ports 80 and 443 stay open for the reverse proxy you put in front; they carry no traffic until you add one.
Status pages and the admin dashboard#
Uptime Kuma serves two surfaces from the same app:
- The admin dashboard configures monitors, notifications, and status pages. Keep it restricted.
- A public status page lives at
/status/SLUG, where you setSLUGin the dashboard. Status pages are meant to be public, so expose them deliberately through the reverse proxy on 443. A status page shows only the monitors you add to it.
When to use this pattern#
Watch HTTP endpoints, TCP ports, and pings for the apps you run, alert on failures through email, webhook, or chat, and give your users a public status page. Uptime Kuma pairs with the monitoring stack (Prometheus and Grafana) for infrastructure metrics and with self-hosted analytics for product usage.
Estimated cost#
Monthly cost estimate
Pricing calculator ↗Sized as a custom package on shared vCPU.
Monthly total for the required template above. Use the configurator below to add optional pieces and see the total update.
What each resource is for
Uptime Kuma host
s1a.small · 2 shared vCPU, 2 GiB RAM, 0.5 Gbps
Runs Uptime Kuma in Docker (monitor checks, alerting, and public status pages), with the SQLite database on an attached volume.
Uptime Kuma is a single SQLite-backed container that runs on 2 vCPU and 2 GiB RAM. Size up only for hundreds of monitors at short check intervals.
Compute shown per role at custom-package rates ($29/dedicated vCPU, $7.25/shared vCPU, $1/GiB RAM). The headline above is the billed total: the cheaper of a named plan and the custom package, plus add-ons.
Included in baseline
s1a.small
2 shared vCPU, 2 GiB RAM, 0.5 Gbps
Compute + RAM rate basis
2 vCPU + 2 GiB RAM at $29/dedicated vCPU, $7.25/shared vCPU, $1/GiB RAM (regular). Totals apply the flat −$5/mo package promotion.
Block storage (40 GiB)
40 GiB at $0.08/GiB/mo
Public IP (included)
1 included with the custom package
Package promotional discount
Flat −$5.00/mo on the custom package (same promotion as named plans).
Included at no charge
These line items are zero on Quake AI. Many other providers meter them separately.
Data transfer (inbound and outbound)
Unlimited data transfer on every plan; Quake AI does not meter per-GB egress.
AWS, GCP, and Azure meter outbound transfer per GB. DigitalOcean and Hetzner include an allowance on compute plans, then charge overage.
Learn morePrivate networking
Private networks, subnets, Neutron routers, and security groups are included with the plan.
VPC objects are usually free to create elsewhere, but NAT gateways bill hourly plus per-GB processed. Quake AI uses router SNAT with no separate NAT line item.
Control-plane API requests
OpenStack API calls for provisioning and management are included.
Some managed services on other clouds meter API calls or charge for premium control-plane features.
Pricing data last validated: . For current rates, check quake.ai/pricing.
Template source#
Show source (7 files)Hide source
data "openstack_images_image_v2" "os" {
name = var.image_name
most_recent = true
}
data "openstack_networking_network_v2" "external" {
name = var.external_network
}
resource "openstack_networking_network_v2" "private" {
name = "${var.app_name}-net"
admin_state_up = true
}
resource "openstack_networking_subnet_v2" "private" {
name = "${var.app_name}-subnet"
network_id = openstack_networking_network_v2.private.id
cidr = var.private_cidr
ip_version = 4
dns_nameservers = ["1.1.1.1", "8.8.8.8"]
}
resource "openstack_networking_router_v2" "main" {
name = "${var.app_name}-router"
external_network_id = data.openstack_networking_network_v2.external.id
}
resource "openstack_networking_router_interface_v2" "private" {
router_id = openstack_networking_router_v2.main.id
subnet_id = openstack_networking_subnet_v2.private.id
}
resource "openstack_networking_secgroup_v2" "kuma" {
name = "${var.app_name}-sg"
description = "SSH and HTTP/HTTPS for a reverse proxy; admin dashboard port 3001 restricted"
}
resource "openstack_networking_secgroup_rule_v2" "ssh" {
direction = "ingress"
ethertype = "IPv4"
protocol = "tcp"
port_range_min = 22
port_range_max = 22
remote_ip_prefix = "0.0.0.0/0"
security_group_id = openstack_networking_secgroup_v2.kuma.id
}
# 80 and 443 carry the dashboard and public status pages when they are served
# over a domain with automatic TLS through a reverse proxy (Caddy or Nginx).
# They are not used until you put a proxy in front of Uptime Kuma; see the
# reference page.
resource "openstack_networking_secgroup_rule_v2" "http" {
direction = "ingress"
ethertype = "IPv4"
protocol = "tcp"
port_range_min = 80
port_range_max = 80
remote_ip_prefix = "0.0.0.0/0"
security_group_id = openstack_networking_secgroup_v2.kuma.id
}
resource "openstack_networking_secgroup_rule_v2" "https" {
direction = "ingress"
ethertype = "IPv4"
protocol = "tcp"
port_range_min = 443
port_range_max = 443
remote_ip_prefix = "0.0.0.0/0"
security_group_id = openstack_networking_secgroup_v2.kuma.id
}
# Raw dashboard HTTP on 3001 is restricted to dashboard_allowed_cidr (the
# private network by default). The admin account is set on first visit, but the
# port stays off the public internet by default. Prefer a domain with TLS on
# 443 for routine access; publish status pages through the same reverse proxy.
resource "openstack_networking_secgroup_rule_v2" "dashboard" {
direction = "ingress"
ethertype = "IPv4"
protocol = "tcp"
port_range_min = 3001
port_range_max = 3001
remote_ip_prefix = var.dashboard_allowed_cidr
security_group_id = openstack_networking_secgroup_v2.kuma.id
}
resource "openstack_networking_port_v2" "kuma" {
name = "${var.app_name}-port"
network_id = openstack_networking_network_v2.private.id
security_group_ids = [openstack_networking_secgroup_v2.kuma.id]
fixed_ip {
subnet_id = openstack_networking_subnet_v2.private.id
}
depends_on = [openstack_networking_router_interface_v2.private]
}
resource "openstack_blockstorage_volume_v3" "data" {
name = "${var.app_name}-data"
size = var.volume_size
}
resource "openstack_compute_instance_v2" "kuma" {
name = var.app_name
flavor_name = var.flavor_name
key_pair = var.key_name
user_data = templatefile("${path.module}/cloud-init/uptime-kuma.yaml.tftpl", {
app_name = var.app_name
})
block_device {
uuid = data.openstack_images_image_v2.os.id
source_type = "image"
destination_type = "volume"
volume_size = 30
boot_index = 0
delete_on_termination = true
}
network {
port = openstack_networking_port_v2.kuma.id
}
}
resource "openstack_compute_volume_attach_v2" "data" {
instance_id = openstack_compute_instance_v2.kuma.id
volume_id = openstack_blockstorage_volume_v3.data.id
}
resource "openstack_networking_floatingip_v2" "kuma" {
pool = var.external_network
}
resource "openstack_networking_floatingip_associate_v2" "kuma" {
floating_ip = openstack_networking_floatingip_v2.kuma.address
port_id = openstack_networking_port_v2.kuma.id
}
variable "key_name" {
description = "SSH keypair name (must already exist in your project)"
type = string
}
variable "flavor_name" {
description = "Instance size. Uptime Kuma is a single SQLite-backed container that runs comfortably on 2 vCPU and 2 GiB RAM. Size up only for hundreds of monitors at short intervals."
type = string
default = "s1a.small"
}
variable "image_name" {
description = "Operating system image. Ubuntu 24.04 is the recommended base."
type = string
default = "Ubuntu-24.04"
}
variable "app_name" {
description = "Display name prefix for compute and network resources"
type = string
default = "uptime-kuma"
}
variable "volume_size" {
description = "Block volume size in GiB, mounted at /var/lib/docker so the Uptime Kuma data (the SQLite database holding monitors, history, and status pages) lives on a volume you can grow rather than on the boot disk."
type = number
default = 10
}
variable "external_network" {
description = "Shared external network for router gateway and floating IPs; defaults to PublicStatic (persisted FIP / production pattern). Override with PublicEphemeral for ephemeral demos."
type = string
default = "PublicStatic"
}
variable "private_cidr" {
description = "CIDR for the private tenant network the instance lives in"
type = string
default = "10.42.0.0/24"
}
variable "dashboard_allowed_cidr" {
description = "CIDR allowed to reach the Uptime Kuma admin dashboard on port 3001. Defaults to the private network only, so the dashboard is not exposed to the public internet on its raw port. Reach it over an SSH tunnel, or (recommended) serve it over a domain with HTTPS on 443 behind a reverse proxy. To allow direct access from your workstation, set this to YOUR_IP/32. Public status pages are served by the same app and reachable from anywhere over the reverse proxy you put in front."
type = string
default = "10.42.0.0/24"
}
output "instance_id" {
description = "ID of the compute instance running Uptime Kuma"
value = openstack_compute_instance_v2.kuma.id
}
output "floating_ip" {
description = "Public floating IP address of the Uptime Kuma host"
value = openstack_networking_floatingip_v2.kuma.address
}
output "private_ip" {
description = "Private IP address of the instance"
value = openstack_compute_instance_v2.kuma.access_ip_v4
}
output "dashboard_url" {
description = "Uptime Kuma admin dashboard URL on port 3001. Reachable from dashboard_allowed_cidr (the private network by default; tunnel over SSH, or put a reverse proxy in front and use HTTPS on 443)."
value = "http://${openstack_networking_floatingip_v2.kuma.address}:3001"
}
output "status_page_url" {
description = "Base URL for public status pages. Each published status page lives at /status/SLUG, where you set SLUG in the dashboard. Serve it over the reverse proxy on 443 for a public, TLS-terminated status page."
value = "http://${openstack_networking_floatingip_v2.kuma.address}:3001/status"
}
terraform {
required_version = ">= 1.6.0"
required_providers {
openstack = {
source = "terraform-provider-openstack/openstack"
version = "~> 2.0"
}
}
}
provider "openstack" {}
# Required: SSH keypair must already exist in your project
key_name = "YOUR_KEY_NAME"
# Recommended: restrict the admin dashboard (port 3001) to your workstation IP.
# Leave unset to keep 3001 reachable only from the private network and tunnel
# over SSH, or put a reverse proxy in front and use HTTPS on 443. Public status
# pages are served by the same app through that reverse proxy.
# dashboard_allowed_cidr = "203.0.113.10/32"
# flavor_name = "s1a.small"
# image_name = "Ubuntu-24.04"
# app_name = "uptime-kuma"
# volume_size = 10
# external_network = "PublicStatic"
# private_cidr = "10.42.0.0/24"
#cloud-config
package_update: true
packages:
- ca-certificates
- curl
write_files:
- path: /opt/uptime-kuma/docker-compose.yml
permissions: "0644"
content: |
# Uptime Kuma for ${app_name}. Single container, SQLite-backed.
# The admin account is created on first visit to the dashboard; no
# credential ships with this template. The SQLite database (monitors,
# check history, notification settings, and status pages) lives in the
# uptime-kuma named volume under /var/lib/docker on the data volume.
services:
uptime-kuma:
image: louislam/uptime-kuma:1
restart: unless-stopped
ports:
- "3001:3001"
volumes:
- uptime-kuma:/app/data
volumes:
uptime-kuma:
runcmd:
- |
set -e
# The data volume attaches as /dev/sdb on this platform (not /dev/vdb).
# Mount it at /var/lib/docker before Docker is installed so the Uptime Kuma
# named volume (its SQLite database) lives on the resizable volume rather
# than the boot disk.
DEV=/dev/sdb
for i in $(seq 1 30); do [ -b "$DEV" ] && break; sleep 5; done
if ! blkid "$DEV" >/dev/null 2>&1; then mkfs.ext4 -F -L kumadata "$DEV"; fi
mkdir -p /var/lib/docker
mount "$DEV" /var/lib/docker
grep -q "$DEV" /etc/fstab || echo "$DEV /var/lib/docker ext4 defaults,nofail 0 2" >> /etc/fstab
# Install Docker Engine plus the compose plugin from Docker's convenience
# script, then bring Uptime Kuma up from the compose file written above.
curl -fsSL https://get.docker.com | sh
cd /opt/uptime-kuma
docker compose up -d
# Uptime Kuma status and uptime monitoring
Single compute instance running [Uptime Kuma](https://github.com/louislam/uptime-kuma), a self-hosted uptime-monitoring and status-page tool (a self-hosted alternative to Statuspage, Pingdom, or Better Uptime) on infrastructure you control. After apply, you open the dashboard, set the admin account, add monitors for your services, configure notifications, and publish public status pages.
**Network class:** production — `external_network` defaults to `PublicStatic` for persisted floating IPs and multi-tier stacks; override with `PublicEphemeral` for ephemeral demos.
The instance provisions a private network, a floating IP, and a block volume mounted at `/var/lib/docker` so the Uptime Kuma data (its SQLite database of monitors, check history, and status pages) lives on a resizable volume. cloud-init installs Docker Engine and brings Uptime Kuma up from a compose file on first boot.
## Where this fits
Uptime Kuma is the monitoring layer for the services you deploy: it polls HTTP endpoints, TCP ports, and pings on a schedule, alerts you when a check fails, and exposes a public status page so your users see incidents. It runs on a VM you own rather than on a third-party status SaaS, which keeps monitor configuration and history on your infrastructure.
## Prerequisites
- OpenTofu >= 1.6.0 or Terraform >= 1.6.0
- Quake AI account with OpenStack credentials
- An existing SSH keypair in your project (the value of `key_name` must match that keypair)
## Resource baseline
Uptime Kuma is a single SQLite-backed container that runs on 2 vCPU and 2 GiB RAM. The default `s1a.small` flavor leaves headroom for Docker plus a few dozen monitors. Size up only for hundreds of monitors at short check intervals.
## Usage
1. Clone or copy this template directory
2. Copy `terraform.tfvars.example` to `terraform.tfvars` and fill in your values
3. Source your OpenStack credentials: `source openrc.sh`
4. Initialize: `tofu init`
5. Preview: `tofu plan`
6. Apply: `tofu apply`
After apply, cloud-init takes a few minutes to install Docker and start Uptime Kuma on first boot. Then reach `dashboard_url` from the outputs and complete the first-run setup, which creates your admin account. No credential ships with this template.
## Dashboard access and security
The dashboard listens on port 3001 over plain HTTP. The security group restricts 3001 to `dashboard_allowed_cidr`, which defaults to the private network only, so the raw dashboard is not exposed to the public internet. Uptime Kuma sets the admin account on first visit. Choose one of:
- **Recommended:** put a reverse proxy (Caddy or Nginx) in front of Uptime Kuma and serve the dashboard over HTTPS on 443. Point the domain's DNS A record at `floating_ip`.
- **SSH tunnel:** `ssh -L 3001:localhost:3001 user@<floating_ip>`, then open `http://localhost:3001`.
- **Direct, scoped:** set `dashboard_allowed_cidr` to your workstation IP (`YOUR_IP/32`) to reach 3001 directly from one address.
Ports 80 and 443 stay open for the reverse proxy you put in front; they carry no traffic until you add one.
## Status pages versus the admin dashboard
Uptime Kuma serves two distinct surfaces from the same app:
- The **admin dashboard** at `/dashboard` configures monitors, notifications, and status pages. Keep it restricted (the default).
- A **public status page** lives at `/status/SLUG`, where you set `SLUG` in the dashboard when you create the page. Status pages are meant to be public: expose them deliberately through the reverse proxy on 443. The status page shows only the monitors you add to it, not the full dashboard.
## How the instance is provisioned
cloud-init:
1. Mounts the data volume at `/var/lib/docker` (formatting it on first boot) and adds an `/etc/fstab` entry so it persists across reboots.
2. Writes `/opt/uptime-kuma/docker-compose.yml`.
3. Installs Docker Engine from `https://get.docker.com` and runs `docker compose up -d`, which starts Uptime Kuma on port 3001.
## Variables
| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `key_name` | string | yes | n/a | SSH keypair name (must already exist in your project) |
| `flavor_name` | string | no | `s1a.small` | Instance size (Uptime Kuma runs on 2 vCPU / 2 GiB) |
| `image_name` | string | no | `Ubuntu-24.04` | Operating system image |
| `app_name` | string | no | `uptime-kuma` | Display name prefix for resources |
| `volume_size` | number | no | `10` | Block volume size in GiB, mounted at `/var/lib/docker` |
| `external_network` | string | no | `PublicStatic` | Persisted FIP / production default; override with `PublicEphemeral` for demos |
| `private_cidr` | string | no | `10.42.0.0/24` | CIDR for the private subnet |
| `dashboard_allowed_cidr` | string | no | `10.42.0.0/24` | CIDR allowed to reach the dashboard on port 3001 |
## Outputs
| Name | Description |
| --- | --- |
| `floating_ip` | Public floating IP assigned to the instance |
| `private_ip` | Private IP address of the instance |
| `dashboard_url` | Uptime Kuma admin dashboard URL on port 3001 |
| `status_page_url` | Base URL for public status pages (`/status/SLUG`) |
| `instance_id` | Compute instance ID |
## Scope
This is a single-VM Uptime Kuma host that you operate, not a managed monitoring cloud. It is CPU-only and runs in one region, so the monitor that watches your services shares a failure domain with nothing else you run there. For independent monitoring, run this host in a different region from the workloads it watches.
## Documentation
See also: [monitoring stack](/resources/iac-templates/monitoring-stack), [self-hosted analytics (Umami)](/resources/iac-templates/analytics-umami)
Resources, parameters, and variables
key_namerequiredflavor_name="s1a.small"image_name="Ubuntu-24.04"app_name="uptime-kuma"volume_size=10external_network="PublicStatic"private_cidr="10.42.0.0/24"dashboard_allowed_cidr="10.42.0.0/24"
Customize this pattern#
- Customize a template's image and flavor
- Add a block volume to a template
- Parameterize a template with a tfvars file
See also#
Usage Guidelines
The sample code, software libraries, command line tools, proofs of concept, templates, and other related technology on this page (including any of the foregoing that is provided by Quake AI personnel) is provided to you as Quake AI Content under the Quake AI Customer Agreement, or the relevant written agreement between you and Quake AI (whichever applies). Do not use this Quake AI Content in your production accounts, or on production or other critical data. You are responsible for testing, securing, and optimizing the Quake AI Content (such as sample code) as appropriate for production grade use based on your specific quality control practices and standards. Deploying Quake AI Content may incur Quake AI charges for creating or using Quake AI chargeable resources, such as running Compute instances or storing data in Object Storage. Your use is also subject to the Acceptable Use Policy.
For the full policy, see Usage Guidelines.
See Also
Terraform and OpenTofu on Quake AI
Prerequisite
Networks
Prerequisite
Authoring IaC templates for Quake AI
Shares: Volumes, Security Groups
Deploy an API gateway with the api-gateway template
Shares: Volumes, Security Groups
Deploy a regional edge cache with the edge-cache template
Shares: Volumes, Security Groups