In this blog post, I'm going to show you how to create a Docker image from your github repository and publish it to the Docker hub container registry. So anytime you cut a new release or commit to the master
branch, Github actions will build your image and push it to the registry.
Why?
Why would you publish your repository as a Linux container image? Good question. Not all projects should be published as Linux container images. The rule of thumb is if your code can be used as a standalone piece of software (front-end and back-end applications, servers, databases, command-line programs) then it could and it should be packaged as a Linux container.
By publishing your code to the container registries ( it doesn't have to be a Docker registry) you can automate the deployment of your apps by pulling those published containers to your hosting providers, stopping the old ones, and running the new ones ( I'm going to show you how to do this in a future article).
The Plan
This tutorial will be software language-agnostic since we are not going to concentrate on the process of packaging the code itself, but we are going to concentrate on setting up continuous integration and tying it all together (although the example repository is a Node.js server application).
The Code
When building software one very important rule is that the master
branch should always be deployable. So we are only ever going to publish Docker images from the master
branch although you could publish from different branches and create different images such as canary
, beta
etc...
So we have two options for the master
branch. We could publish the images on every commit to the master or we could publish the images only when we create/publish a new release
.
If you are unsure what Github releases are and how to use them, you can consult Github documentation.
This is all the code that is needed for the github actions workflow.
name: Build and publish Docker image
on:
release:
types: [published]
# on:
# push:
# branches:
# - master
jobs:
push_to_registry:
name: Build and publish
runs-on: ubuntu-latest
steps:
- name: Get release tag
id: tag_name
run: echo ::set-output name=SOURCE_TAG::${GITHUB_REF#refs/tags/}
- name: Check out the repo
uses: actions/checkout@v2
- name: Npm Install
run: npm ci
- name: Npm Build
run: npm run build
- name: Login to DockerHub
run: echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
- name: Build image
env:
SOURCE_TAG: ${{ steps.tag_name.outputs.SOURCE_TAG }}
run: ./docker-image/build.sh
- name: Docker Hub Description
uses: peter-evans/dockerhub-description@v2
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKERHUB_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
DOCKERHUB_REPOSITORY: ivandotv/grant-server
Now, I'm going to explain each step individually.
First, we need to respond to the event
that is triggered on the repository. In our case, we are responding to the release
event of type published
. This means that the workflow will only ever be triggered when this event happens. There are a few ways you can trigger the release
event on the repository, you can automate it (by creating another workflow that does this for you), or you can do it manually. You can read more about the github workflow events from the official documentation.
on:
release:
types: [published]
Or if you want to respond to every push to the master
branch you can use this code (commented out in the original):
on:
push:
branches:
- master
Next, we need to set up a job that is going to do all the heavy lifting. The job id is push_to_registry
and the name
is "Build and publish". It is going to run on Ubuntu 20.04
jobs:
push_to_registry:
name: Build and publish
runs-on: ubuntu-20.04
steps:
# see next step
The Steps
1 - Since we are building the image after a new release (which always should have a git tag) we are going to retrieve that tag, which will later be used in step 6. In case if you are using a push to master
event then you should use the latest commit data instead of a tag, something like git rev-parse --short HEAD
.
- name: Get release tag
id: tag_name
run: echo ::set-output name=SOURCE_TAG::${GITHUB_REF#refs/tags/}
2 - Check out the repository. This means that we are going to have the full repository (all the files) to work with inside our workflow.
- name: Check out the repo
uses: actions/checkout@v2
3 - Since the example is a Node.js application we are going to install all node package dependencies we need:
- name: Npm Install
run: npm ci
4 - Build the application by running the build
script from the package.json
.
Note that the build script could run other tasks like lint
and test
but at this point, we are counting on that the application works as expected since all the testing should be done in some previous workflow that you have set up when pushing commits to your branches.
- name: Npm Build
run: npm run build
5 - Login to Docker hub. For this step, you need to setup github secrets (that are going to be available inside the workflow). We need docker username
and docker password
. How to create secrets is explained in Github docs
- name: Login to DockerHub
run: echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
6 - Build the image. Building the Docker image involves a few command line calls, so the best thing is to create a script that will group all those commands in a single file that you can run easily. We are setting the SOURCE_TAG
environment variable that we are referencing from the previous step (steps.tag_name
- step 1), this variable will be picked up by the script ./docker-image/build.sh
(this is the file inside the repository itself) and be used to tag the docker image before pushing to Docker hub.
- name: Build image
env:
SOURCE_TAG: ${{ steps.tag_name.outputs.SOURCE_TAG }}
run: ./docker-image/build.sh
7 - Optional. Since Docker hub images also have the concept of a README
file, we are going to take the README.md
from the repository and also put it up on the Docker hub. That way we have only one source for the documentation, both for the Github repository and the Docker hub.
- name: Docker Hub Description
uses: peter-evans/dockerhub-description@v2
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKERHUB_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
DOCKERHUB_REPOSITORY: ivandotv/grant-server
That is all it takes to automate the publishing of container images to the Docker hub.
Now, if you take a look at the repository (link provided below) you will find workflow files in .github/workflows
directory.
.github/
└── workflows
├── CI.yml
└── docker-build.yml
There are two files: CI.yml
and docker-build.yml
. Code referenced in this tutorial is from the later file, the former one is used to test the code, publish to NPM and create and then tag a release. That file triggers the release
event that workflow in this tutorial (docker-build.yml
) is using to start the publishing process. If you are developing JavaScript applications you might want to look at that file as well.
Next, we have docker-image
directory with two files
docker-image/
├── build.sh
└── Dockerfile
I haven't covered those files in this tutorial because they are used for building the actual Docker image itself, build.sh
file contains command-line instructions on how to prepare for the build process, and Dockerfile
contains instruction for building the image.
Repository: ivandotv/grant-server
Docker hub page: Grant server
Thank you for reading.
Top comments (0)