Setup Github Actions for Hugo
This blog-post describes how to set up Github Actions for building and deploying Hugo using Docker container.
The described approach is a starting point for further improvements but it will automate the deployment of new blog posts to a remote server where your blog is hosted.
This blog post doesn’t explain every line of the configuration but describes the used approach and refer to the official Github Action documentation.
We’re going to achieve the following:
- push a new blog post to the
masterbranch - build a Docker image and push it to the
Github Packages - deploy the Docker image to the remote server
Create a Docker file
To render a static content of our blog I’ll use Docker image based on Alpine Linux.
Create a Docker file with the following content at the root of the project folder:
FROM nginx:alpine
COPY public /usr/share/nginx/html
Add .gitignore
To exclude generated static content to be pushed to the repository add the following .gitignore file:
/public
.idea/
data/
layouts/
resources/
Push all changes to the repository.
Create a personal access token
To pull and push Docker images from/to Github packages a personal access token should be configured.
For more information read the Github help page
Add Github Actions to the repository
Github Actions may be added to the repository by one of the following ways:
- create
workflowsfile manually under.github/workflows - use an existing template from Github
I’ll describe how to use the Publish Docker Container action template.
Go to the blog Github project for which Github actions will be configured.
Select Actions tab
Find the Publish Docker Container template and click on Set up this workflow:
In case if the template is not displayed click on Workflows for Python, Maven, Docker and more... which looks like this:
Provide the workflow and file names.
A new workflow file will be created under .github/workflows/deploy-and-publish.yml
Find the IMAGE_NAME variable and update it to something more meaningful. In my case, it’s dev-pages. Right now the whole workflow file looks smth like this:
name: Deply_and_publish
on:
push:
# Publish `master` as Docker `latest` image.
branches:
- master
# Publish `v1.2.3` tags as releases.
tags:
- v*
# Run tests for any PRs.
pull_request:
env:
# TODO: Change variable to your image's name.
IMAGE_NAME: dev-pages
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
GIT_SSH_COMMAND: "ssh -o StrictHostKeyChecking=no"
DOCKER_LOGIN_COMMAND: echo "${{ secrets.GITHUB_TOKEN }}" | docker login docker.pkg.github.com -u ${{ github.actor }} --password-stdin
jobs:
# Run tests.
# See also https://docs.docker.com/docker-hub/builds/automated-testing/
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run tests
run: |
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 Dockerfile
fi
# Push image to GitHub Packages.
# See also https://docs.docker.com/docker-hub/builds/
push:
# Ensure test job passes before pushing image.
needs: test
runs-on: ubuntu-latest
if: github.event_name == 'push'
steps:
- uses: actions/checkout@v2
- name: Build image
run: docker build . --file Dockerfile --tag image
- name: Log into registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login docker.pkg.github.com -u ${{ github.actor }} --password-stdin
- name: Push image
run: |
IMAGE_ID=docker.pkg.github.com/${{ github.repository }}/$IMAGE_NAME
# Change all uppercase to lowercase
IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')
# 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 $IMAGE_ID:$VERSION
docker push $IMAGE_ID:$VERSION
Click on the Start commit button.
Our build should fail because according to our Docker file we need to copy the public folder to the container folder /usr/share/nginx/html.
Go to the Action tab again. Now we should see our first failed workflow:
The error message will look like:
Fix failed workflow
To fix our failed workflow open .github/workflows/deploy-and-publish.yml and update test and push jobs
Update test job
Update test job so that Hugo is installed and static content generated before verification of docker-compose file. The test job should look like:
# Run tests.
# See also https://docs.docker.com/docker-hub/builds/automated-testing/
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true # Fetch Hugo themes
fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: 'latest'
extended: true
- name: Build
run: hugo --minify
- name: Run tests
run: |
if [ -f docker-compose.test.yml ]; then
docker-compose --file docker-compose.test.yml build
docker-compose --file docker-compose.test.yml run sut
fi
Update push job
Update push job so that Hugo is installed and static content generated before building and pushing Docker image. Push job should look like:
push:
# Ensure test job passes before pushing image.
needs: test
runs-on: ubuntu-latest
if: github.event_name == 'push'
steps:
- uses: actions/checkout@v2
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: 'latest'
extended: true
- name: Build
run: hugo --minify
- name: Build image
run: docker build . --file Dockerfile --tag image
- name: Log into registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login docker.pkg.github.com -u ${{ github.actor }} --password-stdin
- name: Push image
run: !!! LEFT WITHOUT CHANGES. SEE ABOVE !!!
Here I used the following:
- GitHub Actions for Hugo
- the latest Hugo version
- the
extendedversion of Hugo (usedbilberry-hugo-themerequires it)
Now our workflow should be green on the Action tab.
A new generated Docker image may be found on the packages tab:
Open Your profile tab
Go to the Packages file
Deployment configuration
This section describes how to add and configure a deployment job to the Workflow. The described approach is not perfect but rather simple and works so far.
Options for improvements:
- run Github runner in Docker container
- configure ssh key at runtime
Add and configure self-hosted runners
To add a self-hosted runner please refer to the Github help page: Hosting your own runners
Open .github/workflows/deploy-and-publish.yml file and replace all runs-on: ubuntu-latest by runs-on: self-hosted
Generate ssh key
To deploy a new Docker image to the remote service we need to
- generate
sshkey on the runner’s host machine - add
publickey to theauthorized_keysfile on a remote server where the blog is running
To generate ssh key login to the runner’s machine and run (without a passphrase):
ssh-keygen
As a result, a public and a private keys will be generated at the ~/.ssh folder.
Copy all content from the public key ~/.ssh/id_rsa.pub
Add authorized_keys
- Login to the blog remote host
- Past the content of
~/.ssh/id_rsa.pubto the~/.ssh/authorized_keys
Add deploy job
Now it’s time to add a deploy job.
Open .github/workflows/deploy-and-publish.yml file and add:
deploy:
needs: push
runs-on: self-hosted
steps:
- name: Log into registry
run: ssh [email protected] "$DOCKER_LOGIN_COMMAND"
- name: connect via ssh and recreate docker image
run: ssh [email protected] "cd ~/path_with_docker_compose_file && docker-compose pull && docker-compose up -d"
where
blogusera user on the remote server where the blog is running and who will deploy a new Docker imageblog.hostis the hostname or IP address of the remote blog serverpath_to_docker_compose_filepath to the folder wheredocker-compose.ymlfile is located.
Create docker-compose.yml file
Create docker-compose.yml file at the project root folder like:
version: '3'
services:
hugo:
image: docker.pkg.github.com/{GITHUB_USER}/{GITHUB_REPOSITORY_NAME}/{DOCKER_IAMGE_NAME}:latest
restart: always
container_name: your-blog-hugo-container-name
hostname: your-blog-host-name
ports:
- 8384:80
where {GITHUB_USER}, {GITHUB_REPOSITORY_NAME} and {DOCKER_IAMGE_NAME} are self explained.
The full docker image path may be found on the Github Packages folder. See above.
Full version of .github/workflows/deploy-and-publish.yml
The complete version of .github/workflows/deploy-and-publish.yml looks like:
name: Deply_and_publish
on:
push:
# Publish `master` as Docker `latest` image.
branches:
- master
# Publish `v1.2.3` tags as releases.
tags:
- v*
# Run tests for any PRs.
pull_request:
env:
# TODO: Change variable to your image's name.
IMAGE_NAME: dev-pages.info
jobs:
# Run tests.
# See also https://docs.docker.com/docker-hub/builds/automated-testing/
test:
runs-on: self-hosted
steps:
- uses: actions/checkout@v2
with:
submodules: true # Fetch Hugo themes
fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: 'latest'
extended: true
- name: Build
run: hugo --minify
- name: Run tests
run: |
if [ -f docker-compose.test.yml ]; then
docker-compose --file docker-compose.test.yml build
docker-compose --file docker-compose.test.yml run sut
fi
# Push image to GitHub Packages.
# See also https://docs.docker.com/docker-hub/builds/
push:
# Ensure test job passes before pushing image.
needs: test
runs-on: self-hosted
if: github.event_name == 'push'
steps:
- uses: actions/checkout@v2
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: 'latest'
extended: true
- name: Build
run: hugo --minify
- name: Build image
run: docker build . --file Dockerfile --tag image
- name: Log into registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login docker.pkg.github.com -u ${{ github.actor }} --password-stdin
- name: Push image
run: |
IMAGE_ID=docker.pkg.github.com/${{ github.repository }}/$IMAGE_NAME
# Change all uppercase to lowercase
IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')
# 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 $IMAGE_ID:$VERSION
docker push $IMAGE_ID:$VERSION
deploy:
needs: push
runs-on: self-hosted
steps:
- name: Log into registry
run: ssh [email protected] "$DOCKER_LOGIN_COMMAND"
- name: connect via ssh and recreate docker image
run: ssh [email protected] "cd ~/path_to_docker_compose_file && docker-compose pull && docker-compose up -d"