The Azure Devops team recently added a new feature that gives the ability to run a build definition inside of a container pull from the Docker Hub. That container is running directly on the build host, executing the steps of the definition.

As I’ve been battling recently to get the Uno Platform Wasm-AOT Linux builds to run in a consistent context, without having to much to maintain. Mono-wasm comes with an AOT toolchain that only works on Ubuntu 18.04, and Azure DevOps hosted agents only provide Ubuntu 16.04.

This container support feature comes in handy to build in the appropriate environment!

How to build inside a container

The whole feature holds into two pieces of the yaml file definition.

First, the resources that describes the container images that can be used in the build definition:

resources:
  containers:
  - container: nv-bionic-wasm
    image: nventive/wasm-build:1.0-bionic

The wasm-build image here is built on top of the .NET Core Bionic image found on the docker hub, and contains all the Mono tools and Emscripten.

Then, to build something using this image, the container reference is added in a job definition:

container: nv-bionic-wasm

And that’s it !

The rest of the build definition will run inside the container, such as bash scripts.

In the case of Uno Platform based apps, this means that it’s possible to build a complete Full-AOT or Mixed Interpreter/AOT Mno based app in complete isolation.

Overall Build Time

The only drawback of this approach I could find for now is the setup time in the context of Azure hosted agents. The image’s layers are not cached by the build host, and it takes about 3 minutes to initialize. Using a custom agent may be a faster to build using containers.

About the default user for build scripts

Another detail to keep in mind is that the container is being setup by the build agent in a way that the build definitions scripts are executed under a non-root user.

Here’s what the agent runs in the container right after starting it:

## Try create an user with UID '1001' inside the container.
/usr/bin/docker exec xxxx bash -c "grep 1001 /etc/passwd | cut -f1 -d:"
/usr/bin/docker exec xxxx useradd -m -u 1001 vsts_azpcontainer
## Grant user 'vsts_azpcontainer' SUDO privilege and allow it run any command without authentication.
/usr/bin/docker exec xxxx groupadd azure_pipelines_sudo
/usr/bin/docker exec xxxx usermod -a -G azure_pipelines_sudo vsts_azpcontainer
/usr/bin/docker exec xxxx su -c "echo '%azure_pipelines_sudo ALL=(ALL:ALL) NOPASSWD:ALL' >> /etc/sudoers"

This is certainly a best practice, but it prevents the use of the official distributions, where sudo is not installed.

I tried a bit to work around this, but in the end, the time needed to build the wasm-build image is about a hour on the docker hub. It makes sense to create an image that is properly configured to begin with.

A sample definition using containers

The Uno.Wasm.Bootstrapper project is using new feature in its build linux definition, which creates all the Full AOT and Mixed interpreter/AOT builds.