Uptime Kuma is a self-hosted monitoring system that allows you to monitor the availability of various services. It offers a variety of monitoring types, including those for different types of HTTP endpoints such as websites or REST APIs.

As it is also available as a ready-made Docker image, you can easily deploy it as an Azure App Service. For rolling it out using Terraform, a simple configuration is sufficient.

The Terraform code for this post can be found on Github: https://github.com/davull/uptime-kuma-azure-app-service

Azure App Service

To run a Docker container in Azure as a Web App for Container, we need an App Service that resides in an App Service Plan. Alternatively, containers can also be run in Azure Container App or Azure Kubernetes Service, an overview is provided by Microsoft here.

To ensure that the data collected by Uptime Kuma (an SQLite database) is not lost with each container restart, we persist it in a Storage Account.

Resource Group

To organize resources in Azure, we create a new Resource Group. To do this, we search for Resource Group in the Azure Marketplace and click Create.

Create Resource Group

We assign a name, choose a region, and click Review + Create. Afterward, we can create our services within the Resource Group.

Storage Account

When creating the Storage Account, we select the created Resource Group, set the name, region, and redundancy level. For non-critical applications, the most cost-effective option is typically Local-redundant storage (LRS). The remaining settings can be kept as default.

Create Storage Account

Once our Storage Account is provisioned, we can create a File share. For the tier, we choose Hot, and the backup option can be enabled or disabled based on our needs.

Create File Share

App Service Plan und Web App for Container

To create an App Service Plan, we follow a similar process. As the Operation System, we choose Linux, and the smallest non-free pricing plan Basic B1 is sufficient. This plan costs approximately €13 per month and provides 1.75 GB of memory and 1 vCPU. Depending on the number of services to be monitored, it might be advisable to choose a larger plan. However, scaling up can also be done at any time later.

Create App Service Plan

Now that an App Service Plan and Storage are available, we can create the actual App Service. To start the wizard, we search for Web App for Containers or Web App in the Azure Portal. Although they are listed separately in the Marketplace, they represent the same service.

As the Publish method, we choose Docker, and for the Operating System, we select Linux. The name chosen here will also be the subdomain under which the service will be accessible later (<name>.azurewebsites.net).

Create Web App

In the next tab, Docker, we switch the Image Source to Docker Hub and enter the desired image with the tag, for example, louislam/uptime-kuma:latest.

Create Web App

After completing the wizard, Azure pulls the Docker image from the Hub and starts our container. During the first launch of Uptime Kuma, it takes some time for the service to be up and running and the website to be accessible. You can track the process under the Deployment Center entry in the Logs tab.

Mount Azure File Share

To ensure that the Uptime Kuma database is not lost with each container restart, we mount the previously created File Share into the container. To do this, select the Configuration section and go to the Path mappings tab. Add a new entry, choosing the previously created Storage Account and the Storage Container created for the File Share. Select Azure File as the Storage Type. The path under which the File Share is mounted in the container should be /app/data. This is where Uptime Kuma stores its application data. After saving, the container will be restarted, and the data will now be stored in our Storage Container.

Add Path Mapping

When we now access the website of the Web App, Uptime Kuma greets us with the setup dialog, where we can create the admin user.

Terraform

As an alternative to the Azure Portal, managing the configuration of our application in a true Infrastructure as Code manner using Terraform is a great option. For this, we can utilize the azurerm Provider. The various authentication methods are described in the documentation. We create the same resources as we did in the portal using Terraform.

The configuration file looks like this:

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.79"
    }
  }
}

provider "azurerm" {
  features {}
}

resource "azurerm_resource_group" "rg_kuma" {
  name     = var.resource_group_name
  location = var.location
}

resource "azurerm_storage_account" "sa_kuma" {
  name                     = var.storage_account_name
  resource_group_name      = azurerm_resource_group.rg_kuma.name
  location                 = azurerm_resource_group.rg_kuma.location
  account_tier             = "Standard"
  account_replication_type = "LRS"
  min_tls_version          = "TLS1_2"
}

resource "azurerm_storage_share" "share_kuma" {
  name                 = "kumashare"
  storage_account_name = azurerm_storage_account.sa_kuma.name
  quota                = var.storage_quota
  access_tier          = "Hot"
}

resource "azurerm_service_plan" "service_plan_kuma" {
  name                = "service-plan-kuma"
  resource_group_name = azurerm_resource_group.rg_kuma.name
  location            = azurerm_resource_group.rg_kuma.location
  os_type             = "Linux"
  sku_name            = var.service_plan_sku
}

resource "azurerm_linux_web_app" "web_app_kuma" {
  name                = var.web_app_name
  resource_group_name = azurerm_resource_group.rg_kuma.name
  location            = azurerm_resource_group.rg_kuma.location
  service_plan_id     = azurerm_service_plan.service_plan_kuma.id
  https_only          = true

  site_config {
    http2_enabled       = true
    minimum_tls_version = "1.2"

    application_stack {
      docker_image_name   = var.docker_image
      docker_registry_url = "https://index.docker.io"
    }
  }

  app_settings = {
    "DOCKER_ENABLE_CI" = "true"
  }

  storage_account {
    access_key   = azurerm_storage_account.sa_kuma.primary_access_key
    account_name = azurerm_storage_account.sa_kuma.name
    name         = "webappstorage"
    type         = "AzureFiles"
    share_name   = azurerm_storage_share.share_kuma.name
    mount_path   = "/app/data"
  }
}

The variables are defined in a separate file.

variable "resource_group_name" {
  type = string
}

variable "storage_account_name" {
  type = string
}

variable "web_app_name" {
  type = string
}

variable "location" {
  type    = string
  default = "westeurope"
}

variable "storage_quota" {
  type    = number
  default = 50
}

variable "service_plan_sku" {
  type    = string
  default = "B1"
}

variable "docker_image" {
  type    = string
  default = "louislam/uptime-kuma:latest"
}

In a .tfvar file, the values for the variables are set.

resource_group_name  = "rg-kuma"
storage_account_name = "<your-storage-account-name>"
web_app_name         = "<your-web-app-name>"

Now, with a single command, we can create the entire environment and also delete it when needed.

terraform apply -var-file="terraform.tfvar"
terraform destroy -var-file="terraform.tfvar"