Automatically updating containers with Docker

Running something in a container using Docker or Podman is cool, but maybe you want an automated way to always run the latest container? Using the :latest tag alone does not to this, that just pulls the latest container at the time. You could have a cronjob that just always pulls the latest containers and restarts the container but then if there’s no update you have an outage for no reason.

It’s not too hard to write a script to pull the latest container and restart the service only if required, then tie that together with a systemd timer.

To restart a container you need to know how it was started. If you have only one container then you could just hard-code it, however it gets more tricky to manage if you have a number of containers. This is where something like runlike can help!

First, start up your container however you need to (OwnTracks recorder, for example).

Next, let’s install runlike with pip.

sudo pip install runlike

Now, let’s create a simple script that takes one optional argument, the name of a running container. If the argument is omitted, it will default to all containers. The script will check if the latest image is different to the running image, and if so, restart the container using the new image with the same arguments as before (determined by runlike). If there is no newer image, then it will just leave the running container alone.

Create the script.

cat << \EOF | sudo tee /usr/local/bin/update-containers.sh
#!/bin/bash

# Abort on all errors, set -x
set -o errexit

# Get the containers from first argument, else get all containers
CONTAINER_LIST="${1:-$(docker ps -q)}"

for container in ${CONTAINER_LIST}; do
  # Get the image and hash of the running container
  CONTAINER_IMAGE="$(docker inspect --format "{{.Config.Image}}" --type container ${container})"
  RUNNING_IMAGE="$(docker inspect --format "{{.Image}}" --type container "${container}")"

  # Pull in latest version of the container and get the hash
  docker pull "${CONTAINER_IMAGE}"
  LATEST_IMAGE="$(docker inspect --format "{{.Id}}" --type image "${CONTAINER_IMAGE}")"

  # Restart the container if the image is different
  if [[ "${RUNNING_IMAGE}" != "${LATEST_IMAGE}" ]]; then
    echo "Updating ${container} image ${CONTAINER_IMAGE}"
    DOCKER_COMMAND="$(runlike "${container}")"
    docker rm --force "${container}"
    eval ${DOCKER_COMMAND}
  fi
done
EOF

Make the script executable.

sudo chmod a+x /usr/local/bin/update-containers.sh

You can test the script by just running it.

/usr/local/bin/update-containers.sh

Now that you have a script which will check for a new images and update containers, let’s make a systemd service and timer for it. This way you can schedule regular update checks whenever you like. If you want a script for a specific container, just add the container names as arguments to the script.

First, create the service

cat << EOF | sudo tee /etc/systemd/system/update-containers.service 
[Unit]
Description=Update containers
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/update-containers.sh
EOF

Next, create the matching timer service (note that the service and timer names need to match).

cat << EOF | sudo tee /etc/systemd/system/update-containers.timer 
[Unit]
Description=Timer for updating containers
Wants=network-online.target

[Timer]
OnActiveSec=24h
OnUnitActiveSec=24h

[Install]
WantedBy=timer.target
EOF

Reload systemd to pick up the new service and enable the timer.

sudo systemctl daemon-reload
sudo systemctl start update-containers.timer
sudo systemctl enable update-containers.timer

You can check the status of the timer and the service using standard systemd tools.

sudo systemctl status update-containers.timer
sudo systemctl status update-containers.service
sudo journalctl -u update-containers.service

That’s it! Sit back and let your containers be automatically updated for you. If you want to manually update a container, you could just use version tags and manage them separately.


Leave a Reply

Your email address will not be published. Required fields are marked *