Docker Uptime Monitoring for Small Teams
Docker uptime monitoring means tracking whether your containers and the host they run on are actually up and serving traffic, then getting alerted the moment something stops. For a small team, the goal is simple: know your service is down before a customer emails you about it. You do not need Kubernetes or a Prometheus cluster to get there. A health check, a sane restart policy, and one alerting path that reaches a human phone will cover most of what a two-to-five person team needs.
What "uptime" actually means for a container
A container can be in the running state and still be broken. The process is alive, docker ps looks green, but the app inside is deadlocked, out of file descriptors, or returning 500s on every request. So there are two layers you care about.
The first is liveness: is the container process running at all? The second is readiness: is the app inside it responding correctly? Real uptime monitoring checks readiness. If all you confirm is that the process is alive, you will miss the deadlocked app that still shows green in docker ps. Docker gives you a built-in tool for readiness with health checks, and it is the foundation everything else sits on.
Start with a health check
A HEALTHCHECK tells Docker how to ask your container "are you actually working?" on an interval. Add it to your Dockerfile:
FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm ci --omit=dev
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD wget -qO- http://localhost:3000/health || exit 1
CMD ["node", "server.js"]
Or set it in docker-compose.yml without touching the image:
services:
api:
image: myorg/api:latest
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 5s
start_period: 20s
retries: 3
restart: unless-stopped
The container now reports a status you can see:
docker ps --format 'table {{.Names}}\t{{.Status}}'
# NAME STATUS
# api Up 4 minutes (healthy)
That (healthy) / (unhealthy) string is the signal your monitoring reads. Point your /health endpoint at something real, a database ping or a check of a downstream dependency, not just a route that returns 200 no matter what. A health check that always passes is worse than none because it lies to you. If you want to go deeper on writing endpoints that catch the right failures, see Docker Health Checks Explained, With Examples.
Set a restart policy so small failures self-heal
Most outages are transient. A memory spike, a dropped database connection, a crash on a bad request. A restart policy handles those without waking anyone:
services:
api:
restart: unless-stopped
Or on a plain docker run:
docker run -d --restart unless-stopped --name api myorg/api:latest
Use unless-stopped over always so a container you deliberately stopped stays stopped across a daemon restart. Skip on-failure unless you specifically want the container to give up after non-zero exits.
One caveat: a restart policy without monitoring hides problems. A container that restarts every 40 seconds looks fine in docker ps for the brief windows it is up, but it is effectively down. Watch the restart count:
docker inspect --format '{{.RestartCount}}' api
If that number climbs, you have a crash loop, not a healthy service. That failure mode is common enough that it has its own playbook: How to Fix a Docker Container Stuck in a Restart Loop.
Add autoheal for unhealthy containers
Docker restarts a container that exits. It does not restart one that goes unhealthy but keeps running. To close that gap, the small-team standard is the autoheal container, which watches health status and restarts anything that flips unhealthy:
services:
autoheal:
image: willfarrell/autoheal
restart: unless-stopped
environment:
AUTOHEAL_CONTAINER_LABEL: all
volumes:
- /var/run/docker.sock:/var/run/docker.sock
Mounting the Docker socket gives this container control over your daemon, so treat it like a privileged process and only run images you trust. With AUTOHEAL_CONTAINER_LABEL: all it watches every container that has a health check defined.
Now make it tell you
Self-healing is great until it fails to heal. The whole point of uptime monitoring is the alert. You want a message on someone's phone when a host goes offline or a container will not come back. A few options that fit a small team:
Uptime Kuma
A self-hosted monitor you run as its own container. It pings HTTP endpoints, TCP ports, or ping targets on a schedule and pushes notifications to Slack, Telegram, Discord, email, or a webhook.
services:
uptime-kuma:
image: louislam/uptime-kuma:1
restart: unless-stopped
volumes:
- ./kuma-data:/app/data
ports:
- "3001:3001"
It monitors from the outside, so it catches the case where the whole host is down, which an in-host agent never can. The tradeoff is that it should run somewhere other than the server it is watching. A $5 VPS or a second box is enough.
A cron-driven check
If you want almost nothing to maintain, a shell script and cron will do. This checks health status and posts to a Slack webhook when a container is not healthy:
#!/usr/bin/env bash
for c in $(docker ps --format '{{.Names}}'); do
status=$(docker inspect --format '{{.State.Health.Status}}' "$c" 2>/dev/null)
if [ "$status" = "unhealthy" ]; then
curl -s -X POST "$SLACK_WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d "{\"text\":\"Container $c is unhealthy on $(hostname)\"}"
fi
done
# crontab -e
* * * * * /opt/scripts/check-health.sh
Crude, but it works and you understand every line of it. The same warning applies as above: if the host itself dies, cron dies with it, so this only catches container-level problems.
From your phone
Most small-team on-call happens away from a desk. This is where Docker HQ fits: it connects to your hosts over SSH, shows container and host status live, and sends push alerts when a host or container goes down, so the pager lives in your pocket instead of a browser tab you forgot to keep open. You can open a shell or read logs from the same screen to triage before you get to a laptop. Pricing for team access with per-host RBAC is on the pricing page.
Watch the host, not only the containers
Container uptime does not matter if the host runs out of disk and the Docker daemon wedges. Keep an eye on the three things that take down small setups most often: disk, memory, and the daemon itself.
# disk, the classic silent killer via log growth
df -h /var/lib/docker
# per-container live resource use
docker stats --no-stream
# is the daemon even up?
systemctl is-active docker
Container logs are the usual reason a disk fills. Cap them in your daemon config so one chatty service does not sink the box:
// /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
Restart the daemon after changing that, and note it only applies to containers created afterward.
A Docker uptime monitoring setup that fits a small team
You do not need all of the above at once. A reasonable order:
- Add a real
HEALTHCHECKto every service. - Set
restart: unless-stopped. - Run
autohealto restart unhealthy containers. - Add one external monitor (Uptime Kuma or a phone app) that can alert you when the host is gone.
- Cap log sizes and watch disk.
That is a full uptime story: detect, self-heal, and alert when self-healing is not enough. For the broader picture of metrics, dashboards, and log aggregation once you outgrow this, the complete guide to Docker container monitoring covers the next steps.
Frequently asked questions
What is the difference between a Docker health check and uptime monitoring?
A health check runs inside Docker and reports whether one container is working. Uptime monitoring is the layer on top: it collects those statuses, checks the host, and alerts a human when something is wrong. Health checks are an input to monitoring, not a replacement for it. A health check on a dead host reports nothing, which is exactly when you most need to know.
How often should a health check run?
A 30-second interval with a 3-retry threshold is a sane default, so a real failure is confirmed in about 90 seconds without flapping on a single slow response. Set start_period long enough to cover your app's boot time, otherwise Docker marks the container unhealthy during a normal startup and restart loops kick in.
Do I need Prometheus and Grafana for a small team?
Usually not to start. Prometheus and Grafana are excellent for metrics and historical dashboards, but they are more to run and tune than a small team needs on day one. Begin with health checks, autoheal, and a single alerting path. Add a metrics stack when you actually want trends and capacity planning, not before.
Can I monitor Docker uptime without opening a port on my server?
Yes. Tools that connect over SSH read container and host status through your existing SSH access, so you avoid exposing a monitoring dashboard or agent port to the internet. That is how Docker HQ reaches hosts. If you prefer a self-hosted dashboard like Uptime Kuma, keep it behind a VPN or reverse proxy with auth rather than a raw open port.
Next step
Pick your least-observed service and add a HEALTHCHECK that hits a /health endpoint doing a real dependency check today. Then confirm it works by forcing a failure, stop the dependency and watch docker ps flip the container to (unhealthy). If nothing tells you about that flip, you have found the gap to close first.