Dockerfile-lock

A system to re-use same images dependency like nodejs package/package-lock (see: https://docs.npmjs.com/files/package-lock.json).

If I build an image from Dockerfile or docker-compose.yml which target something like “nginx:latest” or “node:latest” for tests, I can’t rebuild the image and be sure it work again, because the nginx or node version can have changed.

However, this is what many developers do when they develop with a local build image, commit and push their change in git and the CI rebuilds the image to test it and send it into production (see: https://stackoverflow.com/questions/54190484/git-and-docker-team-workflow).

A “Dockerfile-lock” could simply remember the image version (or image-id) installed like :

#Dockerfile-lock.yml
images:
. node:
. . latest: “node:1.15.8”
. nginx:
. . latest: “nginx:1.15.8”
. . 1.14: “nginx:1.14.2”

And if I run the “docker build --from-lock .” command, it will rerun the Dockerfile (or docker-compose.yml) but don’t search images by current versions on the hub, just get hub image by lock version.
If any locked image was deleted or unreachable, the system can make a warning and ask for default image.

If I just run docker build, it will build normally the image and update the Dockerfile-lock.

I finally made my own lock system with a bash alias or alternatively one or two scripts :

bash alias (Add docker ci command and improve docker build) :

dockeralias() {
    args=$@
    args_cat=$1
    shift
    args_without_cat_files_and_final=""    

    dockerfile="Dockerfile"
    while test $# -gt 1; do
        case "$1" in
            -f|--file)
                shift
                dockerfile=$1
                shift
                ;;
            -f=*)
                dockerfile=${1#"-f="}
                shift
                ;;
            --file=*)
                dockerfile=${1#"--file="}
                shift
                ;;
            *)
                args_without_cat_files_and_final="$args_without_cat_files_and_final $1 "
                shift
                ;;
        esac
    done
    lockfile="$dockerfile-lock"

    args_final=$@

    if [ $args_cat == "ci" ]; then
        echo "Build from $lockfile"
        command docker build $args_without_cat_files_and_final --file $lockfile $args_final
        return
    fi

    if ! command docker $args; then
        return
    fi

    if [ $args_cat == "build" ]; then
        echo "Make $lockfile from $dockerfile"

    	cp $dockerfile $lockfile
	grep ^FROM $lockfile | while read -r line ; do

	    image=`echo $line | cut -d" " -f2`
	    digest=`command docker inspect --format='{{index .RepoDigests 0}}' $image`

	    echo "$image > $digest"

	    sed -i -e "s/$image/$digest/g" $lockfile
	done
    fi
}
alias docker=dockeralias

Alternatively docker-build.sh to replace docker build in dev

#!/bin/bash

docker build "$@"

dockerfile="Dockerfile"
while test $# -gt 0; do
    case "$1" in
        -f|--file)
            shift
            dockerfile=$1
            shift
            ;;
        -f=*)
            dockerfile=${1#"-f="}
            shift
            ;;
        --file=*)
            dockerfile=${1#"--file="}
            shift
            ;;
        *)
	    shift
            ;;
    esac
done
lockfile="$dockerfile-lock"

echo "Make $lockfile from $dockerfile"

cp $dockerfile $lockfile
grep ^FROM $lockfile | while read -r line ; do

    image=`echo $line | cut -d" " -f2`
    digest=`docker inspect --format='{{index .RepoDigests 0}}' $image`

    echo "$image > $digest"

    sed -i -e "s/$image/$digest/g" $lockfile
done

And docker-ci.sh to replace docker build in CI (preprod, prod…) or just use “docker build --file Dockerfile-lock .

#!/bin/bash
    
args=""
    
dockerfile="Dockerfile"
while test $# -gt 1; do
    case "$1" in
        -f|--file)
            shift
            dockerfile=$1
            shift
            ;;
        -f=*)
            dockerfile=${1#"-f="}
            shift
            ;;
        --file=*)
            dockerfile=${1#"--file="}
            shift
            ;;
        *)
	    args="$newargs $1 "
	    shift
            ;;
    esac
done
lockfile="$dockerfile-lock"

echo "Build from $lockfile"
docker build $args --file $lockfile "$@"

Here an example of what do the script :

From Dockerfile

FROM node:latest
EXPOSE 8080
WORKDIR /usr/src/app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
CMD npm start

Create Dockerfile-lock

FROM node@sha256:d2180576a96698b0c7f0b00474c48f67a494333d9ecb57c675700395aeeb2c35
EXPOSE 8080
WORKDIR /usr/src/app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
CMD npm start

Thank you for the code…It is really helpful.

@lortetio @gayle210

I have made cli-plugin, docker-lock, that manages image digests for docker and docker-compose in a separate lockfile.

Here is an excerpt from the README:

docker-lock is a cli-plugin that uses Lockfiles (think package-lock.json or Pipfile.lock) to manage image digests. With docker-lock, you can refer to images in Dockerfiles or docker-compose files by mutable tags (as in python:3.6) yet receive the same benefits as if you had specified immutable digests (as in python:3.6@sha256:25a189a536ae4d7c77dd5d0929da73057b85555d6b6f8a66bfbcc1a7a7de094b).

docker-lock ships with 3 commands that take you from development to production:

  • docker lock generate finds base images in your docker and docker-compose files and generates a lockfile containing digests that correspond to their tags.
  • docker lock verify lets you know if there are more recent digests than those last recorded in the lockfile.
  • docker lock rewrite rewrites Dockerfiles and docker-compose files to include digests.

I hope this is helpful :slight_smile: