We want our test and production stacks to be automatically updated every time something new is pushed to the test or release branch. CI builds the docker image on successful test runs, then stores it in our private registry. But how do you automatically pull and deploy those updated images?

I looked into the Watchtower project, which is interesting. You add Watchtower to the stack, and it will diligently check for new versions of the images used by the containers in the stack, pulling, building and deploying as needed while the stack is up and running. In my experiments, however, I had little luck in making it talk with our private registry. Also, I’m not too fond of polluting my stack with foreign containers. I want my docker stack to be simple, tidy, clean, and single-tasked.

I ended up doing something super simple. A cronjob routinely invokes a script that pulls relevant images from our registry. If updated images are downloaded, then the docker stack up command is issued. Finally, a docker image prune -af ensures obsolete images are deleted. For the simplest scenario, where we only need to take care of one image, the script looks like this:

#!/bin/bash
set -e

readonly IMAGE=[image]
readonly TAG=[tag]

out=$(docker pull $IMAGE:$TAG)

if [[ $out != *"up to date"* ]]; then
    echo "an updated image has been donwloaded for '$IMAGE:$TAG'"
    # we actually launch a script here:
    docker stack up -c stack.yml mystack --with-registry-auth
    docker image prune -af
else
    echo "no updates available for '$IMAGE:$TAG'"
fi

I expected docker pull to return 1 on successful pulls; it turns out it always returns 0, so I’m checking its output for confirmation (I got the hint here).

You might be wondering why we don’t directly execute docker stack up in our cronjob. It updates the stack resolving new images by default. The problem is that, in our experience, it also briefly stops the services. Not an issue if you run this command sporadically. We want our stacks refreshed minutes after the initial developer push, though, so the cronjob runs frequently. With our pre-fetch approach, actual deployment only happens when an updated image has been found and downloaded.

Now, when I push to, say, test branch, I have the updated services up and running a minute later, without me doing anything on the docker or server-side of things. Mission accomplished, I guess, but I am sure there are other, better ways around this problem. If you happen to know one, please let me know about it (keep in mind, we don’t use alternative orchestrators, just the built-in ‘swarm’ thing.)