Skip to content

How to build Docker Containers with Cake

Many frequent readers of my blog will know that I am a fan of Cake –  a free and open source cross-platform build automation system  and as such it is my defacto approach to creating my CI/CD scripts and I often make use of Cake with Github Actions.

In my API Template Pack project, Cake is the default Build script and there are both Github action and BitBucket pipeline configurations provided. In this post, I’ll discuss how we are expanding the current implementation of the API Template Pack, to build our docker containers and then also to publish them to the Github Container Registry.

What is a Container

In simple terms a Container is the bundling of an application and all its dependencies as a package, that enables it to be deployed easily and consistently regardless of environments. The dependencies typically include binaries, libraries and all configuration required to run the application.

While Linux is the best Operating System for container technology, containers are often managed and deployed using Container Management systems like Kubernetes or Docker.

When developing Microservices applications are developed by building independent components that each execute a process as a service, these services communicate via well defined interfaces making use of lightweight API’s.

For developers Containers are essentially a mechanism for packaging software for development, because containers enable developers to write applications in the whatever language or framework of their choice and deploy it to whatever environment it is required in.

What is a Container Registry

A container registry is essentially a storage location to store our container images so that they can catalogued and versioned and enable developers to push and pull container images.

Each container image in Container Registry, will have Container Repository which will contain different versions of the image. The most popular Container Registries used are:

  • Docker Hub
  • Amazon Elastic Container Registry
  • Google Container Registry
  • Azure Container Registry
  • Github Packages

The are many more, and organisations may have a Self-Hosted Container Registry.

Containers & Microservices

Containers and microservices are currently the preferred approach for scaling and refactoring legacy applications to make them cloud native. This provides the agility and resiliency in the age of Web & Mobile applications.

Build docker container

The first step to building a Docker Container is to include a Dockerfile in your project. A Dockerfile is a simple text file that consists of instructions to build Docker images. The API Template Pack provides a simple dotnet based example of a Dockerfile, which will be fine for most applications. The actual Building of the application is handle by the accompanying Cake script, which will publish the artefacts of the build process to the .publish folder

### RUNTIME CONTAINER
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS runtime
WORKDIR /app
COPY .publish/ ./

ENTRYPOINT ["dotnet", "api.dll"]

There probably isn’t more that needs to be provided to this file as it primarily just Copy and the use the contents of the .publish directory.

Using Docker in Cake

One of the many reasons why I have become a an of Cake of the years is that there is a fairly large open source community that supports and this community seems to be always developing handy plugins to used within Cake ecosystem. This is most certainly true for Docker itself. Miha Markic has developed the Cake.Docker plugin which simplifies many of the steps to using Docker commands in your Cake scripts.

In the remainder of this post we will explore how we can use Cake to build and publish our Docker containers to a Docker Container Registry in this example we will be using Github Container Registry.

To start using the Cake.Docker plugin within your Build Scripts all you need to add the reference to library at the top of your build.cake file

#addin nuget:?package=Cake.Docker&version=1.1.2

This will then enable you to make use of a number of the functions available within the plugin directly in your script.

Once we have added the Plugin we can then write tasks on how we would like to interact with Docker. for instance, we may need to login into our preferred Container Registry before we can interact with it. In this example you we need to login into the Github Container Registry and do this we will need to use a Personal Access Token, or as is the case in this example we will be making use of a special authentication token, GITHUB_TOKEN associated with the organisation account.

What is GITHUB_TOKEN

The GITHUB_TOKEN is special authentication token, that can be used by Github Actions. GitHub automatically creates a GITHUB_TOKEN secret for you to use in your workflow, and you can use it to authenticate in a workflow run.

The way this works is that when you enable GitHub Actions in a repository, GitHub installs a GitHub App on that. The GITHUB_TOKEN secret is basically a GitHub App installation access token.

Before each job begins, GitHub fetches an installation access token for the job from the GitHub App. Since the App has access to a single repository, the token’s permissions are limited to the repository that contains your workflow. And to make it even more secure, the token expires when the job is finished.

The GITHUB_TOKEN instead expires just right after the job is over. So if someone is able to steal it they basically can’t do anything wrong. By Default, the GITHUB_TOKEN has a quite comprehensive list of permissions assigned to it.

For more detailed information regarding Automatic token authentication and understand the Permissions for the GITHUB_TOKEN

How to use GITHUB_TOKEN

To make use of the GITHUB_TOKEN within your Github Actions you can use two forms of syntax, both are equivalent and do exactly the same thing.

It is possible to access it via secrets collection as below

${{ secrets.GITHUB_TOKEN }}

Or my preferred syntax because in my opinion it provides a clearer explanation of where it comes from, because you access via the github context and access the token.

 ${{ github.token }}

In my Github action yaml file I usually initialise an environment variable to store this value in order to make use of it in my Cake script.

name: cake-build
on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Setup .NET
        uses: actions/setup-dotnet@v1
        with:
          dotnet-version: 6.0.x
      - name: Check out Code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0
      - name: Run cake
        shell : bash
        env:
          REGISTRY_TOKEN: ${{ github.token }}
          CONTAINER_REGISTRY: "ghcr.io"
        run: |
          dotnet new tool-manifest
          dotnet tool install Cake.Tool
          dotnet tool restore
          dotnet cake

You may notice an additional Environment variable we configure the CONTAINER_REGISTRY , which we provide the value of ghcr.io , which stands for Github Container Registry. The Cake.Docker, will default to Docker.io if for all activity, fortunately it does provide a useful option to enable interacting with alternative registries.

Docker login

We can now write our task to Login into our chosen Container Registry as follows, which makes use of the Environment variable we defined. A key point to note here is that we configure the DockerRegistryLoginSettings to use our GITHUB_TOKEN in the password and we just provide a spurious username which will be ignored.

Task("Docker-Login")
.Does(() => {
    var loginSettings = new DockerRegistryLoginSettings{ Password = EnvironmentVariable("REGISTRY_TOKEN") , Username= "USERNAME" };
    DockerLogin(loginSettings, EnvironmentVariable("CONTAINER_REGISTRY"));  
});

We can execute our Github action either by commit it too Github or as I mentioned in How to use Cake with Github Actions we can make use of act remember that if using act you need to provide a value for your GITHUB_TOKEN which may be the value of Development PAT or some such. The README of the act repo provides further details.

act -s GITHUB_TOKEN=[insert token or leave blank and omit equals for secure input]

Upon successful execution you should see the success message printed in your Github actions

Login Succeeded

Docker Build

We can now add our additional tasks now to build and publish our Container to the registry.

The code below essentially creates a package name: packageName = $"{containerRegistry}/{ rootNamespace.ToLower() }/{ projectTag.ToLower() }";

Which we provide the Container Registry, in this instance we’re making use of “ghcr.io” , the name of the Organisation i.e. RootNameSpace and then finally the name of the Container we are creating. We also then tag it with a version number.

Task("Docker-Build")
 .IsDependentOn("Docker-Login")
.Does(() => {
     packageName = $"{containerRegistry}/{ rootNamespace.ToLower() }/{ projectTag.ToLower() }";
    string [] tags = new string[]  {  $"{ packageName}:{version}"};
      Information("Building : Docker Image");
    var settings = new DockerImageBuildSettings { Tag=tags};
    DockerBuild(settings, "./");
});

Docker Push

Once the build is successful we push it to our Container Registry.

Task("Docker-Push")
 .IsDependentOn("Docker-Build")
.Does(() => {
        Information("Pushing : Docker Image");
      var settings = new DockerImagePushSettings{ AllTags = true};
      DockerPush(settings, $"{ packageName }");
});

Conclusion

In just a few steps we have created a fully automated build process to include a Docker Build and publish tasks using cake and working with Github actions.

Latest posts by Gary Woodfine (see all)