Full CI/CD with Docker + GitHub Actions + DigitalOcean (Droplets + Container Registry)

#github   #docker  
1_UVpX30VREQINLbZILszpuQ.png

In this article, I will show you how did I set up CI/CD for my personal blog with free services.

Nowadays, CI/CD became an essential part when developing software. It can save tons of money, time and reduce risks… In this article, I will show you how did I set up CI/CD for my personal blog with free services.

If you are not familiar with any services, check their definitions:

Firstly, have a look at this workflow.

1_w0mh0Ra8rta6X8LV458Wow.png

  1. Every time we push a new commit to GitHub, it will start processing GitHub Actions workflow.
  2. GitHub Actions will build a Docker image in its runner and push that image to Container Registry.
  3. GitHub Actions then will connect to Droplets and deploy the image from Container Registry to that Droplets.

Let’s see how to set up this workflow.

Add Actions secrets

There are some secret variables that you need to use in the GitHub Action workflow.yml file, so it should be better if they are encrypted by storing them in Github Secrets.

You can go to repository SettingsSecretsActions, then click New repository secret.

1_puAGO_GkIbIRb7kuMcf3DQ.png

GitHub Secrets

There are some variables we need:

  • DIGITALOCEAN_ACCESS_TOKEN: A token that you have generated to access the DigitalOcean API.
    From DigitalOcean dashboard → API → Token/keys → Generate New Token (Remember to check both Read and Write permission)
  • HOST: your ssh host (Droplets ipv4)
  • PASSPHRASE: to encrypt the private key, generated when you create ssh keys for Droplets.
  • SSHKEY: the content of ssh private key. ex raw content of ~/.ssh/id_rsa
  • USERNAME: ssh username, default: root

Now we have enough secret variables to use in our CI/CD workflow file.

Create a workflow YAML file

  • Create a directory called .github/workflows in the repository directory.
  • In that directory, create a new file called build-and-deploy.yml and add the following code:

name: CI

# 1
# Controls when the workflow will run
on:
  # Triggers the workflow on push events but only for the master branch
  push:
    branches: [ master ]

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:
    inputs:
      version:
        description: 'Image version'
        required: true
#2
env:
  REGISTRY: "your-digitalocean-registry-container-url"
  IMAGE_NAME: "your-docker-image-name"

#3
jobs:
  build_and_push:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout the repo 
        uses: actions/checkout@v2
        
      - name: Build container image
        run: docker build -t $(echo $REGISTRY)/$(echo $IMAGE_NAME):$(echo $GITHUB_SHA | head -c7) .

      - name: Install doctl
        uses: digitalocean/action-doctl@v2
        with:
          token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
    
      - name: Log in to DigitalOcean Container Registry with short-lived credentials
        run: doctl registry login --expiry-seconds 600
      
      - name: Remove all old images
        run: if [ ! -z "$(doctl registry repository list | grep "$(echo $IMAGE_NAME)")" ]; then doctl registry repository delete-manifest $(echo $IMAGE_NAME) $(doctl registry repository list-tags $(echo $IMAGE_NAME) | grep -o "sha.*") --force; else echo "No repository"; fi

      - name: Push image to DigitalOcean Container Registry
        run: docker push $(echo $REGISTRY)/$(echo $IMAGE_NAME):$(echo $GITHUB_SHA | head -c7)
        
  deploy:
    runs-on: ubuntu-latest
    needs: build_and_push
    
    steps:
      - name: Deploy to Digital Ocean droplet via SSH action
        uses: appleboy/ssh-action@v0.1.3
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.SSHKEY }}
          passphrase: ${{ secrets.PASSPHRASE }}
          envs: IMAGE_NAME,REGISTRY,{{ secrets.DIGITALOCEAN_ACCESS_TOKEN }},GITHUB_SHA
          script: |
            # Login to registry
            docker login -u ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} -p ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} registry.digitalocean.com
            # Stop running container
            docker stop $(echo $IMAGE_NAME)
            # Remove old container
            docker rm $(echo $IMAGE_NAME)
            # Run a new container from a new image
            docker run -d \
            --restart always \
            --name $(echo $IMAGE_NAME) \
            $(echo $REGISTRY)/$(echo $IMAGE_NAME):$(echo $GITHUB_SHA | head -c7)

  1. Controlling when the workflow will run: In this case, every time we have a push in the master branch.
  2. Define some environment variables that don’t need to be encrypted.
    - IMAGE_NAME: your Docker image name
    - REGISTRY: your DigitalOcean Registry Container URL

1_qelydYvn515ykFP6NYOLlA.png

Registry Container URL

3. We have 2 jobs here: run build_and_push then after it finishes, run deploy.

1_fINT5ipBSpOp_KuMw1eapQ.png

Running jobs

Some things need to be noted in the YAML files:

  • Because DigitalOcean Container Registry only has 500MB for the free plan, so we should remove all old images after we build and push the new image.
  • When deploying, it is better to stop running the container, remove it then run the new image (will pull it from DigitalOcean Container Registry).

If you found this blog is helpful, please share and give some claps 👏👏👏

You can connect with me on LinkedIn and Twitter.

If you have any comments, questions, or recommendations, feel free to post them in the comment section below!

Start blogging about your favorite technologies and get more readers

Join other developers and claim your FAUN account now!

3

Authority

259

Total Hits

Discussed tools
GitHub ActionsDocker