Update your Docker containers safely
Safe Harbor
Docker containers are a great way to produce lean and portable services, not only for the enterprise, but also at home. Docker takes the complexity out of setting up software services that are inherently complicated, like an email or a Nextcloud server. However, it's difficult to know when and how to update the software inside the Docker container to keep it as secure as possible.
In this article, I present a scripted approach to alerting you about when to update Docker containers. The first script checks Docker Hub for image tags or versions available. If you want to update, you need to know your software version and adjust the image tag accordingly, so updates stay within a safe range of versions. The second set of scripts checks your local version against possible updates on Docker Hub. One mini-script produces an ID file containing the two Docker image IDs and another checks for updates available on the Hub.
With these scripts, you can automate your checks for available updates. The updating process itself is left in your hands.
Updating a Docker Container
Many official software projects like Nextcloud, MariaDB, Nginx, and so on maintain official Docker images. Often, though, their prescription for Docker is a Docker Compose file in which you install some latest version of the image; however, it is not advisable to keep blindly updating to a latest version. For instance, the Nginx container defined in the Docker Compose file shown in Listing 1, when run, pulls an nginx:alpine
image from Docker Hub, where nginx
is the image and alpine
is the tag (referred to hereafter as <image><tag>
).
Listing 1
docker-compose.yml
version: '2' services: web: image: nginx:alpine restart: always container_name: nginx ports: - 8080:80 environment: - NGINX_HOST=www.somewebsite.com - NGINX_PORT=80 command: "nginx -g 'daemon off;'"
If you pull nginx:alpine
, you pull a so-called "latest" version. Several commonly used tags also pull a latest version, such as <image>:latest
, <image>:stable
, nginx:alpine
, nextcloud:fpm-alpine
, and so on. By "latest," I mean the tag has no version (e.g., alpine-10.4
).
If you leave the latest
tag in your configuration files while updating the container, you are in for trouble: The software version might go from nginx-8.3
to nginx-11.2
, which, bottom line, could break things. On the other hand, if you do not update at all, the software after some time will contain known vulnerabilities (which is why you update in the first place, of course).
Depending on the software in the container, you should decide what version changes are probably safe or, in other words, the version range in which you want to update. By version range, I mean it's probably fine to upgrade nextcloud-10.4.1
to nextcloud-10.4.8
, but it's not advisable to update blindly from version 10.4 to version 12.3. A safe version range, then, would be to stay within version 10.4. Of course, it's entirely up to you what risks to take and what you consider a safe version range.
To update a Docker container within a safe version range, you need two pieces of information: the image version you are using right now and what <image><tag>
you will use when updating, so you don't run into trouble.
Image Version
To know whether your Docker container needs an update within a safe version range, you need to know what version you are running. The answer lies in a set of identifiers and tags to be found in the manifest that is also downloaded when you pull an image. You can inspect the manifest in two steps: Find the image ID on your local machine, and inspect its manifest for a version tag.
You can inspect container or image manifests on a Docker host (a host running Docker containers) as shown in Listing 2. The docker image
command produces the image ID of running containers. In this case, my running container uses the nginx
image with the id bf85f2b6bf52
.
Listing 2
Inspecting Image Manifests
docker images REPOSITORY TAG IMAGE ID CREATED SIZE nginx_web latest c100b674c0b5 13 months ago 19MB nginx alpine bf85f2b6bf52 13 months ago 15.5MB
With the image ID in hand, you can inspect the image manifest:
docker inspect bf85f2b6bf52 | egrep -i version
In this case, it yields: NGINX_VERSION=1.13.7 .
An easy way to get the versions of all running Docker containers is shown in Listing 3. When I run the script on my Nextcloud machine, it yields (together with some lines of no use):
"NEXTCLOUD_VERSION=12.0.4" MARIADB_MAJOR=10.2 MARIADB_VERSION=10.2.12+maria~jessie
What Tag?
Listing 3
Docker Container Versions
## get versions for running containers for i in `docker ps | egrep -v NAMES | awk '{print $1}'`; do echo " image-ID is now $i" docker inspect --format='{{.Config.Env}}' $i docker inspect $i | egrep VERSION done
Now that you know your software versions, the next question to ask is what tag to use in docker-compose.yml
. You can't use nginx:alpine:1.13
, so what tag can you use?
The answer depends, of course, on what tags a software project has issued for its images on Docker Hub. I like to use the script in Listing 4, which asks the Docker Hub Registry API to list all available tags. To download the script, make it executable, and run it, enter:
curl https://raw.githubusercontent.com/ hanscees/dockerscripts/master/scripts/get-docker-hub--image-tags.sh > get-public-image-tags.sh chmod +x get-public-image-tags.sh ./get-public-image-tags.sh library/nginx
Listing 4
get-public-image-tags.sh
01 #!/bin/bash 02 03 # Retrieves image tags from public 04 # images in DockerHub 05 # if someone improves this script, please let me know, preferably in Python 06 # hanscees@AT@hanscees.com 07 # modified from https://gist.github.com/cirocosta/17ea17be7ac11594cb0f290b0a3ac0d1x 08 09 set -o errexit 10 11 main() { 12 check_args "$@" 13 14 local image=$1 15 local token=$(get_token $image) 16 local tags=$(get_tags $image $token) 17 echo "tags reported are:" 18 echo $tags 19 } 20 21 get_token() { 22 local image=$1 23 24 echo "Retrieving Docker Hub token. 25 IMAGE: $image 26 " >&2 27 28 curl --silent "https://auth.docker.io/token?scope=repository:$image:pull&service=registry.docker.io" | jq -r '.token' 29 } 30 31 # Retrieve the digest, now specifying in the header 32 # that we have a token (so we can ... 33 get_tags() { 34 local image=$1 35 local token=$2 36 37 echo "Retrieving image tags. 38 IMAGE: $image 39 " >&2 40 41 curl --silent --header "Accept: application/vnd.docker.distribution.manifest.v2+json" --header "Authorization: Bearer $token" "https://registry-1.docker.io/v2/$image/tags/list" | jq -r '.tags' 42 } 43 check_args() { 44 if (($# != 1)); then 45 echo "Error: 46 One argument must be provided - $# provided. 47 48 Usage: 49 ./get-docker-hub-image-tags.sh <image> 50 for instance ./get-docker-hub-image-tags.sh library/mariadb 51 Aborting." 52 exit 1 53 fi 54 } 55 56 main "$@"
In the last line, I prepended library/
to nginx
: All official Docker Hub repositories require this prefix.
The output comprises many tags, including various versions of alpine
, perl
, and alpine-perl
, along with their mainline
and stable
versions (Table 1).
Table 1
get-public-image-tags.sh Output
Tags | ||
---|---|---|
1.13.3-alpine | 1.13.8-alpine | 1.8-alpine |
1.13.3-perl | 1.13.8-perl | 1.8.1-alpine |
1.13.3 | 1.13.8 | 1.8.1 |
1.13.5-alpine-perl | 1.13.9-alpine-perl | 1.8 |
1.13.5-alpine | 1.13.9-alpine | alpine |
1.13.5-perl | 1.13.9-perl | latest |
1.13.5 | 1.13.9 | mainline-alpine-perl |
1.13.6-alpine-perl | 1.13 | mainline-alpine |
1.13.6-alpine | 1.14-alpine-perl | mainline-perl |
1.13.6-perl | 1.14-alpine | mainline |
1.13.6 | 1.14-perl | perl |
1.13.7-alpine-perl | 1.15-alpine | stable-alpine-perl |
1.13.7-alpine | 1.15.3-alpine | stable-alpine |
1.13.7-perl | 1.15.3-perl | stable-perl |
1.13.7 | 1.15.3 | stable |
1.13.8-alpine-perl | 1.15.4-alpine-perl |
The get-public-image-tags.sh
script uses jq
(a command-line JSON processor) in lines 28 and 41 to parse the JSON. On Ubuntu, the command to install the utility is:
apt-get install jq
As you can see, Nginx has quite a few tags. It also becomes clear how to reference a version of nginx:alpine
: Simply use nginx:<version>-alpine
. Because Nginx seems unlikely to change in any important way, I would suggest considering the nginx:1.15-alpine
tag in the YAML file.
Buy this article as PDF
(incl. VAT)