Monitor All Your Docker Containers in Uptime Kuma (The Right Way)
Uptime Kuma is great for HTTP and TCP monitoring, but a lot of people don't realize it can also watch your Docker containers directly — showing you whether a container is running or not, completely separate from whether its exposed port responds.
The problem is the default setup. Most guides tell you to bind-mount /var/run/docker.sock into the Uptime Kuma container, which works, but it's a really bad idea from a security standpoint. The Docker socket is basically root access to your host. If Kuma ever has a vulnerability — or gets compromised through one of your monitored services somehow — you've handed over the keys.
There's a better way: a lightweight proxy called docker-socket-proxy that sits between Kuma and the Docker socket, and only allows the specific API calls Kuma actually needs. Everything else gets blocked at the proxy level.
On top of that, if you have 15+ containers running, adding monitors one by one through the Uptime Kuma UI is painful. So I'll also write a small Python script that reads your running containers and bulk-imports all of them into a group automatically.
What You'll Need
- Docker and Docker Compose on your server
- Uptime Kuma running (or about to run) via Docker Compose
- Python 3 with pip available on the host
- sqlite3 package
- About 10 minutes
The Compose Setup
Here's the full compose.yml. Two services: Uptime Kuma and the socket proxy, isolated on an internal network so nothing else can reach the proxy.
services:
uptime-kuma:
image: louislam/uptime-kuma:1
container_name: uptime-kuma
restart: unless-stopped
ports:
- "127.0.0.1:3001:3001"
volumes:
- ./data:/app/data
networks:
- kuma-internal
environment:
- TZ=Africa/Cairo
depends_on:
- socket-proxy
deploy:
resources:
limits:
memory: 256M
cpus: "0.75"
reservations:
memory: 128M
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
socket-proxy:
image: tecnativa/docker-socket-proxy:latest
container_name: socket-proxy
restart: unless-stopped
environment:
- CONTAINERS=1
- IMAGES=1
- INFO=1
- PING=1
- VERSION=1
- POST=0
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- kuma-internal
read_only: true
tmpfs:
- /run
- /tmp
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
networks:
kuma-internal:
driver: bridge
A few things worth pointing out here:
The proxy has POST=0 set, which blocks write operations over the Docker API. Kuma only needs to read container state, so there's no reason to allow anything else. The read_only: true flag locks down the container's own filesystem too. We add /run and /tmp as tmpfs mounts because the proxy's entrypoint script writes a haproxy config at startup — without those, it crashes in a restart loop.
/usr/local/bin/docker-entrypoint.sh: line 21: can't create /tmp/haproxy.cfg: Read-only file systeKuma itself no longer has the socket mounted at all. It talks to the proxy over the internal Docker network using a plain TCP connection.
Run it:
docker compose up -d
docker logs socket-proxy --tail 20
You can verify connectivity from inside the Kuma container before touching the UI:
docker exec uptime-kuma curl -s http://socket-proxy:2375/_ping
If that returns OK, the proxy is working and Kuma can reach it.
Connecting the Docker Host in Uptime Kuma
Log into the Uptime Kuma UI and go to Settings → Docker Hosts → Add Docker Host.
- Friendly Name: Docker containers (or whatever you like)
- Connection Type: TCP
- Docker Daemon:
tcp://socket-proxy:2375
Hit Test — it should connect immediately. If it doesn't, double-check that both containers are on the same kuma-internal network:
docker inspect uptime-kuma socket-proxy --format '{{.Name}}: {{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}'
Bulk-Adding All Containers via Python
Adding monitors one by one through the UI works fine if you have three or four containers. Once you've more than 20 or 30, it's a lot of wasted time. The uptime-kuma-api library talks to Kuma over its internal Socket.IO API and lets you automate everything.
First, install it:
pip3 install uptime-kuma-api
You'll need your Docker Host ID. Rather than authenticating through the API just to get it, query the SQLite database directly — Kuma stores everything there:
sqlite3 ./data/kuma.db "SELECT id, name FROM docker_host;"
Now create a file called bulk_kuma.py:
import subprocess
from uptime_kuma_api import UptimeKumaApi, MonitorType
KUMA_URL = "http://127.0.0.1:3001"
USERNAME = "your_username"
PASSWORD = "your_password"
DOCKER_HOST_ID = 1 # replace with the ID from the sqlite query
GROUP_NAME = "Docker containers" #replace with the group name you want
result = subprocess.run(["docker", "ps", "--format", "{{.Names}}"], capture_output=True, text=True)
containers = [c for c in result.stdout.strip().split("\n") if c]
api = UptimeKumaApi(KUMA_URL)
api.login(USERNAME, PASSWORD)
# Clean up any existing Docker-type monitors
for m in api.get_monitors():
if m.get("type") == MonitorType.DOCKER:
api.delete_monitor(m["id"])
print(f"[DELETED] {m['name']}")
# Create the group
group = api.add_monitor(type=MonitorType.GROUP, name=GROUP_NAME)
group_id = group["monitorID"]
print(f"[GROUP] Created '{GROUP_NAME}' with ID {group_id}")
# Add all running containers under the group
for name in containers:
api.add_monitor(
type=MonitorType.DOCKER,
name=name,
docker_container=name,
docker_host=DOCKER_HOST_ID,
parent=group_id,
)
print(f"[ADDED] {name}")
api.disconnect()
Run it:
python3 bulk_kuma.py
The script will delete any existing Docker monitors (so you start clean), create a group called "Docker containers", then add every currently-running container underneath it. Groups in Kuma are just monitors with a special type — the parent parameter is what nests them visually in the UI.
When it's done, refresh the Uptime Kuma dashboard. You'll see a collapsible "Docker containers" group with one monitor per container. Containers that are running show as UP; anything that stops will immediately flip to DOWN and trigger your configured notifications.
A Note on Re-running the Script
If you deploy new containers later and want to add them without duplicating existing monitors, change the delete block to a skip instead:
existing = {m["name"] for m in api.get_monitors()}
for name in containers:
if name in existing:
print(f"[SKIP] {name} already exists")
continue
api.add_monitor(
type=MonitorType.DOCKER,
name=name,
docker_container=name,
docker_host=DOCKER_HOST_ID,
parent=group_id,
)
print(f"[ADDED] {name}")
You'll need to set group_id to your existing group's ID in that case — get it from the same sqlite query:
sqlite3 ./data/kuma.db "SELECT id, name FROM monitor WHERE type='group';"