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
master
branch - 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
workflows
file 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
extended
version of Hugo (usedbilberry-hugo-theme
requires 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
ssh
key on the runner’s host machine - add
public
key to theauthorized_keys
file 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.pub
to 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
bloguser
a user on the remote server where the blog is running and who will deploy a new Docker imageblog.host
is the hostname or IP address of the remote blog serverpath_to_docker_compose_file
path to the folder wheredocker-compose.yml
file 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"