Production readiness checklist
Before a self-hosted server goes live, go through what usually breaks first. Each item says what to check, gives a read-only command to run on the server, explains why it matters and links to the tool on this site that helps. Your marks are kept in the link, never sent.
Progress
0 of 33 items checked.
Backup and restore
- What to check
- A timer or cron job runs the backup, and the copy lands on another machine or in object storage, not only on this disk.
- How to check
Read-only command systemctl list-timers --all --no-pager; crontab -l- Why it matters
- A failed disk, a deleted VM or ransomware takes every copy that sits on the same server with it.
- What to check
- Every named volume and bind mount that holds data is in the backup, and each database is saved with its own dump tool (for example
pg_dump), not by copying its files while it runs. - How to check
Read-only command docker volume ls; docker ps --format '{{.Names}}: {{.Mounts}}'- Why it matters
- Files a database is writing to can be copied in an inconsistent state, and the restore fails exactly when you need it.
- What to check
- A recent backup was restored on another machine and the app started with its data. Note the date and how long it took.
- How to check
Read-only command ls -lt <backup-dir> | head- Why it matters
- A backup that was never restored is untested. Restoring it once shows what is missing and how long the service would be down.
Security updates
- What to check
- unattended-upgrades is installed and enabled, and its daily run is switched on.
- How to check
Read-only command systemctl is-enabled unattended-upgrades; cat /etc/apt/apt.conf.d/20auto-upgrades- Why it matters
- A published fix protects the server only once it is installed. Automatic security updates install it without waiting for you to log in.
- Tools on this site
- What to check
- No kernel or library update is waiting for a reboot. Plan reboots in a maintenance window.
- How to check
Read-only command cat '/var/run/reboot-required.pkgs' 2>/dev/null || echo 'nothing pending'- Why it matters
- An installed kernel fix protects nothing until the server boots into it.
- What to check
- You know how old each running image is and follow the security releases of each app.
- How to check
Read-only command docker image ls --format '{{.Repository}}:{{.Tag}} {{.CreatedSince}}'- Why it matters
- unattended-upgrades does not touch containers: an image keeps the libraries it was built with until you pull a newer one.
- Tools on this site
Exposed ports and firewall
- What to check
- Every socket on
0.0.0.0or[::]is one you meant to expose. Databases and admin panels listen on127.0.0.1or on a private network. - How to check
Read-only command sudo ss -tulpn- Why it matters
- Public addresses are scanned all the time. What does not listen publicly cannot be reached from outside.
- Tools on this site
- What to check
- Incoming traffic is denied by default, and only SSH and the ports of your reverse proxy are allowed.
- How to check
Read-only command sudo ufw status verbose- Why it matters
- A default-deny firewall stops a service you start later from being reachable by accident.
- Tools on this site
- What to check
- Each published port is bound to
127.0.0.1unless it must be public. A plain8080:80listens on every address. - How to check
Read-only command docker ps --format '{{.Names}}: {{.Ports}}'- Why it matters
- Docker writes its own iptables rules, so ports it publishes are reachable even when ufw denies them.
- Tools on this site
- What to check
passwordauthenticationisnoandpermitrootloginisnoorprohibit-passwordin the effective SSH configuration.- How to check
Read-only command sudo sshd -T | grep -Ei '^(passwordauthentication|permitrootlogin) '- Why it matters
- Password guessing against SSH never stops on a public address. With keys only, a guessed password opens nothing.
- Tools on this site
TLS and DNS
- What to check
- The A record (and the AAAA record, if there is one) of each domain holds this server's address. Remove an old AAAA record rather than leave it pointing elsewhere.
- How to check
Read-only command dig +short A <domain>; dig +short AAAA <domain>- Why it matters
- A stale record sends part of your visitors to another machine, and certificate validation can fail over IPv6.
- Tools on this site
- What to check
- The certificate covers the domain, its end date is weeks away, and the proxy (or a timer) renews it without you.
- How to check
Read-only command openssl s_client -connect <domain>:443 -servername <domain> </dev/null 2>/dev/null | openssl x509 -noout -subject -dates- Why it matters
- Certificates from Let's Encrypt are short-lived; when renewal breaks silently, browsers block the site on the day it expires.
- Tools on this site
- What to check
- A request over plain HTTP gets a permanent redirect (301 or 308) to the HTTPS address.
- How to check
Read-only command curl -sI http://<domain>/ | head -n 3- Why it matters
- Without the redirect, logins and cookies can travel unencrypted from links and old bookmarks.
- Tools on this site
Secrets
- What to check
- Passwords, tokens and keys come from an
.envfile or Compose secrets, and the Compose file can be shared or committed as it is. - How to check
Read-only command grep -nEi '(password|secret|token|api_?key)[^:=]*[:=]' docker-compose.yml- Why it matters
- A Compose file gets pasted into issues, chats and repositories; a secret inside it leaks with it.
- Tools on this site
- What to check
.envhas mode600, belongs to the deploy user and is listed in.gitignore.- How to check
Read-only command stat -c '%a %U %n' .env; git check-ignore -v .env- Why it matters
- Every account on the server, and every backup of the directory, can otherwise read the secrets.
- Tools on this site
- What to check
- No
ARGorENVin the build history holds a secret; build-time secrets use a secret mount. - How to check
Read-only command docker history --no-trunc <image>- Why it matters
- Anyone who can pull the image can read its layers and its build history.
- Tools on this site
Resources and limits
- What to check
- Under normal load there is free memory left and the data disk is well below full.
- How to check
Read-only command free -h; df -h- Why it matters
- A full disk stops databases from writing; running out of memory makes the kernel kill a process, often the largest one.
- Tools on this site
- What to check
- Each container shows a limit lower than the server's memory, so one runaway service cannot take the others down.
- How to check
Read-only command docker stats --no-stream --format '{{.Name}}: {{.MemUsage}}'- Why it matters
- Without a limit a leak grows until the kernel's OOM killer picks a victim, which may be your database.
- Tools on this site
- What to check
- Old images, stopped containers and build cache do not fill the disk; a cleanup runs on a schedule you chose.
- How to check
Read-only command docker system df- Why it matters
- Every update leaves the previous image behind; on a small disk that adds up within months.
- What to check
- Small servers have some swap as a buffer; you know its size and that it is not in constant use.
- How to check
Read-only command cat /proc/swaps; vmstat 1 5- Why it matters
- A little swap absorbs short peaks; constant swapping means the server is too small for its load.
- Tools on this site
Health checks and restart
- What to check
- Each long-running container uses
unless-stoppedoralways, so it comes back after a crash or a reboot. - How to check
Read-only command docker inspect --format '{{.Name}}: {{.HostConfig.RestartPolicy.Name}}' $(docker ps -q)- Why it matters
- The default policy is
no: after a reboot for a kernel update, the service stays down until someone notices. - Tools on this site
- What to check
- Each service that others depend on has a health check and shows
(healthy); dependents wait forservice_healthy. - How to check
Read-only command docker ps --format '{{.Names}}: {{.Status}}'- Why it matters
- A running process is not a working service. A health check tells Docker and you the difference.
- Tools on this site
- What to check
- No container is in the restarting state, and the restart counts stay at zero after a day.
- How to check
Read-only command docker ps -a --filter status=restarting; docker inspect --format '{{.Name}}: {{.RestartCount}}' $(docker ps -aq)- Why it matters
- A restart policy hides a crash: the service looks up most of the time while it fails over and over.
- Tools on this site
- What to check
- The Docker service is enabled, so the containers come back after a reboot.
- How to check
Read-only command systemctl is-enabled docker- Why it matters
- Restart policies apply only once the Docker daemon itself is running.
- Tools on this site
Logs and rotation
- What to check
- The logging driver is
local, orjson-filewithmax-sizeandmax-file, set in/etc/docker/daemon.jsonor on each service. - How to check
Read-only command docker info --format '{{.LoggingDriver}}'; cat /etc/docker/daemon.json- Why it matters
- The default
json-filedriver never rotates: one noisy container can fill the disk. - Tools on this site
- What to check
- The journal's disk use is known and capped (
SystemMaxUseinjournald.conf, or the default share of the disk). - How to check
Read-only command journalctl --disk-usage- Why it matters
- System logs grow with every SSH attempt and every service restart; a cap keeps them from crowding out your data.
- What to check
- The logs reach back far enough to cover an incident you notice a day later, with timestamps.
- How to check
Read-only command docker compose logs --timestamps --since 24h --tail 20- Why it matters
- Rotation that keeps too little erases the cause before anyone looks.
- Tools on this site
Monitoring and alerts
- What to check
- An uptime check from another machine or service calls each public address every few minutes.
- How to check
Read-only command curl -fsS -o /dev/null -w '%{http_code} %{time_total}s\n' https://<domain>/- Why it matters
- A monitor on the same server goes down with it, and cannot tell you that it did.
- Tools on this site
- What to check
- Metrics are collected, and a threshold on disk space, memory and load sends an alert before the server runs out.
- How to check
Read-only command uptime; df -h /- Why it matters
- A disk that fills up over weeks gives plenty of warning, but only to someone who is watching.
- Tools on this site
- What to check
- A test alert was sent and arrived on a channel someone reads: email, chat or phone.
- How to check
Read-only command systemctl list-units --type=service --state=running --no-pager | grep -Ei 'alert|monitor|exporter'- Why it matters
- An alert nobody receives is the same as no alert.
- Tools on this site
Upgrade and rollback plan
- What to check
- No service runs
latestor an untagged image; each one names a version, or a digest. - How to check
Read-only command docker ps --format '{{.Names}}: {{.Image}}'- Why it matters
- With
latest, a restart can pull a new major version you never chose, and there is no known version to return to. - Tools on this site
- What to check
- You know the running version of each app and what changed up to the next one, especially database migrations and breaking changes.
- How to check
Read-only command docker image inspect --format '{{index .RepoTags 0}} {{index .RepoDigests 0}}' <image>- Why it matters
- Many apps migrate their database on the first start of a new version; some migrations cannot be undone.
- Tools on this site
- What to check
- Before each upgrade: a fresh backup, the previous version written down, and the steps to return to it, including restoring the data.
- How to check
Read-only command docker image ls <image>- Why it matters
- After a migration, going back to the old image alone often fails; the way back is the old image with the backup taken before the upgrade.
- Tools on this site
Export
# Production readiness checklist
0 of 33 items checked.
Source: https://anaroq.com/tools/production-readiness-checklist
## Backup and restore (0/3)
- [ ] **Backups run on a schedule and leave the server**
- Check: A timer or cron job runs the backup, and the copy lands on another machine or in object storage, not only on this disk.
- Command: `systemctl list-timers --all --no-pager; crontab -l`
- Why: A failed disk, a deleted VM or ransomware takes every copy that sits on the same server with it.
- [ ] **The backup covers the volumes and the databases**
- Check: Every named volume and bind mount that holds data is in the backup, and each database is saved with its own dump tool (for example `pg_dump`), not by copying its files while it runs.
- Command: `docker volume ls; docker ps --format '{{.Names}}: {{.Mounts}}'`
- Why: Files a database is writing to can be copied in an inconsistent state, and the restore fails exactly when you need it.
- [ ] **A restore was tested**
- Check: A recent backup was restored on another machine and the app started with its data. Note the date and how long it took.
- Command: `ls -lt <backup-dir> | head`
- Why: A backup that was never restored is untested. Restoring it once shows what is missing and how long the service would be down.
## Security updates (0/3)
- [ ] **Security updates install automatically**
- Check: unattended-upgrades is installed and enabled, and its daily run is switched on.
- Command: `systemctl is-enabled unattended-upgrades; cat /etc/apt/apt.conf.d/20auto-upgrades`
- Why: A published fix protects the server only once it is installed. Automatic security updates install it without waiting for you to log in.
- On ANAROQ: [cloud-init Generator for Ubuntu 24.04](https://anaroq.com/tools/cloud-init-generator)
- [ ] **No reboot is pending**
- Check: No kernel or library update is waiting for a reboot. Plan reboots in a maintenance window.
- Command: `cat '/var/run/reboot-required.pkgs' 2>/dev/null || echo 'nothing pending'`
- Why: An installed kernel fix protects nothing until the server boots into it.
- [ ] **Container images are updated, not only the OS**
- Check: You know how old each running image is and follow the security releases of each app.
- Command: `docker image ls --format '{{.Repository}}:{{.Tag}} {{.CreatedSince}}'`
- Why: unattended-upgrades does not touch containers: an image keeps the libraries it was built with until you pull a newer one.
- On ANAROQ: [Release Tracker](https://anaroq.com/self-host/releases)
## Exposed ports and firewall (0/4)
- [ ] **Only the ports you meant to open listen publicly**
- Check: Every socket on `0.0.0.0` or `[::]` is one you meant to expose. Databases and admin panels listen on `127.0.0.1` or on a private network.
- Command: `sudo ss -tulpn`
- Why: Public addresses are scanned all the time. What does not listen publicly cannot be reached from outside.
- On ANAROQ: [Port Map](https://anaroq.com/self-host/ports), [Docker port publishing: 127.0.0.1, 0.0.0.0 and ufw](https://anaroq.com/learn/docker-port-publishing)
- [ ] **The firewall is on and denies by default**
- Check: Incoming traffic is denied by default, and only SSH and the ports of your reverse proxy are allowed.
- Command: `sudo ufw status verbose`
- Why: A default-deny firewall stops a service you start later from being reachable by accident.
- On ANAROQ: [cloud-init Generator for Ubuntu 24.04](https://anaroq.com/tools/cloud-init-generator)
- [ ] **Ports published by Docker were reviewed**
- Check: Each published port is bound to `127.0.0.1` unless it must be public. A plain `8080:80` listens on every address.
- Command: `docker ps --format '{{.Names}}: {{.Ports}}'`
- Why: Docker writes its own iptables rules, so ports it publishes are reachable even when ufw denies them.
- On ANAROQ: [Docker Compose Analyzer](https://anaroq.com/tools/compose-analyzer), [Docker port publishing: 127.0.0.1, 0.0.0.0 and ufw](https://anaroq.com/learn/docker-port-publishing)
- [ ] **SSH accepts keys only, and no root login**
- Check: `passwordauthentication` is `no` and `permitrootlogin` is `no` or `prohibit-password` in the effective SSH configuration.
- Command: `sudo sshd -T | grep -Ei '^(passwordauthentication|permitrootlogin) '`
- Why: Password guessing against SSH never stops on a public address. With keys only, a guessed password opens nothing.
- On ANAROQ: [SSH config generator and key helper](https://anaroq.com/tools/ssh-config-generator), [cloud-init Generator for Ubuntu 24.04](https://anaroq.com/tools/cloud-init-generator)
## TLS and DNS (0/3)
- [ ] **DNS points at this server, and only at it**
- Check: The A record (and the AAAA record, if there is one) of each domain holds this server's address. Remove an old AAAA record rather than leave it pointing elsewhere.
- Command: `dig +short A <domain>; dig +short AAAA <domain>`
- Why: A stale record sends part of your visitors to another machine, and certificate validation can fail over IPv6.
- On ANAROQ: [Reverse proxy config generator: Caddy, Traefik and nginx](https://anaroq.com/tools/reverse-proxy-generator)
- [ ] **The certificate is valid and renews by itself**
- Check: The certificate covers the domain, its end date is weeks away, and the proxy (or a timer) renews it without you.
- Command: `openssl s_client -connect <domain>:443 -servername <domain> </dev/null 2>/dev/null | openssl x509 -noout -subject -dates`
- Why: Certificates from Let's Encrypt are short-lived; when renewal breaks silently, browsers block the site on the day it expires.
- On ANAROQ: [Reverse proxy config generator: Caddy, Traefik and nginx](https://anaroq.com/tools/reverse-proxy-generator)
- [ ] **HTTP redirects to HTTPS**
- Check: A request over plain HTTP gets a permanent redirect (301 or 308) to the HTTPS address.
- Command: `curl -sI http://<domain>/ | head -n 3`
- Why: Without the redirect, logins and cookies can travel unencrypted from links and old bookmarks.
- On ANAROQ: [Reverse proxy config generator: Caddy, Traefik and nginx](https://anaroq.com/tools/reverse-proxy-generator)
## Secrets (0/3)
- [ ] **No secret is written in the Compose file**
- Check: Passwords, tokens and keys come from an `.env` file or Compose secrets, and the Compose file can be shared or committed as it is.
- Command: `grep -nEi '(password|secret|token|api_?key)[^:=]*[:=]' docker-compose.yml`
- Why: A Compose file gets pasted into issues, chats and repositories; a secret inside it leaks with it.
- On ANAROQ: [Docker Compose Analyzer](https://anaroq.com/tools/compose-analyzer), [Docker Compose Generator](https://anaroq.com/tools/compose-generator)
- [ ] **The .env file is readable by its owner only**
- Check: `.env` has mode `600`, belongs to the deploy user and is listed in `.gitignore`.
- Command: `stat -c '%a %U %n' .env; git check-ignore -v .env`
- Why: Every account on the server, and every backup of the directory, can otherwise read the secrets.
- On ANAROQ: [Docker Compose Generator](https://anaroq.com/tools/compose-generator)
- [ ] **No secret is baked into an image**
- Check: No `ARG` or `ENV` in the build history holds a secret; build-time secrets use a secret mount.
- Command: `docker history --no-trunc <image>`
- Why: Anyone who can pull the image can read its layers and its build history.
- On ANAROQ: [Dockerfile linter](https://anaroq.com/tools/dockerfile-linter)
## Resources and limits (0/4)
- [ ] **Memory and disk have headroom**
- Check: Under normal load there is free memory left and the data disk is well below full.
- Command: `free -h; df -h`
- Why: A full disk stops databases from writing; running out of memory makes the kernel kill a process, often the largest one.
- On ANAROQ: [Stack Sizer](https://anaroq.com/tools/stack-sizer)
- [ ] **Containers have memory limits**
- Check: Each container shows a limit lower than the server's memory, so one runaway service cannot take the others down.
- Command: `docker stats --no-stream --format '{{.Name}}: {{.MemUsage}}'`
- Why: Without a limit a leak grows until the kernel's OOM killer picks a victim, which may be your database.
- On ANAROQ: [Stack Sizer](https://anaroq.com/tools/stack-sizer), [What takes VRAM when you run an LLM](https://anaroq.com/learn/llm-vram)
- [ ] **Docker's disk use is known and cleaned**
- Check: Old images, stopped containers and build cache do not fill the disk; a cleanup runs on a schedule you chose.
- Command: `docker system df`
- Why: Every update leaves the previous image behind; on a small disk that adds up within months.
- [ ] **Swap is set on purpose**
- Check: Small servers have some swap as a buffer; you know its size and that it is not in constant use.
- Command: `cat /proc/swaps; vmstat 1 5`
- Why: A little swap absorbs short peaks; constant swapping means the server is too small for its load.
- On ANAROQ: [cloud-init Generator for Ubuntu 24.04](https://anaroq.com/tools/cloud-init-generator), [Stack Sizer](https://anaroq.com/tools/stack-sizer)
## Health checks and restart (0/4)
- [ ] **Every service has a restart policy**
- Check: Each long-running container uses `unless-stopped` or `always`, so it comes back after a crash or a reboot.
- Command: `docker inspect --format '{{.Name}}: {{.HostConfig.RestartPolicy.Name}}' $(docker ps -q)`
- Why: The default policy is `no`: after a reboot for a kernel update, the service stays down until someone notices.
- On ANAROQ: [Docker Compose Analyzer](https://anaroq.com/tools/compose-analyzer)
- [ ] **Services report their health**
- Check: Each service that others depend on has a health check and shows `(healthy)`; dependents wait for `service_healthy`.
- Command: `docker ps --format '{{.Names}}: {{.Status}}'`
- Why: A running process is not a working service. A health check tells Docker and you the difference.
- On ANAROQ: [Docker Compose Analyzer](https://anaroq.com/tools/compose-analyzer), [Container keeps restarting: find the cause from its exit code](https://anaroq.com/troubleshoot/container-restart-loop)
- [ ] **Nothing is restarting in a loop**
- Check: No container is in the restarting state, and the restart counts stay at zero after a day.
- Command: `docker ps -a --filter status=restarting; docker inspect --format '{{.Name}}: {{.RestartCount}}' $(docker ps -aq)`
- Why: A restart policy hides a crash: the service looks up most of the time while it fails over and over.
- On ANAROQ: [Container keeps restarting: find the cause from its exit code](https://anaroq.com/troubleshoot/container-restart-loop)
- [ ] **Docker starts at boot**
- Check: The Docker service is enabled, so the containers come back after a reboot.
- Command: `systemctl is-enabled docker`
- Why: Restart policies apply only once the Docker daemon itself is running.
- On ANAROQ: [cloud-init Generator for Ubuntu 24.04](https://anaroq.com/tools/cloud-init-generator)
## Logs and rotation (0/3)
- [ ] **Container logs rotate**
- Check: The logging driver is `local`, or `json-file` with `max-size` and `max-file`, set in `/etc/docker/daemon.json` or on each service.
- Command: `docker info --format '{{.LoggingDriver}}'; cat /etc/docker/daemon.json`
- Why: The default `json-file` driver never rotates: one noisy container can fill the disk.
- On ANAROQ: [Docker Compose Analyzer](https://anaroq.com/tools/compose-analyzer)
- [ ] **The system journal has a size limit**
- Check: The journal's disk use is known and capped (`SystemMaxUse` in `journald.conf`, or the default share of the disk).
- Command: `journalctl --disk-usage`
- Why: System logs grow with every SSH attempt and every service restart; a cap keeps them from crowding out your data.
- [ ] **You can read the logs of the last incident**
- Check: The logs reach back far enough to cover an incident you notice a day later, with timestamps.
- Command: `docker compose logs --timestamps --since 24h --tail 20`
- Why: Rotation that keeps too little erases the cause before anyone looks.
- On ANAROQ: [Container keeps restarting: find the cause from its exit code](https://anaroq.com/troubleshoot/container-restart-loop)
## Monitoring and alerts (0/3)
- [ ] **Something outside the server checks it is up**
- Check: An uptime check from another machine or service calls each public address every few minutes.
- Command: `curl -fsS -o /dev/null -w '%{http_code} %{time_total}s\n' https://<domain>/`
- Why: A monitor on the same server goes down with it, and cannot tell you that it did.
- On ANAROQ: [Uptime Kuma](https://anaroq.com/self-host/uptime-kuma), [WordPress with uptime monitoring: WordPress, MariaDB and Uptime Kuma](https://anaroq.com/self-host/stacks/wordpress-with-uptime-monitoring)
- [ ] **Disk, memory and load raise alerts**
- Check: Metrics are collected, and a threshold on disk space, memory and load sends an alert before the server runs out.
- Command: `uptime; df -h /`
- Why: A disk that fills up over weeks gives plenty of warning, but only to someone who is watching.
- On ANAROQ: [Prometheus](https://anaroq.com/self-host/prometheus), [Grafana](https://anaroq.com/self-host/grafana)
- [ ] **Alerts reach a person who acts on them**
- Check: A test alert was sent and arrived on a channel someone reads: email, chat or phone.
- Command: `systemctl list-units --type=service --state=running --no-pager | grep -Ei 'alert|monitor|exporter'`
- Why: An alert nobody receives is the same as no alert.
- On ANAROQ: [Uptime Kuma](https://anaroq.com/self-host/uptime-kuma)
## Upgrade and rollback plan (0/3)
- [ ] **Images are pinned to a version**
- Check: No service runs `latest` or an untagged image; each one names a version, or a digest.
- Command: `docker ps --format '{{.Names}}: {{.Image}}'`
- Why: With `latest`, a restart can pull a new major version you never chose, and there is no known version to return to.
- On ANAROQ: [Docker Compose Analyzer](https://anaroq.com/tools/compose-analyzer), [Release Tracker](https://anaroq.com/self-host/releases)
- [ ] **You read the release notes before upgrading**
- Check: You know the running version of each app and what changed up to the next one, especially database migrations and breaking changes.
- Command: `docker image inspect --format '{{index .RepoTags 0}} {{index .RepoDigests 0}}' <image>`
- Why: Many apps migrate their database on the first start of a new version; some migrations cannot be undone.
- On ANAROQ: [Release Tracker](https://anaroq.com/self-host/releases)
- [ ] **There is a written way back**
- Check: Before each upgrade: a fresh backup, the previous version written down, and the steps to return to it, including restoring the data.
- Command: `docker image ls <image>`
- Why: After a migration, going back to the old image alone often fails; the way back is the old image with the backup taken before the upgrade.
- On ANAROQ: [Release Tracker](https://anaroq.com/self-host/releases)
## Sources
- [Docker Docs: Back up, restore, or migrate data volumes](https://docs.docker.com/engine/storage/volumes/#back-up-restore-or-migrate-data-volumes)
- [PostgreSQL Documentation: Backup and Restore](https://www.postgresql.org/docs/current/backup.html)
- [Ubuntu Server documentation: Automatic updates](https://documentation.ubuntu.com/server/how-to/software/automatic-updates/)
- [Debian Wiki: UnattendedUpgrades](https://wiki.debian.org/UnattendedUpgrades)
- [Docker Docs: Packet filtering and firewalls](https://docs.docker.com/engine/network/packet-filtering-firewalls/)
- [Ubuntu manpage: ufw(8)](https://manpages.ubuntu.com/manpages/noble/man8/ufw.8.html)
- [OpenSSH manual: sshd_config(5)](https://man.openbsd.org/sshd_config)
- [Let's Encrypt: Challenge Types](https://letsencrypt.org/docs/challenge-types/)
- [Caddy documentation: Automatic HTTPS](https://caddyserver.com/docs/automatic-https)
- [MDN: Strict-Transport-Security](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security)
- [Docker Docs: Secrets in Compose](https://docs.docker.com/compose/how-tos/use-secrets/)
- [Docker Docs: Build secrets](https://docs.docker.com/build/building/secrets/)
- [Docker Docs: Resource constraints](https://docs.docker.com/engine/containers/resource_constraints/)
- [Compose Specification: deploy resources](https://docs.docker.com/reference/compose-file/deploy/#resources)
- [Docker Docs: docker system df](https://docs.docker.com/reference/cli/docker/system/df/)
- [Docker Docs: Start containers automatically](https://docs.docker.com/engine/containers/start-containers-automatically/)
- [Compose file reference: healthcheck](https://docs.docker.com/reference/compose-file/services/#healthcheck)
- [Dockerfile reference: HEALTHCHECK](https://docs.docker.com/reference/dockerfile/#healthcheck)
- [Docker Docs: JSON File logging driver](https://docs.docker.com/engine/logging/drivers/json-file/)
- [Docker Docs: Configure logging drivers](https://docs.docker.com/engine/logging/configure/)
- [systemd: journald.conf](https://www.freedesktop.org/software/systemd/man/latest/journald.conf.html)
- [Prometheus: Overview](https://prometheus.io/docs/introduction/overview/)
- [Prometheus: Alerting overview](https://prometheus.io/docs/alerting/latest/overview/)
- [Uptime Kuma](https://github.com/louislam/uptime-kuma)
- [Docker Docs: Pull an image by digest](https://docs.docker.com/reference/cli/docker/image/pull/#pull-an-image-by-digest-immutable-identifier)
- [Docker Docs: docker compose pull](https://docs.docker.com/reference/cli/docker/compose/pull/)
Hand off to an agent
A Markdown task for a coding agent: the files, the findings, the limits and fixed guardrails. Values that look like secrets are masked. It is built on this page and sent nowhere.
What it does
A list of checks for one server that runs self-hosted apps with Docker, grouped by topic. You run the commands yourself, on your server, and mark what holds. The page never connects to your server.
10 categories, 33 items. Items without a tool on this site explain how to check them without a link; when a tool is published, the item links to it.
How it works
- Open a shell on the server and go through the categories in order.
- Run each item's command. It only reads: it changes nothing on the server. Replace
<domain>,<image>and<backup-dir>first. - Mark an item once what it describes holds on your server. The marks go into the link after
#. - Copy the share link to continue later or hand the review to a colleague, download the checklist as Markdown, or hand the open items to a coding agent.
Limits
- It checks nothing by itself: the marks are what you confirmed, not a scan of the server.
- It covers one Linux server running Docker; clusters, managed platforms and Windows servers need more.
- It is not a security audit or a compliance framework; passing it does not make a server secure.
- Commands assume Debian or Ubuntu with systemd, ufw and Docker Compose v2; adapt them on other systems.
Methodology: how the numbers are worked out
Sources
- Docker Docs: Back up, restore, or migrate data volumes
- PostgreSQL Documentation: Backup and Restore
- Ubuntu Server documentation: Automatic updates
- Debian Wiki: UnattendedUpgrades
- Docker Docs: Packet filtering and firewalls
- Ubuntu manpage: ufw(8)
- OpenSSH manual: sshd_config(5)
- Let's Encrypt: Challenge Types
- Caddy documentation: Automatic HTTPS
- MDN: Strict-Transport-Security
- Docker Docs: Secrets in Compose
- Docker Docs: Build secrets
- Docker Docs: Resource constraints
- Compose Specification: deploy resources
- Docker Docs: docker system df
- Docker Docs: Start containers automatically
- Compose file reference: healthcheck
- Dockerfile reference: HEALTHCHECK
- Docker Docs: JSON File logging driver
- Docker Docs: Configure logging drivers
- systemd: journald.conf
- Prometheus: Overview
- Prometheus: Alerting overview
- Uptime Kuma
- Docker Docs: Pull an image by digest
- Docker Docs: docker compose pull
Related
Next step
A free public tool by ANAROQ, a company building compute infrastructure. What ANAROQ is building