On this page (6)
This Uptime Kuma Docker Compose file runs one container, louislam/uptime-kuma:2.5.7, with SQLite in a named volume and the web interface on 127.0.0.1:3001. We tested it on Oct 26, 2026 as the median of three runs on a 2-vCPU GitHub Actions runner. A fresh install with an admin account and no monitors idled at 129 MiB (range 128 to 129). With 30 monitors checking every 20 s for 10 minutes it peaked at 146 MiB (144 to 147), and all 928 heartbeats stored in each run were up. The highest reading in any run, 158.5 MiB, came at startup, so by our sizing rule it needs 1 GB of RAM.
Measured readings
Test box: Uptime Kuma 2.5.7
Measured in our lab on : 3 full runs, each from a fresh stack. Each figure is the median, with the range over the runs under it. The numbers come from the raw result file.
- Idle RAM
- 129 MiBrange 128–129 · 3 runs all containers in the published compose file, median of 3 runs; each run is the mean over 31 s after the stack settled
- Startup peak RAM
- 159 MiBrange 158–159 · 3 runs highest total between healthy and settled
- Peak RAM
- 146 MiBrange 144–147 · 3 runs under load, median of the run peaks
- Settled after
- 124 srange 123–125 · 3 runs after the first healthy response
- Idle CPU
- 0.2 %range 0.2–0.3 · 3 runs of one core, lab host
- Startup
- 2.3 srange 2.2–2.3 · 3 runs to first healthy response
- App version
- 2.5.7
- Tested
- , 3 runs (compose file verified the same day)
- How the figures are made
- Each run starts a fresh stack and goes through setup, settling, the idle window and the load step. We publish the median over the runs and the lowest and highest value, because the lab host and some apps vary from run to run. Runs, median and range
- Lab host
- AMD EPYC 9V45 96-Core Processor, 2 cores, 7.8 GiB RAM, x86_64.Ubuntu 24.04.5 LTS, Docker 28.0.4.
- Disk for the images
- About 2.6 GB with the containerd image store, the default on fresh installs of Docker Engine 29 and later (our estimate: unpacked plus compressed size, plus 10%; not measured on a containerd host). About 1.7 GB with the older overlay2 store (the unpacked column, as measured on this lab host). Plan for the larger figure. How we size disk
- How idle was measured
- After the stack was healthy and set up, we sampled total RAM until it stayed within max(2% of its mean, 10 MiB) over 60 s (at least 120 s, at most 600 s), then measured idle over the next 31 s. Rule in full
- Peak RAM measured
- Under load. A small real install: log in, then add 30 monitors with the `add` event and let Uptime Kuma run them for 10 minutes. 25 HTTP(s) monitors (GET, one URL each: http://target/site-1 to site-25), 3 TCP port monitors and 2 ping monitors (3 ICMP packets per check), every monitor at a 20 s interval, 16 s timeout (10 s for ping), no retries, no notifications, all pointing at one nginx container on the Compose network (answers 200 on every path), so no traffic leaves the lab. That is 30 checks every 20 s, 1.5 checks per second, about 90 per minute. The client disconnects after the monitors are added (no dashboard is open during the window; an open dashboard receives every heartbeat) and logs in again at the end to count the heartbeats stored in the database (`getMonitorBeats`). The measured window starts when the first monitor is added and lasts 10 minutes. No status page, no browser, no notification provider, no Docker or other integrations.
| Container | Idle RAM | Peak RAM (under load) | Image, compressed (download) | Image, unpacked (disk, overlay2 store) |
|---|---|---|---|---|
| target (lab only, not in the totals) | 3 MiBsame when rounded in all 3 runs | 4 MiBsame when rounded in all 3 runs | 26 MB | 62 MB |
| uptime-kuma | 129 MiBrange 128–129 · 3 runs | 146 MiBrange 144–147 · 3 runs | 617 MB | 1.7 GB |
Every run (3)
| Run | Idle RAM | Peak RAM | Startup peak | Startup | Lab host CPU |
|---|---|---|---|---|---|
| 1 (closest to median idle) | 129 MiB | 146 MiB | 159 MiB | 2.3 s | AMD EPYC 9V45 96-Core Processor |
| 2 | 129 MiB | 147 MiB | 158 MiB | 2.2 s | AMD EPYC 9V45 96-Core Processor |
| 3 | 128 MiB | 144 MiB | 159 MiB | 2.3 s | AMD EPYC 9V45 96-Core Processor |
0 s to 160 s after healthy; the line spans 0 to 158 MiB. Dashed line: settled.
Timeline as a table
| Seconds after healthy | Total RAM |
|---|---|
| 30 | 127 MiB |
| 60 | 127 MiB |
| 90 | 128 MiB |
| 120 | 128 MiB |
| 150 | 129 MiB |
| 160 | 129 MiB |
RAM is MiB (220 bytes); image sizes are MB (106 bytes). The unpacked column is what our lab host's Docker reported; see Disk for the images for other setups. Per-container peaks happen at different moments, so they do not add up to the total peak. Medians of separate columns need not add up either. Totals, disk and the timeline leave out the lab-only container (target): it is not in the published compose file.
Download verified compose fileRaw result JSONHow we measure
Lab notes (8)
- Measured, in this order: (1) idle: Uptime Kuma 2.5.7 with SQLite, one admin account and NO monitors, settled. (2) load: 30 monitors (25 HTTP, 3 TCP, 2 ping) at a 20 s interval against a local nginx for 10 minutes, with the dashboard closed. Peak and mean RAM and CPU of the load window are in runs[].load; they include the few seconds in which the 30 monitors are created (30 `add` calls, each starting a monitor; about 1 s in a development run, and the CPU peak sits there). The old result of this app (2.5.6) was a server that never left the database wizard and monitored nothing; do not compare with it.
- UPTIME_KUMA_DB_TYPE=sqlite (wiki: Environment Variables, since 2.0.0) is what skips the 'setup database' wizard. It is in the published compose file. Without it a v2 server waits on the wizard and starts no monitoring.
- The 30 checks have no external traffic: the target is a lab-only nginx container (compose.lab.yaml, NOT in the published compose). The stack total in the result therefore includes that nginx (a few MiB); the Uptime Kuma figures are the `uptime-kuma` container's. A real install also pays for the DNS lookups and the TLS handshakes of HTTPS sites, which plain-HTTP checks to a local container do not show.
- Heartbeats: `runs[].load.reported_by_script` has `heartbeats_recorded` (rows in the heartbeat table for the 30 monitors, read through `getMonitorBeats` after the window), `heartbeats_expected` (monitors x window / interval), `heartbeats_up`, `heartbeats_down`, `monitors_created`, `monitors_failed` and per type counts. The run fails if a monitor could not be created, has no heartbeat or has no UP heartbeat, or if fewer than half of the expected heartbeats were recorded.
- Client: Node script in a throwaway container of the same image (socket.io-client 4.8.3 from the image's own node_modules), over the Socket.IO API the web page uses. Nothing is installed or downloaded, and the client is outside the Uptime Kuma container, so it is not in its memory. Monitor fields are those the 2.5.7 web UI sends for a new monitor.
- The admin password is a lab-only value written in this file; the server is never reachable from outside the lab network and is removed with its volume after each run. There is no .env and no secret in compose.yaml.
- Lab-only compose override compose.lab.yaml (NOT in the published compose): Adds a `target` container (nginx 1.30.6-alpine, answers 200 on every path, no access log) that the 30 monitors watch, so the test sends no traffic outside the Compose network. The published file has Uptime Kuma alone.
- Service(s) target exist only in the lab-only compose override and are NOT in the published compose; their figures are in the result, their image line is not in the verified compose.
Verified compose file
One service, uptime-kuma, and one named volume. The image is pinned to a full v2 release, with the digest we tested in a comment, so an upgrade is a step you take on purpose. Uptime Kuma’s Docker Tags wiki page lists 2 as the recommended floating tag for the latest v2 release and calls latest deprecated, “always pointing to v1”. The comments in the file are the lab’s.
# Verified compose for Uptime Kuma, tested 2026-10-26 by the SelfHostBench lab (3 runs; the figures are the median).
# Lab host: AMD EPYC 9V45 96-Core Processor, 2 vCPU, 7.8 GiB RAM, x86_64, Docker 28.0.4.
# Floating image tags below were replaced by the version resolved at test time;
# the registry digest is in the comment on each image line.
# Measurements: uptime-kuma.json (schema 3).
# Lab difference: Adds a `target` container (nginx 1.30.6-alpine, answers 200 on every path, no access log) that the 30 monitors watch, so the test sends no traffic outside the Compose network. The published file has Uptime Kuma alone.
# Uptime Kuma 2.5.7: self-hosted uptime monitor. Single container, SQLite in /app/data.
# Pinned to a full v2 release so upgrades are a deliberate step (the floating `2` tag follows the v2 stable line; `latest` still pointed at v1 as of 2026-10).
# UPTIME_KUMA_DB_TYPE=sqlite picks the database up front. Without it v2 opens a "setup database" wizard (SQLite or MariaDB) on the first visit
# and does not start until you have answered it. Wiki: https://github.com/louislam/uptime-kuma/wiki/Environment-Variables
services:
uptime-kuma:
image: louislam/uptime-kuma:2.5.7 # sha256:4b95dbe7ac5343b768f8e04e3035ccd59997872b1a35ab9b0f082d2cc279070e (version 2.5.7, from exec)
restart: unless-stopped
environment:
# Use the built-in SQLite database (/app/data/kuma.db) and skip the database wizard. The first visit then asks for the admin account only.
UPTIME_KUMA_DB_TYPE: sqlite
volumes:
# Monitors, history and settings. Back this up.
- kuma-data:/app/data
ports:
- "127.0.0.1:3001:3001"
volumes:
kuma-data:How it differs from the compose file in the project’s repository: that file uses the floating 2 tag, a ./data bind mount and publishes 3001:3001 on all interfaces. Ours pins 2.5.7, uses a named volume, binds the port to 127.0.0.1 and sets the database up front (next section). The port binding means the interface is not reachable from other machines until you put a reverse proxy in front or change the port line. Uptime Kuma’s installation wiki page maps /app/data to a directory or volume and warns that SQLite needs a filesystem with POSIX file locks: NFS is its example of a place where locking problems appear. Keep the volume on local disk.
The database choice
Uptime Kuma 2 asks which database to use on the first visit, and the server does not start until you answer. The published file avoids the question with one line, UPTIME_KUMA_DB_TYPE: sqlite. The official environment variables page lists that variable (sqlite or mariadb, since 2.0.0) and a flag for an embedded MariaDB, which it lists under “For Development only”.
We ran both versions of the file on 2.5.7 on a fresh volume (manual check, Oct 27, 2026; raw output in lab/notes/uptime-kuma-2026-10-26.md, sections 2 and 3):
| File | Root address | Log | /app/data |
|---|---|---|---|
Published file without the environment block |
Redirected to /setup-database |
Ended with Waiting for user action... |
No kuma.db, no db-config.json |
| Published file as it is | Redirected to /dashboard |
No user, need setup, then listening |
kuma.db and a 42-byte db-config.json |
Without the setting the container still passes its health check, so docker compose up -d --wait reports it healthy while it waits for a browser. In all three lab runs the setup step confirmed the other case: the server answered that it needed no database setup, we created the admin account through the same Socket.IO API the web page uses, and a login worked. The lab’s idle figure is therefore a running Uptime Kuma with an admin account and no monitors. We did not try MariaDB, embedded or external.
Install steps
We ran the harness steps in all three lab runs: docker compose up -d, an HTTP check on http://uptime-kuma:3001/ that expected status 200, and the account step above. The other commands below ran by hand on our sandbox host on Oct 27, 2026 (Docker 29.6.2, Compose 5.3.1), one time each, on 2.5.7; the raw output is in lab/notes/uptime-kuma-2026-10-26.md.
-
Install Docker Engine with the Compose plugin.
-
Make a folder and save the file above as
compose.yamlin it. The download link is on the block.bash mkdir uptime-kuma cd uptime-kuma -
Start it.
--waitreturns when Docker reports the container healthy. The first start pulls 616.8 MB (compressed).bash docker compose up -d --waitThe image ships its own health check (
extra/healthcheck; interval 60 s, start period 180 s, perdocker inspect), which is why the compose file has none. -
Open
http://127.0.0.1:3001/. On a remote machine, use an SSH tunnel, or put a reverse proxy in front (see Gotchas). Not run in our lab: the pages in a browser. The root address redirected to/dashboard, and the server log saidNo user, need setup. -
Create the first account. We created ours through the API with a script, and it reported the account created and a working login; a browser was not used.
To stop it, docker compose down removes the container and keeps the named volume. Adding -v deletes the volume, which holds your monitors, history and settings.
Measured resources
Source: uptime-kuma.json, three runs on Oct 26, 2026, each from a fresh stack with docker compose up -d, on the lab host (a GitHub Actions ubuntu-latest runner: 2 vCPU AMD EPYC 9V45, 7.8 GiB RAM, Docker 28.0.4, Compose 2.38.2). Every figure is the median of the three runs with the range, lowest to highest (why we repeat runs). Each run went through three states:
- Idle: an admin account, no monitors. The figure is the mean of the samples over about 31 s, taken after the container’s memory had settled (124.3 s after it was healthy, 123.4 to 125.3 over the three runs; rule on the methodology page).
- Startup peak: the highest reading between healthy and settled.
- 30 monitors: 25 HTTP, 3 TCP port and 2 ping monitors, each at a 20 s interval with no retries and no notifications, added through the API and left running for 10 minutes with no dashboard open. That is 30 checks every 20 s, about 90 a minute. All 30 pointed at one stock nginx container on the Compose network that answers 200 on every path, so no traffic left the lab.
That nginx container, target, is in the lab’s second compose file and not in the file above. Every RAM figure on this page and in the Test box is the Uptime Kuma container alone and leaves target out. It used 3.25 MiB at idle; the result file’s own stack totals add it (131.74 MiB idle, 149.74 MiB peak).
| Measure | Median (range of 3 runs) |
|---|---|
| Idle RAM, account and 0 monitors (MiB) | 128.52 (128.34 to 128.53) |
| Startup peak RAM (MiB) | 158.5 (158.4 to 158.5) |
| Mean RAM during the 30-monitor window (MiB) | 142.80 (137.13 to 143.82) |
| Peak RAM, 30 monitors (MiB) | 146.0 (144.2 to 146.9) |
| Idle CPU (% of one core, lab host) | 0.20 (0.19 to 0.35) |
| Mean CPU, 30 monitors (% of one core, lab host) | 1.50 (1.27 to 1.76) |
| Highest CPU sample, 30 monitors (% of one core, lab host) | 68.3 (59.2 to 68.4) |
Web server answered after docker compose up (s, lab host) |
2.3 (2.2 to 2.3) |
| Heartbeats stored per run, up / recorded (900 expected) | 928 / 928 in all 3 runs |
| Image, compressed (MB) | 616.8 |
| Image, unpacked (MB) | 1,740 |
The highest CPU sample sits in the first seconds, while the 30 monitors are created (0.4 to 0.5 s in the three runs); the mean over the 10 minutes is the number to plan with. The heartbeats are what the database held after the window, read back through the API: 775 from the HTTP monitors, 93 from the TCP monitors and 60 from the ping monitors in every run, none down, and all 30 monitors were created in each run. The container did not restart and was not killed for memory in any run. Peak RAM is the highest sample (about one every 2 to 3 s) of the 10-minute window; shorter spikes between samples are not seen.
The compressed size is the registry’s number for linux/amd64. The unpacked size is what Docker reports on the lab host, which uses the classic overlay2 image store. Docker Engine 29 and later uses the containerd store on fresh installs, which also keeps the compressed layers; for disk we plan for unpacked plus compressed size plus 10% (how we size disk), which is 2,600 MB for this image.
What the numbers say:
- The container used 129 MiB at idle with an account and no monitors. During the 10 minutes with 30 monitors its RAM averaged 142.8 MiB, about 14 MiB above idle, and its highest reading was 146.0 MiB. CPU averaged 1.5% of one core on the lab host.
- By our sizing rule (the highest reading in any run, 158.5 MiB, times 1.5, rounded up to 1, 2, 4, 8, 16 or 32 GB) it needs 1 GB. The highest reading came at startup, not under the 30-monitor load. For comparison, our Home Assistant Docker Compose guide measured 309 MiB idle.
- The load is light: 30 checks against a container on the same network, answered in about a millisecond (the mean response time of the UP heartbeats was 1.1 to 1.4 ms). A real install also pays for DNS lookups and TLS handshakes to other people’s sites, which these checks do not show.
Gotchas, upgrade and backup
Reverse proxy: WebSockets
The reverse proxy wiki page says Uptime Kuma is based on WebSocket, so the proxy has to pass the Upgrade and Connection headers (Nginx Proxy Manager has a “WebSockets Supports” switch). It gives this Caddy example for a proxy on the same machine. Not run in our lab:
subdomain.domain.com {
reverse_proxy 127.0.0.1:3001
}The same page says to set Settings, Reverse Proxy, “Trust Proxy” to Yes when the app is reachable only through a proxy, so the logs show the real client address. For a working Caddy-in-front setup with HTTPS, see our Vaultwarden Docker Compose guide; we did not put Caddy in front of Uptime Kuma.
Tags: slim and rootless
The Docker Tags page says the full image includes the embedded MariaDB and Chromium (for the browser-engine monitor type) and that the -slim images omit both and are 300 to 400 MB smaller; it also lists -rootless tags with the warning that some features may not work. We tested the full 2.5.7 image only.
Run the monitor on a different machine
Our advice, not a measurement: a monitor on the same machine as the apps it watches cannot report that machine going down. A small separate host is the usual answer. At 129 MiB idle the container fits a 1 GB plan by our sizing rule (see Where to run it).
Update
Not run in our lab. Within v2, set the new tag and recreate the container:
docker compose pull
docker compose up -dBack up first. We did not test any upgrade between 2.x versions.
Upgrading from 1.x
Not run in our lab; this is the official v1 to v2 migration page as we read it on Oct 26, 2026:
- Stop Uptime Kuma, then back up the
datadirectory. The page says this is currently the only supported backup method. - Change the image to
louislam/uptime-kuma:2, start it with the same volume, and watchdocker compose logs -f. - The upgrade aggregates the heartbeat table into a new format. The page’s own example (20 monitors, 90 days of data) took about 7 minutes; the page warns it can take hours on slower hardware or with more monitors, and not to interrupt it. If it is interrupted, restore from the backup and retry.
- The page lists breaking changes, including Alpine-based images no longer being supported, rootless images not being recommended for the upgrade, new monitors defaulting to 0 retries, and JSON backup and restore being removed.
- Moving from SQLite to MariaDB is not officially supported, and the page advises against the third-party converter it names.
Back up and restore
The official guidance is the data directory, copied with the app stopped. We ran this on Oct 27 on 2.5.7 with SQLite, an empty database and one account. The volume is named <folder>_kuma-data by Compose; ours was uptime-kuma_kuma-data. We used the app image itself to run tar, so nothing else is pulled:
docker compose stop
mkdir backup
docker run --rm --user root -v uptime-kuma_kuma-data:/data:ro -v "$PWD/backup":/backup --entrypoint tar louislam/uptime-kuma:2.5.7 czf /backup/kuma-data.tar.gz -C /data .The archive held kuma.db, db-config.json, upload, screenshots and docker-tls (14,599 bytes). tar ran as root, so expect the archive file to be owned by root on the host (we did not check). The restore below is a drill: we extracted the archive into a new empty volume and started a second container on that volume, next to the stopped stack, not in place of it:
docker volume create kumarestore
docker run --rm --user root -v kumarestore:/data -v "$PWD/backup":/backup:ro --entrypoint tar louislam/uptime-kuma:2.5.7 xzf /backup/kuma-data.tar.gz -C /data
docker run -d --name kumarestore -e UPTIME_KUMA_DB_TYPE=sqlite -v kumarestore:/app/data -p 127.0.0.1:3002:3001 louislam/uptime-kuma:2.5.7The restored container did not ask for setup, and logging in with the original account succeeded (Successfully logged in user labadmin in its log). To remove the drill container and volume afterwards:
Not run in our lab.
docker rm -f kumarestore
docker volume rm kumarestoreNot tested: monitors and history in the backup, a backup taken while the container runs, a MariaDB setup, and restoring over a live volume. On a different app (Vaultwarden, SQLite) we showed that a plain copy of a running database file can look fine and still lose recent data; see how to back up self-hosted Bitwarden. We did not test that for Uptime Kuma, so stop the container first. For a tool that copies the archive off the machine on a schedule, see restic vs Borg vs Kopia, measured on one generated dataset.
What we did not measure
- More than 30 monitors, monitors with a shorter interval than 20 s or with retries, HTTPS and keyword monitors against real sites, notification providers, the status page feature, and an open dashboard (an open dashboard receives every heartbeat; ours was closed during the window).
- History growth and long-term memory: the window was 10 minutes, on a database that started empty.
- MariaDB (embedded or external), the
-slimand-rootlessimages, and ARM hosts. - Any update between versions.
- The setup pages and the dashboard in a browser, and a reverse proxy in front.
Where to run it
- Hardware. Uptime Kuma idled at 129 MiB and peaked at 146 MiB under load (medians of 3 runs) in our lab. The highest reading in any run was 159 MiB, counting the startup spike, which was higher than the load peak. Plan for at least 1 GB of RAM, sized from the highest run.
- VPS. The smallest plan that runs this comfortably has 1 GB of RAM, plus disk for your data and the images. Plan for about 2.6 GB for the images (Docker 29 and later with the containerd image store, the default on fresh installs; 1.7 GB with the older overlay2 store).
- Keep it safe. Back up the data volume before you rely on it, and put the app behind a reverse proxy with HTTPS before you expose it to the internet. See backing up self-hosted apps.
How we size: recommended RAM is the highest reading in any of our runs (under load or during startup, not the median) times 1.5, rounded up to the next of 1, 2, 4, 8, 16 or 32 GB. The extra half covers the operating system, Docker, file cache and spikes our sampling can miss. Your own data and extra apps need more. Disk for images: Docker Engine 29 and later uses the containerd image store on fresh installs, which keeps each image twice (as downloaded and unpacked); older installs use overlay2, which keeps it unpacked only. We plan for the larger figure: unpacked plus compressed size, plus 10%, rounded up. Your data volumes and growth come on top.
Related
- Other apps in the same format: the Vaultwarden Docker Compose guide (a password server, 25 MiB idle with Caddy), the Home Assistant Docker Compose guide (309 MiB idle) and the Immich Docker Compose guide, a stack where RAM decides the host.
- Another small always-on container for the same box: AdGuard Home vs Pi-hole v6, a DNS ad blocker pair measured the same way. We did not run it next to Uptime Kuma.
- How the lab measures RAM, CPU and image sizes: methodology. More apps: the self-hosted apps index.
- Official sources used on this page, read Oct 26, 2026 (the 2.5.7 release notes again on Oct 27): the Uptime Kuma wiki pages on installation, Docker tags, environment variables, the v1 to v2 migration and reverse proxy, the repository’s compose.yaml, and the release notes for 2.5.7. The pages were read through a web fetch tool that summarizes them, so quotes are limited to short phrases.
Update log
- First published. Uptime Kuma 2.5.7, three runs on Oct 26, 2026 on a GitHub Actions runner: an admin account with no monitors, then 30 monitors (25 HTTP, 3 TCP, 2 ping) checked every 20 s for 10 minutes. The compose file sets UPTIME_KUMA_DB_TYPE=sqlite. Manual checks of the first start with and without that setting and of a backup and restore, on Oct 27, 2026.