Go to previous section Download Markdown Go to next section
September 12, 2026
In case you use Uptime Kuma for uptime monitoring, here is how to set it up so that we can reach it from an Ansible playbook, such as one called by the Semaphore instance we set up in Part 1 of this series, to schedule a maintenance window.
You might ask: "How will the workflow we build in Part 4 know which monitors in Kuma to put into maintenance, when one host can have multiple monitors that are all named differently?" That is a good question. The answer is simple. We tag each monitor with its host's hostname, so that when a request arrives carrying that hostname, Kuma applies the maintenance window to every monitor carrying that tag, regardless of what the monitor itself is called.
Now what if you have multiple sites and thus more than one instance of Uptime Kuma? Don't worry, I've got you covered. The workflow we build in Part 4 will simply have a field where you define as many instances as you need. Then, when a request for "hostX" comes in, it gets sent to every configured instance, and whichever ones find a tag match apply a maintenance window for however many minutes you define.
If you are already running Uptime Kuma (whether as a Docker container, in an LXC, or bare metal), skip Steps 1 and 2.
You might ask why install Uptime Kuma in an LXC container rather than in Docker? A few reasons, drawn straight from lessons in this guide:
apt install whatever tool you need and add a cron job. In a Docker deployment, any extra tool has to be baked into a custom image ahead of time, which is exactly the kind of friction we hit with Semaphore's own Docker install later in this guide.None of this means Docker is a bad choice for Kuma. If you already run it that way, it works fine for everything in this guide; the API calls we make later do not care how Kuma itself is hosted.
apt update && apt upgrade -y
curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
apt install -y nodejs git
git clone https://github.com/louislam/uptime-kuma.git
cd uptime-kuma
npm run setup
npm install pm2 -g
pm2 install pm2-logrotate
pm2 start server/server.js --name uptime-kuma
pm2 save
pm2 startup
http://<lxc-ip>:3001 in your browser and create the initial account.
Add monitors as you normally would for each service running on a host: HTTP checks, TCP port checks, ping monitors, Docker container monitors, whatever applies. Nothing about how you build a monitor changes for this guide.
The one thing that matters for the automation in Part 3 is tagging. On every monitor you want protected during a patch run, add a tag whose name is the exact hostname that Ansible / PatchMon uses for that host (its inventory_hostname). Case matters, since the tag match later in this guide is case sensitive.
A single host is very likely to have several monitors, and they do not all have to carry the same name as the host itself. Take web1 as an example: it might have a ping monitor, two separate Docker container monitors, and a Syncthing monitor, four different monitor names in total, all tagged web1. Tag every one of them; the automation matches on the tag, not on the monitor's own name.
If you run more than one Uptime Kuma instance, this also does not have to line up 1:1. A single host's monitors can be spread across several instances (for example, its ping monitor lives on one instance and its Docker monitors on another), all tagged web1. Each instance is queried independently in Part 4, and a host with nothing tagged on a given instance is simply skipped there, not treated as an error.
To be able to send API-like commands to our UptimeKuma instance, we will need to utilize some middleware. While older (last update was three years ago), I have verified that lucasheld’s collection using a Python library still works even on the newest 2.5.0 for basic commands like setting or removing a maintenance window, which is all we need. Let’s use it till something more modern is available (alternatives include Keithah’s REST API and Zerka’s KumaCompanion, both unmaintained for over a year).
docker exec -it <semaphore-container-name> pip3 install uptime-kuma-api jmespath
--break-system-packages first; many Docker base images are not built on a distro Python that enforces PEP 668, so it may just install cleanly. Only add the flag if you hit an externally-managed-environment error.docker compose pull && up -d), so treat it as a one-off test, not a fix.requirements.txt, but it is mounted at $SEMAPHORE_CONFIG_PATH (commonly /etc/semaphore) and only runs once, at container startup, not per task:volumes:
- ./requirements.txt:/etc/semaphore/requirements.txt
pip3 install --upgrade -r ${SEMAPHORE_CONFIG_PATH}/requirements.txt) does not itself pass --break-system-packages, so depending on the base image it may hit the same PEP 668 wall silently. Verify it actually landed after a restart:docker exec <semaphore-container-name> python3 -c "import uptime_kuma_api; print('ok')"
dockerfile) instead of depending on any runtime install:# dockerfile
FROM semaphoreui/semaphore:latest
RUN pip3 install --break-system-packages uptime-kuma-api jmespath
docker-compose.yml at that tag instead of the stock image. This survives every restart and pull without depending on whether the config-path install actually fires.# For Semaphore to work with it, sudo and break-system-packages is necessary
sudo pip3 install --break-system-packages uptime-kuma-api jmespath
ansible-galaxy collection install lucasheld.uptime_kuma
sudo matters. Without it, this installs into your own login, ~/.local/lib/..., invisible to the Semaphore user that runs web-triggered tasks (see also PR #2083).requirements.txt auto-install do not hit the same PEP 668 wall:sudo systemctl edit semaphore
# Add these two rows, save and exit:
[Service]
Environment=PIP_BREAK_SYSTEM_PACKAGES=1
sudo systemctl daemon-reload
sudo systemctl restart semaphore
# Confirm that you can see it (should get: Environment=PIP_BREAK_SYSTEM_PACKAGES=1).
sudo systemctl show semaphore -p Environment
sudo pip3 install above. In my own testing, this override alone did not fix the missing-module error; the direct system-wide install did.cd semaphore-playbooks/
git pull
nano ~/semaphore-playbooks/collections/requirements.txt
# Paste this into it, save and exit
uptime-kuma-api
jmespath
# Remember to commit it if you are editing it directly on the server:
git add .
git commit -am "Added the requirements.txt file needed for Uptimekuma maintenance controls."
git push
ansible-playbook monitoring/uptimekuma/maintenance/uptimekuma_login_test.yml \
-e "uptimekuma_test_url=http://1.2.3.4:3001" \
-e "uptimekuma_test_username=YOUR_USERNAME" \
-e 'uptimekuma_test_password=YOUR_PASSWORD'
So far we did a read-only test. Let’s do a write test where we set a host or more into a maintenance group using tags.
proxmox-nodesecho "[all]
proxmox-nodes" > /tmp/uptimekuma-test-inventory.ini
ansible-playbook -i /tmp/uptimekuma-test-inventory.ini \
monitoring/uptimekuma/maintenance/uptimekuma_maintenance.yml \
-e 'uptimekuma_action=start' \
-e 'uptimekuma_duration_minutes=5' \
-e '{"uptimekuma_instances": [{"name": "uptimekuma1", "url": "http://1.2.3.4:3001"}]}' \
-e '{"uptimekuma_credentials": {"uptimekuma1": {"username": "YOUR_USERNAME", "password": "YOUR_PASSWORD"}}}'
The terminal connection was great to verify that we can talk to Uptime Kuma and send commands using the lucasheld.uptime_kuma collection.
grep -rl "ANSIBLE_VAULT" . --include="*.yml"
# Navigate to your Semaphore playbooks folder (we created this one in Part 1).
cd ~/semaphore-playbooks/monitoring/uptimekuma/maintenance
nano uptimekuma_vault.yml
---
uptimekuma_credentials:
uptimekuma1:
username: YOUR_USERNAME
password: "REAL_PASSWORD_HERE"
uptimekuma2:
username: YOUR_USERNAME
password: "REAL_PASSWORD_HERE"
uptimekuma3:
username: YOUR_USERNAME
password: "REAL_PASSWORD_HERE"
# Modify the path if you are using your own folder structure
ansible-vault encrypt ~/semaphore-playbooks/monitoring/uptimekuma/maintenance/uptimekuma_vault.yml
uptimekuma_vault.yml, it will be full of numbers (not really proper encryption but works well).cd ~/semaphore-playbooks
mv /tmp/uptimekuma-test-inventory.ini inventory/uptimekuma-test-inventory.ini
semaphore-playbooks/monitoring/uptimekuma/maintenance/uptimekuma_maintenance.yml.uptimekuma_maintenance_one_instance.yml in the same folder:git pull to fetch it.ansible-playbook -i /inventory/uptimekuma-test-inventory.ini ~/semaphore-playbooks/monitoring/uptimekuma/maintenance/uptimekuma_maintenance.yml -e 'uptimekuma_action=start' -e 'uptimekuma_duration_minutes=5' -e '{"uptimekuma_instances": [{"name": "uptimekuma1", "url": "http://1.2.3.4:3001"}]}' --ask-vault-pass
ansible-galaxy collection list | grep -i uptime .ansible-vault edit inventory/group_vars/all/uptimekuma_vault.yml
ansible-playbook \
-i inventory/uptimekuma-test-inventory.ini \
monitoring/uptimekuma/maintenance/uptimekuma_maintenance.yml \
-e 'uptimekuma_action=stop' \
-e '{"uptimekuma_instances": [{"name": "uptimekuma1", "url": "http://1.2.3.4:3001"}]}' \
--ask-vault-pass
If all is good to this point on your side, then we are finally ready to configure it in Semaphore UI!
💡 Note
Note: If you have previously uploaded templates via a text editor, remember to
git pushthem to your source version control server, so that Semaphore UI can see them!
Since we confirmed that the playbooks work with pure Ansible using SSH from our Semaphore instance, we can proceed with adding a task template.
In Semaphore, go to Key Store → click on the ‘New key’ button.
ansible-vault-uptimekumaThen create a new Task Template:
monitoring/uptimekuma/maintenance/uptimekuma_maintenance.ymluptimekuma_actionstringstartuptimekuma_duration_minutesinteger7 uptimekuma_instancesinteger[{"name":"uptimekuma1","url":"http://1.2.3.4:3001"},{"name":"uptimekuma2","url":"http://1.2.3.4:3001"}start (apply a maintenance window)7 (minutes)web1That run is the one that actually matters - it proves Semaphore's own execution path (including requirements.yml/requirements.txt auto-install, its own service user) can do everything our shell just did. Once that passes, run the stop variant the same way, note the template's numeric ID, and we will be able to use it in Part 4 for n8n orchestrator's SEMAPHORE_TEMPLATE_UPTIMEKUMA_MAINTENANCE field.
Enjoying this tutorial?
This site is a non-profit project. If it saved you some time, you can support it by getting Jan some coffee.
☕ Buy me a coffee
Comments