Publish your own Docker Images with GitHub Actions
Tags: github, docker, webdev, devops
Published: 2022-07-24 09:41:00

**In this post, I will show how easily you can set up your own docker images and then utilize GitHub Actions to deploy them to a Docker registry. This will save you set up time and make your environments more stable.** 

## Prerequisites 
- Git installed and GitHub Account 
- Docker installed (for testing)

## Get Started with Docker

Docker is a virtualization tool which allows you to require an **image** which comes with pre-installed configurations and software. So you can pick an operating system of your liking (NO IOS that's illegal) and then get started with installing what you need. These definitions will be written into a Dockerfile. If you don't have docker installed, head to their [docs](https://docs.docker.com/engine/install/) it is really simple to install it 

### Write your first image

Let's start with a very simple Python image. We will pull the latest distribution of Python and set the `PYTHONUNBUFFERED=1` to ensure that all python output is sent to the terminal for better debugging. Paste the below lines into a `Dockerfile` (Yes the file has no file extension).  

```Dockerfile
FROM python:latest
ENV PYTHONUNBUFFERED=1
```

Once you have the file, you can now run 

```bash
docker build . --file Dockerfile -t ${Image_Name}
```

The `--file` can be used in case your Dockerfile has not the default name or is in another path. The `-t` flag is used to give the image a name. After the image build successfully on your machine, you can go ahead and try to publish it to [hub.docker.com](https://hub.docker.com/). 

Next, we want to tag our image, therefore please create an account at docker hub. We will need the namespace. Before proceeding, please log in locally with docker login.

```bash
docker login --username ${USERNAME}
```
This will log you in and store an encrypted version of your credentials for later use locally.  

To version our image we use tags, so if one image is bad due to any reason, you can always pull an earlier image. The following command will tag the image. The `DOCKER_HUB_NAMESPACE` is by default your username in docker hub. So for me, it would be `snakepy`. 
```bash
docker tag "${IMAGE_NAME}" "${DOCKER_HUB_NAMESPACE}/${IMAGE_NAME}:${VERSION}"
```

Concrete example:

```bash
docker tag python-dev snakepy/python-dev:latest
```

After this, all what is left is to upload the image to docker hub, so you can use it later, therefore you need to run:
```bash
docker push "${DOCKER_HUB_NAMESPACE}/${IMAGE_NAME}:${VERSION}"
```

After you have uploaded your image, you can pull it from another Dockerfile and require it:

```Dockerfile
FROM ${DOCKER_HUB_NAMESPACE}/${IMAGE_NAME}:${VERSION}
```

## WorkFlow for automation 
I have created a repository where I upload my images to. The workflow is set up in a way that if I push to that repository it will automatically build all images and push them to docker hub with a new version. I also created scheduled tasks, which runs periodically to release the latest version of the image. 

This is where the fun begins and once you have your automation set up you can refine it, and it will grow overtime. You can have a look at my [current](https://github.com/snake-py/docker-images) set up.

To automate the creation of docker images, you need to do the following steps:
- create a repository for your images
- set the secrets in the repository 
- create workflow file
- profit :moneybag: 


First create the GitHub repository and then find the secrets tab, there you can add new secrets. You will need to add `DOCKER_HUB_NAMESPACE`, `DOCKER_HUB_PASSWORD` and`DOCKER_HUB_USER`. You can either set them as repository or workflow env. I found it easier to simply set them as repository variable.  

![Screenshot of github secrets](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jw1erjt67mlm6bwuaibs.png)
 

After you have added the secrets, we can proceed to the GitHub workflow file. The file(s) need to be inside `.github/workflows`. In there I created two files, one for scheduled tasks and one if I push. This will help us to deal easier with the versioning of the images. 

```yaml
name: publish-to-docker-hub
on: [push]

env:
  DOCKER_HUB_PASSWORD: ${{ secrets.DOCKER_HUB_PASSWORD }}
  DOCKER_HUB_USER: ${{ secrets.DOCKER_HUB_USER }}
  DOCKER_HUB_NAMESPACE: ${{ secrets.DOCKER_HUB_NAMESPACE }}
  VERSION: ${{ github.sha }}

jobs:
  publish_python:
    runs-on: ubuntu-latest
    env:
      IMAGE_NAME: 'python-dev'
      LANGUAGE: 'python'
      LANGUAGE_VERSION: 3.10
    steps:
      - uses: actions/checkout@v2
      - run: echo ${DOCKER_HUB_PASSWORD} | docker login --username "${DOCKER_HUB_USER}" --password-stdin
      - run: docker build . --file ${IMAGE_NAME}/Dockerfile -t ${IMAGE_NAME}
      - run: docker tag "${IMAGE_NAME}" "${DOCKER_HUB_NAMESPACE}/${IMAGE_NAME}:${LANGUAGE}${LANGUAGE_VERSION}-${VERSION}"
      - run: docker push "${DOCKER_HUB_NAMESPACE}/${IMAGE_NAME}:${LANGUAGE}${LANGUAGE_VERSION}-${VERSION}"
```

First, we define the name of the workflow `publish-to-docker-hub` and then the action trigger. Next, we define the variables which will be pulled from the GitHub secret store. Then we define our jobs. Under Jobs, you can have multiple jobs ([checkout my YAML file](https://github.com/snake-py/docker-images/blob/main/.github/workflows/publish-to-docker-hub.yml)). We are essentially doing what I showed before. We are logging into docker hub, building the image, tagging the image and uploading the image. 

**Please note my repository structure!** Every Dockerfile is placed like this `${IMAGE_NAME}/Dockerfile`. This way I can write small documentations with it or even have config files which I can copy in during build time. 

Now you should see that the action is created for every push you do. I also like an action once a month to release the latest version of the image, just to keep them fresh. 

So I created a second workflow file which is essentially the same and the only difference is the trigger and the VERSION variable. 

```YAML
on:
  schedule:
    - cron: '0 0 1 * *'
  

env:
  DOCKER_HUB_PASSWORD: ${{ secrets.DOCKER_HUB_PASSWORD }}
  DOCKER_HUB_USER: ${{ secrets.DOCKER_HUB_USER }}
  DOCKER_HUB_NAMESPACE: ${{ secrets.DOCKER_HUB_NAMESPACE }}
  VERSION: latest
```


### LET ME KNOW :rocket:

- **Do you need help, with anything written above?** 
- **What will be your first Image?** :smile:
- **Do you think I can improve - then let me know**
- **Did you like the article?** :fire:


Check my [laravel-dev](https://dev.to/snakepy/my-favorite-laravel-development-environment-with-docker-nginx-php-fpm-xdebug-in-vscode-2o03) image out! :fire: