Automate Publishing of Your Github Repository to  Docker Hub
Tags: docker, github, ci
Published: 2021-01-18 18:50:33

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](https://docs.github.com/en/github/administering-a-repository/managing-releases-in-a-repository).

This is all the code that is needed for the github actions workflow.

```yml
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](https://docs.github.com/en/actions/reference/events-that-trigger-workflows).

```yml
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):

```yml
 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`

```yml
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`.

```yml
- 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.


```yml
- 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:

```yml
- 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.

```yml
- 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](https://docs.github.com/en/actions/reference/encrypted-secrets#creating-encrypted-secrets-for-an-environment)

```yml
- 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.

```yml
- 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.

```yml
- 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.
```bash
.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
```bash
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](https://github.com/ivandotv/grant-server)
Docker hub page: [Grant server](https://hub.docker.com/repository/docker/ivandotv/grant-server)

Thank you for reading.