Continuous Delivery With Github Actions, Docker and Traefik on a Virtual Private Server (Part 1)
Tags: github, docker, traefik, tutorial
Published: 2020-06-17 16:45:23

## Table Of Contents
* [Introduction](#intro)
* [Why?](#why)
* [How?](#how)
* [Let's push that docker image to the registry](#push)
* [That was easy, is that it?](#tbc)

### Introduction <a name="intro"></a>

In this article series, I'll show how you can use Github Actions to setup a simple workflow that after every push to your Github repository will build and publish your code as a docker image and then deploy it to your VPS running Traefik with zero downtime.

If the above made your head spin, might be worth brushing up on a few topics first:
- [Github Actions](https://help.github.com/en/actions)
- [Docker](https://docs.docker.com/get-started/)
- [Traefik](https://docs.traefik.io)

### Why? <a name="why"></a>

Because running a fancy Kubernetes cluster might be all the rage, but it's overkill for most things. Not to mention really expensive. If all you've got is a bunch of small (static?) websites or low traffic hobby projects, you'll be better off just running them all in a cheap Virtual Private Server (even a free one - e.g. [GCP Free Tier](https://cloud.google.com/free)). 

That's where Traefik comes in, letting you run multiple domains/subdomains on the same host, each of them running nice and isolated in a Docker container. 

And Github Actions takes care of deploying everything automatically, so you don't have to do anything other than push your code to the master branch (and maybe throw in a [SemVer](https://semver.org) tag to keep things organised). You can even do zero downtime deployments! Neat, huh?

### How? <a name="how"></a>

I guess the flowchart was inevitable then. It goes something like this:

![Flowchart](https://dev-to-uploads.s3.amazonaws.com/i/xy5hu0a6rswmbgiwlhl8.jpg)

### Let's push that docker image to the registry <a name="push"></a>

Right, so now that the how and the why are clear as a whistle, let's push that docker image to the registry! We'll do that by creating a GitHub Actions Workflow in our GitHub repository:

```yml
name: CI

# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
  push:
    branches: [ master ]
    
    # Publish `v1.2.3` tags as releases.
    tags:
      - v*

    # TODO: file paths to consider in the event. Optional; defaults to all.
    paths:
      - 'path/to/code/that/triggers/this/workflow/*'
  
  # Run tests for PRs into master branch
  pull_request:
    branches: [ master ]
    
    # TODO
    paths:
      - 'path/to/code/that/triggers/this/workflow/*'

# Define enviornment variables for the workflow
env:
  # TODO: Change variable to your image's name.
  IMAGE_NAME: image-name

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # Run tests.
  test:
     runs-on: ubuntu-latest
     
     steps:
      - uses: actions/checkout@v2
      
      - name: Run tests
        run: |
          
          # TODO: Run a Dockerized testing script, or just build the docker image if no such script exists
          if [ -f docker-compose.test.yml ]; then
            docker-compose --file docker-compose.test.yml build
            docker-compose --file docker-compose.test.yml run sut
          else
            docker build . --file path/to/Dockerfile
          fi
     
  # Build and push image to Docker Registry
  push:
    # Ensure test job passes before pushing image.
    needs: test
    
    # The type of runner that the job will run on
    runs-on: ubuntu-latest
    
    # Only run this job for push events 
    if: github.event_name == 'push'

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
    # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
    - uses: actions/checkout@v2

    # TODO: Builds the docker image (path relative to api root)
    - name: Build image
      run: docker build . --file path/to/Dockerfile --tag $IMAGE_NAME

    # Login to the docker registry using the default GITHUB_TOKEN environment variable and github.actor (The login of the
    # user that initiated the workflow run)
    - name: Log into registry
      run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login docker.pkg.github.com -u ${{ github.actor }} --password-stdin
      
    # Push the image to the Docker Registry
    - name: Push image
      run: |
        IMAGE_ID=docker.pkg.github.com/${{ github.repository }}/$IMAGE_NAME
        
        # Strip git ref prefix from version
        VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
        
        # Strip "v" prefix from tag name
        [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
        
        # Use Docker `latest` tag convention
        [ "$VERSION" == "master" ] && VERSION=latest
        
        echo IMAGE_ID=$IMAGE_ID
        echo VERSION=$VERSION
        
        docker tag $IMAGE_NAME $IMAGE_ID:$VERSION
        docker push $IMAGE_ID:$VERSION
```

Please note a few things:
- I've used GitHub Packages as the Docker Registry for convenience, you can use any Docker Registry you want
- Pay attention to the TODO comments, you'll need to change those to suit your app - like the paths and image name
- You can use any name and filename for the yml workflow file, but you need to place the file under .github/workflows folder in your repository

### That was easy, is that it? <a name="tbc"></a>

Sure, if all you needed was a way to automatically push a docker image to the registry once you push your code and you're happy enough to manually deploy that to your server. If not, and you want to automate everything like the nice flowchart showed you, tune in for Part 2.

Thanks!