Using Github Actions with Docker
With Github Actions’ arrival in 2018, more and more projects adopt it as a full-blown CI/CD solution for their projects. This article will see how we can leverage Github Actions to build and publish a Docker Image to Github Container registry or your private Registry.
In this article, I will:
- Give you a basic introduction to Github Actions.
- Show you how to make an action that builds our docker image.
- Show you how to push a docker image to our Github repository.
Pre-requisites
Before we start, we need a repository with a working Dockerfile (since we assume that you have a working Dockerfile). If you don’t have that, I have created a reference and published it to Github that you are free to copy or fork at andersro93/docker-static-demo.
An introduction to Workflows, Jobs, and Steps
A workflow is a configurable automated process made up of one or more jobs. You must create a YAML file to define your workflow configuration.
A workflow is a configurable automated process made up of one or more jobs. You must create a YAML file to define your workflow configuration.
When we utilize Github Actions, we always start with what’s called a Workflow. A Workflow is defined using YAML. You may have multiple workflows in different YAML files. These files need to reside inside .github/workflows
relative to the root of your repository.
A Workflow may have one or many jobs that will execute for that Workflow. By default, the jobs are executed in parallel and run on entirely different runners (machines). However, we can force these jobs to run sequentially. Nevertheless, I want to emphasize that you try to treat each job as non-dependent on the previous one.
Now, a job consists of many steps. Each step runs after one another, and the steps will execute in the same environment as the previous one.
So, a workflow consists of one or many jobs, which in turn consists of one or many steps. Confusing? Let’s illustrate this with some pseudo-code.
My workflow (Workflow)
- job 1 (Job)
- Step 1A (Step)
- Step 1B (Step)
- Step 1C (Step)
- job 2 (Job)
- Step 2A (Step)
- Step 2B (Step)
As seen above, the Workflow in total forms a nested structure where the Workflow is the top entity with one or more jobs.
Let’s first explore the role of the Workflow. The Workflow may have multiple attributes. We can, for instance, give our Workflow a name or declare when we want our Workflow to execute. For a complete syntax list for Workflows, see the documentation.
Moving on, we have Jobs. As mentioned, a job consists of steps that execute in order, in the same environment. Meaning that we can copy a file in step 1, while in the next step, we could execute that file. Only running simple commands means that you have to reinvent the wheel; hence, we strive not to go down that route.
That brings us to the real power of Github Actions, namely the steps. A step may also inherit behavior from what’s called an Action (or a template). Meaning, instead of writing many steps doing something trivial, we can use an existing Action or template that someone else wrote.
As a result of the nature of our steps/Actions, we can end up with something like the example below.
My workflow (Workflow)
- Build and deploy (Job)
- Build our app (Step)
- Run our test suite (Step)
- Deploy our app (Step)
We now have only three steps for doing something previously reserved for advanced CI/CD pipelines. In the next section, we will move on to a real example.
Our sample application
Let’s move on to a real-world example. We will now use a sample directory with a working Dockerfile. If you don’t have any, you may clone/fork (or steal) this sample.
If we clone the repository and run the command docker build -t mysample .
in our repository and run our built image with docker build -p 8080:80 mysample
. Then open our browser at localhost:8080; we should see the following.
Creating our first Action
Let’s get straight to it. I will post the entire Workflow below, then explain it. Ready, set, go.
name: Our workflowon:
push:
branches: [ main ]jobs:
build:
name: "Checkout and build"
runs-on: ubuntu-latest steps:
- name: "Checkout code"
uses: actions/checkout@v2 - name: "Build image"
run: docker build .
That’s it! What you saw was all the code that is required for us to build our docker image. (I know, I said in the description that we would do more, but hold on, we’ll get there).
First off, we name the Workflow “Our workflow” nothing big there. The next part is the crucial part. We need to define when our Workflow should run. With that info, you could guess when we run this Workflow. We run our Workflow On (or when someone) push code to any branch called main. There are multiple ways of defining when a workflow should run. I recommend looking at the samples in the documentation if you require more control over your Workflow.
Next, we have jobs. In this case, we have defined a job with an id of “build” (we’ve named it “Checkout and build”). This job runs on the latest Ubuntu image available on Github.
Now, if we take a look at our steps, you can see that we have two of them. The first step is quite common. It merely “checks out” our code from Github and ensures that our code is available in our job. The next step runs our docker build command. After that, you are done! That is all you need.
Now, let’s explore and improve our Workflow even more in the next steps.
Tagging and pushing our image to Github
I imagine that if you have followed along so far, the next steps will be pretty easy for you. We will tag our docker image and push it to a registry that comes with a Github repository.
So let’s start by tagging our image during our build. We can do that by updating our existing Workflow to this.
name: Our workflowon:
push:
branches: [ main ]jobs:
build:
name: "Checkout and build"
runs-on: ubuntu-latest steps:
- name: "Checkout code"
uses: actions/checkout@v2 - name: "Build image"
run: docker build . --tag docker.pkg.github.com/andersro93/docker-static-demo/docker-static-demo:latest
Next, we need to login into our repository. Github allows us to use “secrets” inside our workflow file. Some secrets are already pre-defined, while you may define others. We won’t touch upon user-defined secrets until the next section, so for now, we may use the pre-defined ones. The ones we are going to use are “GITHUB_TOKEN” and “github.actor”. The first will be a token that we may use to login into our repository, while the “github.actor” is the username of the person triggering the Workflow. More information about Github Workflow Authentication is available here.
Now, we only have to use those values to login. Luckily for us, there is an Action that makes this step in a simple manner. In the example below, I have updated our Workflow to utilize this Action.
name: Our workflowon:
push:
branches: [ main ]jobs:
build:
name: "Checkout and build"
runs-on: ubuntu-latest steps:
- name: "Checkout code"
uses: actions/checkout@v2 - name: "Build image"
run: docker build . --tag docker.pkg.github.com/andersro93/docker-static-demo/docker-static-demo:latest - name: "Login to Registry"
uses: docker/login-action@v1.6.0
with:
registry: docker.pkg.github.com
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} - name: "Publish image"
run: docker push docker.pkg.github.com/andersro93/docker-static-demo/docker-static-demo:latest
As you may see, we have added two new steps to our Workflow. In the first, we use an Action that allows us to log in to our repository. Next, we finally push our image to that repository.
Now, we may navigate to /packages on our repository. We can now see that our image is successfully published. Thus, depending on your settings, it should either be open to the world or just you and your team.
Further work
If you want, there are many ways to improve the Workflow, but I think we’ll stop for now. However, if you’re going to dive deeper into the topic, I suggest that you try to create a template for multiple projects using the environment variables you have available. Maybe you can add a “test” step if you have a stage in your Dockerfile that supports it? I think that it’s mostly your imagination that stops you, and with all the Actions in the marketplace, it is super easy to almost anything.
Conclusion
This article has provided us with a template that you may use as a baseline for any project you desire. I hope this has ignited an interest in Github Actions and that you can add this to your toolbox for maybe an ongoing or future project.
If nothing else, I hope you have found the topic fascinating, and if you have any questions, please ask them in the comments below.