Lead Image © yelenayemchuk, 123RF.com

Lead Image © yelenayemchuk, 123RF.com

Update your Docker containers safely

Safe Harbor

Article from ADMIN 51/2019
By
A scripted approach lets you know when to update your Docker containers.

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

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy ADMIN Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

comments powered by Disqus