Deploying Uptime Kuma to GCP using Terraform

I am a Developer Evangelist at Twilio, co-author of Learn to Cloud, co-host of Random Cloud Chats podcast, AWS Community Builder, and YouTuber. Passionate about helping people get into cloud and sharing my learnings in cloud, DevOps and now DevRel.
If you've ever needed to monitor your websites or services, you know how expensive monitoring tools can get. I recently have been exploring monitoring solutions, for my personal projects as well as Learn to Cloud and I stumbled upon Uptime Kuma.
Uptime Kuma is a fantastic open-source and self-hosted monitoring tool that lets you track pretty much anything - websites, APIs, DNS records, Docker containers, and even Steam game servers (yes, really!).
What makes Uptime Kuma stand out is its simplicity. You get a clean, responsive UI, monitoring intervals as low as 20 seconds, and notifications through various services like - Telegram, Discord, Slack, email. Plus, it supports multi-language interfaces and lets you create multiple status pages, which is perfect if you're managing different projects.
Today, we're going to deploy Uptime Kuma to Google Cloud Platform using Terraform. Why GCP? Because their free tier is pretty generous, and with the setup we'll use, you can run this without spending a dime. And why Terraform? Because nobody wants to click through cloud console menus every time they need to set up their cloud infrastructure.
Let's get this monitoring system up and running!
Prerequisites
Before we dive in, make sure you have:
A Google Cloud account with billing enabled
Terraform installed on your machine
Google Cloud CLI installed and authenticated
A basic understanding of GCP and Terraform concepts
If you aren’t familiar with Terraform and GCP concepts or need a refresher, check out this free course on freeCodeCamp.
Project Setup
Let's start by creating a new directory for our Terraform configuration:
mkdir uptime-kuma-terraform
cd uptime-kuma-terraform
The Terraform Configuration
We'll need to create a single file named main.tf. This file will contain all our infrastructure configuration:
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 5.0"
}
}
required_version = ">= 1.0.0"
}
provider "google" {
project = var.project_id
region = var.region
}
# Create a persistent disk for Uptime Kuma data
resource "google_compute_disk" "kuma_disk" {
name = "kuma-disk"
type = "pd-standard"
size = var.disk_size
zone = var.zone
}
# Create VPC network firewall rule for Uptime Kuma
resource "google_compute_firewall" "kuma_firewall" {
name = "allow-kuma-3001"
network = "default"
allow {
protocol = "tcp"
ports = ["3001"]
}
source_ranges = ["0.0.0.0/0"]
target_tags = ["kuma-3001"]
}
# Create the VM instance
resource "google_compute_instance" "uptime_kuma" {
name = "uptime-kuma-vm"
machine_type = var.machine_type
zone = var.zone
tags = ["kuma-3001"]
boot_disk {
initialize_params {
image = "cos-cloud/cos-stable"
size = var.boot_disk_size
}
}
attached_disk {
source = google_compute_disk.kuma_disk.self_link
device_name = "kuma-data"
}
network_interface {
network = "default"
access_config {
// Ephemeral public IP
}
}
metadata = {
google-logging-enabled = "true"
# Configure the container
gce-container-declaration = yamlencode({
spec = {
containers = [{
name = "uptime-kuma"
image = "registry.hub.docker.com/louislam/uptime-kuma:1-debian"
securityContext = {
privileged = false
}
volumeMounts = [{
name = "kuma-data"
mountPath = "/app/data"
readOnly = false
}]
ports = [{
containerPort = 3001
hostPort = 3001
}]
}]
volumes = [{
name = "kuma-data"
hostPath = {
path = "/mnt/disks/kuma-data"
}
}]
restartPolicy = "Always"
}
})
# Format and mount the persistent disk
startup-script = <<-EOF
#!/bin/bash
if [ ! -d "/mnt/disks/kuma-data" ]; then
sudo mkdir -p /mnt/disks/kuma-data
sudo mkfs.ext4 -F /dev/disk/by-id/google-kuma-data
sudo mount -o discard,defaults /dev/disk/by-id/google-kuma-data /mnt/disks/kuma-data
sudo chmod a+w /mnt/disks/kuma-data
fi
EOF
}
service_account {
scopes = ["cloud-platform"]
}
}
# outputs.tf
output "instance_external_ip" {
value = google_compute_instance.uptime_kuma.network_interface[0].access_config[0].nat_ip
description = "The external IP address of the Uptime Kuma instance"
}
output "uptime_kuma_url" {
value = "http://${google_compute_instance.uptime_kuma.network_interface[0].access_config[0].nat_ip}:3001"
description = "The URL to access Uptime Kuma"
}
Since, we have few variables within our main.tf , let’s create them in variables.tf
variable "project_id" {
description = "The ID of the GCP project"
type = string
}
variable "region" {
description = "The region to deploy resources to"
type = string
default = "us-central1"
}
variable "zone" {
description = "The zone to deploy resources to"
type = string
default = "us-central1-a"
}
variable "machine_type" {
description = "The machine type to use for the VM instance"
type = string
default = "e2-micro"
}
variable "disk_size" {
description = "Size of the persistent disk in GB"
type = number
default = 20
}
variable "boot_disk_size" {
description = "Size of the boot disk in GB"
type = number
default = 10
}
Now, create a terraform.tfvars file to specify your project ID:
project_id = "your-project-id"
# Optionally override other variables:
# region = "us-west1"
# zone = "us-west1-a"
# disk_size = 30
# machine_type = "e2-small"
What's Being Created?
Let's break down what this Terraform configuration is doing:
VM Instance: We're creating an e2-micro instance (free tier eligible) using Container-Optimized OS, which is perfect for running Docker containers.
Persistent Storage: A 20GB persistent disk is attached to store your Uptime Kuma data, ensuring your monitoring history and settings survive VM restarts.
Container Configuration: The VM is automatically configured to run the Uptime Kuma Docker container with proper volume mounting for persistent storage.
Networking: A firewall rule is created to allow traffic to Uptime Kuma's default port (3001).
Deployment
Now that our Terraform configuration is ready, let’s deploy our infrastructure.
- First, initialize Terraform:
terraform init
- Preview the changes that will be made:
terraform plan
- Deploy the infrastructure:
terraform apply
When prompted, type yes to confirm. The deployment will take about 2-3 minutes.
Accessing Uptime Kuma
Once deployment is complete, Terraform will output your instance's IP address and the full URL to access Uptime Kuma.
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
Outputs:
instance_external_ip = "34.71.97.11"
uptime_kuma_url = "http://34.71.97.11:3001"
Just open your browser and navigate to: http://<your-instance-ip>:3001
You'll see the Uptime Kuma setup screen where you can create your admin account and start monitoring your services.

Cleaning Up
If you ever want to tear down the infrastructure:
terraform destroy
Pro Tips
Here are some useful tips for managing your deployment:
Cost Management: The configuration uses free-tier eligible resources (e2-micro instance and standard persistent disk), so you shouldn't incur any charges if you're within the free tier limits.
Customization: You can easily modify the configuration by changing variables in your
terraform.tfvarsfile:Need more storage? Adjust
disk_sizeWant a more powerful VM? Change
machine_typeDifferent region? Update
regionandzone
State Management: Keep your
terraform.tfstatefile safe - it's how Terraform tracks your resources. Consider using remote state for team environments.
Troubleshooting
If you can't access Uptime Kuma after deployment:
Wait a few minutes for the container to start up completely
Check if your VM is running in the GCP Console
Verify the firewall rule was created correctly
SSH into the VM to check container logs:
gcloud compute ssh uptime-kuma-vm
docker ps
docker logs $(docker ps -q)
Conclusion
And there you have it! You've just automated the deployment of Uptime Kuma on Google Cloud. No more manual configuration or clicking through the console - just clean, repeatable infrastructure as code.
The best part? This setup is completely free-tier eligible and takes care of all the little details like persistent storage and container configuration. You can now deploy and destroy your monitoring environment with a single command!
Let me know in the comments if you have any questions or run into issues. Happy monitoring! 🚀



