Self-hosting gives you control, and makes you responsible for updates. Outdated apps are the most common way self-hosted servers get compromised, but blindly pulling latest can also break things, especially for apps with database migrations. A simple, repeatable routine keeps you secure without surprises.
1. Know what you run
Keep a short inventory per server: app, how it’s installed (Docker Compose, a package, a manual binary), version, URL, data location and backup method.
docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'
2. Follow releases
- GitHub “Watch → Custom → Releases” for each project, or its release RSS feed (
https://github.com/OWNER/REPO/releases.atom) in a feed reader. - Security mailing lists and announcement channels for critical apps (Nextcloud, Gitea, Home Assistant).
- Diun (Docker Image Update Notifier) or What’s Up Docker notify you when new image tags appear, without updating automatically.
Read release notes before updating, especially for major versions. Look for “breaking changes”, required migrations and minimum versions (e.g. a database upgrade first).
3. Pin versions in Docker Compose
services:
vaultwarden:
image: vaultwarden/server:1.32.5 # not :latest
Pinned tags make updates deliberate and reversible. You change the tag, pull and restart, and can go back if needed. latest hides what changed, and makes rollbacks guesswork. A middle ground is pinning the major version (postgres:16, nextcloud:30) and letting patch releases flow.
4. Back up before every update
Especially before major versions, and before apps that migrate their database on start:
docker compose stop app
docker compose exec -T db pg_dump -U app app > /backups/app-$(date +%F).sql # or mysqldump
tar -czf /backups/app-data-$(date +%F).tar.gz ./data
Then update. If an update corrupts or migrates data in an unwanted way, you need the pre-update copy. See Docker volume backups and database backup automation.
5. Update a Docker Compose app
cd /srv/app
# edit the image tag in compose.yaml, then:
docker compose pull
docker compose up -d
docker compose logs -f --tail=100
Check the app works, and watch the logs for migration errors. Clean up old images when everything is fine:
docker image prune
See Docker Compose and docker prune.
Rollback
Change the tag back, docker compose up -d, and restore the database backup if the new version migrated the schema (older versions usually can’t read migrated data).
6. Native installs and packages
- Apps from the distribution or vendor repositories update with
apt/dnf, which is covered by your normal patching. See patch management. - Manually installed apps (tarballs, PHP apps like Nextcloud with an internal updater): follow the app’s documented update procedure, keep the previous version’s directory for rollback, and back up first.
7. Order matters for stacks
Update dependencies within their supported ranges:
- Databases: major upgrades (PostgreSQL 15 → 16, MariaDB 10.x → 11.x) need dump and restore, or
pg_upgrade. Never just change the image tag of a database container across major versions. See upgrade PostgreSQL. - Apps sometimes require minimum database or Redis versions. Read the release notes.
- Reverse proxies (Nginx Proxy Manager, Traefik, Caddy) are internet-facing, so keep them current.
Automatic updates: when and how
| Approach | Risk | Good for |
|---|---|---|
| Watchtower auto-update everything | Higher. Breaking changes land unattended | Low-stakes, stateless containers |
| Watchtower with labels (opt-in per container) or notify-only | Medium/low | Mixed setups |
| Renovate or Dependabot PRs for compose files in Git | Low. You review and merge | GitOps setups |
| Manual monthly routine + notifications | Low | Stateful, critical apps |
Automate security patch releases for exposed services, and handle major versions manually. See Watchtower.
8. A monthly routine
- Check notifications and release notes.
- Back up (or confirm last night’s backups succeeded).
- Update the host OS (
apt upgrade), and reboot if a kernel update is pending. - Update apps one at a time, checking each.
- Prune old images, and check disk space.
- Note versions in your inventory.
Quick reference
| Task | Command |
|---|---|
| List containers and images | docker ps --format '{{.Names}} {{.Image}}' |
| Update a compose app | docker compose pull && docker compose up -d |
| Watch logs | docker compose logs -f --tail=100 |
| Clean old images | docker image prune |
| Release feed | github.com/OWNER/REPO/releases.atom |
Related: Docker security and 3-2-1 backups.