Server administrationWritten by GLWS14 min readUbuntu 24.04 LTS / AlmaLinux 9
Commands run on your server unless a section says otherwise. Replace example usernames, addresses and paths with your own values. Keep a working session open when changing remote access.

Why updates need a plan

Updating a container is easy: pull a new image and restart. The risks are an update that changes behaviour, a database that migrates its data so that you cannot go back, and data you did not know was stored inside the container. A short routine of pin, back up, update, check and roll back turns most failures into a minor delay.

This guide assumes a Compose project in a directory such as ~/myapp, as in the Docker and Open WebUI guides. Replace the names with yours.

Pin versions instead of using latest

The latest tag changes whenever the project publishes a release, so the same file can give you a different application tomorrow. Pin an exact version or a major-version tag in compose.yaml and update it on purpose. Record what is running now, including the image digest, which identifies the exact build and lets you return to it.

Both systems: what is running now
cd ~/myapp
sudo docker compose images
sudo docker compose config --images
Record each image digest in a notes file
sudo docker compose images -q | xargs -I{} sudo docker image inspect {} --format '{{index .RepoTags 0}} {{index .RepoDigests 0}}' | tee ~/myapp/versions-$(date +%F).txt

Know where your data lives

Everything that must survive an update has to be in a volume or a bind mount, not inside the container. List the mounts, and confirm that each important directory appears. Data inside the container disappears when it is recreated.

Both systems
sudo docker compose config | grep -A3 -E "volumes:"
sudo docker volume ls
sudo docker compose ps --format "table {{.Name}}\t{{.Status}}"
Recreate the application container during an update. Named volumes hold persistent files and database data. A volume is storage, not a backup; preserve a separate recoverable copy.
Recreate the application container during an update. Named volumes hold persistent files and database data. A volume is storage, not a backup; preserve a separate recoverable copy.

Back up before you touch anything

Take a backup of the volumes and, for databases, a proper dump taken with the database tool, because copying live database files can give an unusable copy. Check that the files exist and have a sensible size. Stopping the stack briefly gives the most consistent copy for small applications.

Both systems: stop, archive a volume, start (replace the volume name)
cd ~/myapp
sudo docker compose stop
sudo docker run --rm -v myapp_data:/data -v "$PWD":/backup alpine tar czf /backup/data-$(date +%F).tar.gz -C /data .
sudo docker compose start
ls -lh data-*.tar.gz
A database dump instead, while it runs (PostgreSQL example)
sudo docker compose exec -T db pg_dump -U appuser appdb | gzip > appdb-$(date +%F).sql.gz

Read the release notes

Before updating, read the project's release notes for breaking changes, required configuration changes and migration steps, especially for major versions. Some applications require updating through each major version in turn, and skipping one can corrupt data. If the notes warn about a migration that cannot be undone, the backup is your only way back.

Pull, update and check

Change the version in compose.yaml, pull the new images and recreate only the containers that changed. Watch the logs for migrations and errors, check that the application answers and test the main function by hand. Do this when few people are using it.

Both systems
cd ~/myapp
sudo nano compose.yaml
sudo docker compose pull
sudo docker compose up -d
sudo docker compose ps
sudo docker compose logs --tail 50 --follow

Roll back

If the new version fails, set the previous tag or digest in compose.yaml and start it again. If the application changed its data format during the update, the older version may refuse to start or misbehave with the migrated data. In that case restore your backup instead. Stop the stack, restore the volume or dump, then start the old version.

The restore clears every entry in the selected volume, including dotfiles. A /data/* wildcard misses hidden files and can leave incompatible state behind. Confirm the volume name and archive before deleting its contents.

Both systems: previous version, same data still compatible
cd ~/myapp
sudo nano compose.yaml
sudo docker compose up -d
Both systems: restore a volume from the archive (stack stopped)
sudo docker compose down
sudo docker run --rm -v myapp_data:/data -v "$PWD":/backup alpine sh -c "find /data -mindepth 1 -maxdepth 1 -exec rm -rf -- {} + && tar xzf /backup/data-YYYY-MM-DD.tar.gz -C /data"
sudo docker compose up -d

Clean up and automate carefully

After a few days of stable running, remove old images to recover disk space. Automatic updaters that pull new images on a schedule save time but can update at a bad moment and cannot read release notes, so use them only for low-risk applications, and never for databases. Keep a short log of what you updated and when.

Both systems
sudo docker image prune
sudo docker system df

Official documentation

These upstream references provide more detail on the commands and workflows in this guide.