Building R Docker Images in Azure Container Registry
I have recently written a few posts about Docker images for R.
I maintain four recurring images – r-deps
, r-minimal
, r-base
and r-test
– that depend on each other in that order.
But how do we build and share these images?
We have the images for all the versions of R we use and have used.
A new version of R is typically released twice a year, so I have updated them manually so far.
But now I prioritized looking into a smarter way of doing this.
I have previously referred to this post about tagging practices – it is really worth a read.
I follow the advice here and have two tags for each image:
A “moving” tag which is just the version of R (e.g. 3.5.1
) and a unique tag that also includes a “unique” part.
The unique tag ensures that we can always backtrace if necessary.
Builds in a registry
The container registry has a number of features for building images “in” the registry, that is, transfering a Dockerfile to a service that builds the image and puts it in the registry.
I am going to use a feature that is currently in preview, so don’t rely on everything here being accurate too far out in the future.
Getting ready
Check the official docs for how to create an Azure Container Registry.
Sign into Azure CLI
Set the subscription to that of the container registry.
az account set --subscription "<subscription>"
Log in to the container registry, using <acr name>
of <acr name>.azurecr.io
az acr login --name "<acr name>"
Repository
I keep the Dockerfiles in a repository in Azure DevOps with the following files
r-dockerfiles
├── r-base
│ └── Dockerfile
├── r-deps
│ └── Dockerfile
├── r-minimal
│ └── Dockerfile
├── r-test
│ ├── Dockerfile
│ └── run_tests.R
├── task.yaml
└── values.yaml
In the values.yaml
we have a number of build variables, including the version of R needed in the tag:
ubuntu: 18.04
rversion: 3.5.0
mrandate: 2018-07-02
As I wrote about in a previous post I hardcode the package repository to match the version of R.
When a new version of R is released the rversion
and mrandate
variables must be updated to match each other.
In order to use the values in a build we have to create a build task using a YAML file.
If the task is created as in the official tutorials with a specific Dockerfile the values.yaml
is ignored.
This is what task.yaml
does here.
It is quite a mouthful, but I will go through the parts.
version: 1.0-preview-1
steps:
- id: r-deps-build
build: --build-arg UBUNTU_VERSION={{.Values.ubuntu}} -t {{.Run.Registry}}/r-deps:{{.Values.rversion}}-{{.Run.Date}} -t {{.Run.Registry}}/r-deps:{{.Values.rversion}} -f r-deps/Dockerfile .
when: ["-"]
- id: r-deps-push
push:
- {{.Run.Registry}}/r-deps:{{.Values.rversion}}-{{.Run.Date}}
- {{.Run.Registry}}/r-deps:{{.Values.rversion}}
when: ["r-deps-build"]
- id: r-minimal-build
build: --build-arg REGISTRY={{.Run.Registry}} --build-arg R_VERSION={{.Values.rversion}} --build-arg MRANDATE={{.Values.mrandate}} -t {{.Run.Registry}}/r-minimal:{{.Values.rversion}}-{{.Run.Date}} -t {{.Run.Registry}}/r-minimal:{{.Values.rversion}} -f r-minimal/Dockerfile .
timeout: 1200
when: ["r-deps-build"]
- id: r-minimal-push
push:
- {{.Run.Registry}}/r-minimal:{{.Values.rversion}}-{{.Run.Date}}
- {{.Run.Registry}}/r-minimal:{{.Values.rversion}}
when: ["r-minimal-build"]
- id: r-base-build
build: --build-arg REGISTRY={{.Run.Registry}} --build-arg R_VERSION={{.Values.rversion}} -t {{.Run.Registry}}/r-base:{{.Values.rversion}}-{{.Run.Date}} -t {{.Run.Registry}}/r-base:{{.Values.rversion}} -f r-base/Dockerfile .
when: ["r-minimal-build"]
- id: r-base-push
push:
- {{.Run.Registry}}/r-base:{{.Values.rversion}}-{{.Run.Date}}
- {{.Run.Registry}}/r-base:{{.Values.rversion}}
when: ["r-base-build"]
- id: r-test-build
build: --build-arg REGISTRY={{.Run.Registry}} --build-arg R_VERSION={{.Values.rversion}} -t {{.Run.Registry}}/r-test:{{.Values.rversion}}-{{.Run.Date}} -t {{.Run.Registry}}/r-test:{{.Values.rversion}} -f r-test/Dockerfile .
timeout: 1200
when: ["r-base-build"]
- id: r-test-push
push:
- {{.Run.Registry}}/r-test:{{.Values.rversion}}-{{.Run.Date}}
- {{.Run.Registry}}/r-test:{{.Values.rversion}}
when: ["r-test-build"]
There is a build
step and a push
step for each of the four images.
This is necessary as each step can only do one thing.
In the build step we specify the build arguments and the tags using the registry we are going to push to ({{.Run.Registry}}
), the version of R ({{.Values.rversion}}
) and other variables.
The unique tag is the version of R and the date/time when the build was started.
What makes this multi-step task a winner for me is that we can specify the dependencies between the steps with when
:
r-deps-build
has no dependencies and starts when the task is triggered.
r-deps-push
and r-minimal-build
has to wait for r-deps-build
to finish.
r-minimal-push
and r-base-build
has to wait for r-minimal-build
to finish.
- And so on…
The final trick is that we set a timeout
for the two time consuming steps r-minimal-build
and r-test-build
to be 20 minutes instead of accepting the default of 10 minutes.
Create a build task
With the container registry created and the repository in DevOps a build task can be created.
To enable the container registry to accees the repository we need a personal access token.
Click the “Clone” link in the top right corner and then “Generate Git credentials”.
Armed with a <token>
the task is created with this command:
az acr task create \
--registry "<acr name>" \
--name "r-builds" \
--context "<Azure DevOps URL>" \
--file "task.yaml" \
--values "values.yaml" \
--branch "master" \
--git-access-token "<token>"
Now every push to the master
branch will trigger the task.
It can also be triggered manually with
az acr task run --registry "<acr name>" --name "r-builds"
Inspection
The Azure CLI has tools to inspect the tasks and images.
Some of them trimmed a bit to better fit the page.
The tasks on the registry.
$ az acr task list --registry "<acr name>" --output table
NAME STATUS SOURCE REPOSITORY
-------- -------- -------------------
r-builds Enabled "<Azure DevOps URL>
The runs related to the registry.
$ az acr task list-runs --registry "<acr name>" -o table
TASK STATUS TRIGGER STARTED DURATION
--------- --------- ------------ -------------------- ----------
r-builds Succeeded Commit 2019-03-27T12:21:30Z 00:20:51
r-builds Succeeded Manual 2019-03-27T10:22:46Z 00:20:38
See all images in registry.
$ az acr repository list --name "<acr name>" --output table
Result
---------
r-base
r-deps
r-minimal
r-test
See all tags for a particular image.
Here we also see the multiple tags.
$ az acr repository show-tags --name "<acr name>" --repository r-test --output table
Result
----------------------
3.5.0
3.5.0-20190327-102250z
3.5.1
3.5.1-20190322-130220z
3.5.2
3.5.2-20190327-122135z
Finally, in Azure Portal we can see this overview if the resource group with the container registry is on the dashboard.
Clicking on r-builds
will allow inspection of the build task in JSON format.