Deploying Jekyll on a Linode VM via GitHub Actions
In my GitHub Actions, “Getting your feet wet”, guide series I went over how to build a docker container, automatically updating it using a app called Diun, deploying the container update to a VM in Linode, and creating a releasable binary. All based on the web server I use for my homelab, Caddy. This post was not originally planned to be apart of the series but I am including it as an example of a more, “advanced”, actions deployment. By default Jekyll deploys to GitHub Pages which is really nice if you do not want to spin up your own web server.
For me though I wanted to host it myself. The process for this was mostly straight forward as Jekyll already includes the ability to self host. The diagram below demonstrates the GitHub Actions flow for both the default deployment to pages and my deployment to a Linode VM.
Workflows for deploying Jekyll via GitHub Actions
If you want to use the actions file below you will need the following items completed beforehand.
- Cloned / Setup a Jekyll blog in a repository on GitHub.
- Have a VM running that is accessible by SSH from the internet (WAN).
- I recommend Linode if you are just starting out.
- Have a user account created on the VM for actions to log into via SSH.
- You can check out my post, here, which details this process.
- Have a domain registered and pointing to the public (WAN) IP of the VM.
- Have a web server installed and setup to serve from the directory the blog will be in.
- I am using Caddy to serve the blog directory.
- Repository secrets setup:
- LINODEFINGERPRINT = ssh host fingerprint
- LINODEHOST = hostname/wan ip of the VM
- LINODEUSER = username actions needs for login
- LINODESSHKEY = ssh private key for above user
- DISCORD_WEBHOOK = text channel webhook url. Only needed if you use the notify job.
GitHub Actions workflow for deploying the Jekyll blog:
I am using the Chirpy theme for Jekyll. The files / directories being copied in the build step may be different for the theme you are using! I was also having some odd issues so these file / directory copies may not be necessary.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
name: 'Deploy Blog'
on:
push:
branches:
- main
paths-ignore:
- .gitignore
- README.md
- LICENSE
workflow_dispatch:
jobs:
# build and create the jekyll blog artifact
# include 'skip ci' in commit message to not run this workflow
build:
if: "github.ref == 'refs/heads/main' && !contains(github.event.head_commit.message, 'skip ci')"
runs-on: ubuntu-latest
steps:
- name: checkout repo
uses: actions/checkout@v2
with:
fetch-depth: 0 # for posts's lastmod
- name: setup ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7
bundler-cache: true
- name: build production
env:
_config: _config.yml
_baseurl:
run: JEKYLL_ENV=production bundle exec jekyll b --incremental
- name: copy required files
run: |
cp -r _data _site/data
cp -r _plugins _site/plugins
cp -r _tabs _site/tabs
cp _config.yml _site/config.yml
- name: upload blog
uses: actions/upload-artifact@v3
with:
path: _site/*
retention-days: 1
# grab the build artifact and test it using htmlproofer
test:
needs: build
runs-on: ubuntu-latest
steps:
- name: download blog
uses: actions/download-artifact@v3
with:
name: artifact
path: _site/
- name: test production site
uses: chabad360/htmlproofer@master
with:
directory: "_site/"
arguments: --disable-external --check-html --allow_hash_href=true
# grab artifact if test job is successful and deploy it using rsync
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- name: download blog artifact
uses: actions/download-artifact@v3
with:
name: artifact
path: _site/
- name:
run: |
mkdir ~/.ssh
touch ~/.ssh/known_hosts
echo ${{ secrets.LINODEFINGERPRINT }} >> ~/.ssh/known_hosts
- name: deploy blog
uses: burnett01/[email protected]
with:
switches: -avr
path: _site/
remote_path: /opt/sites/blog/
remote_host: ${{ secrets.LINODEHOST }}
remote_port: 22
remote_user: ${{ secrets.LINODEUSER }}
remote_key: ${{ secrets.LINODESSHKEY }}
# use nobrayner/discord-webhook action to notify status.
# delete this job if you do not want to use it.
notify:
name: notify
runs-on: ubuntu-latest
needs:
- build
- test
- deploy
if: ${{ always() }}
steps:
- name: send notif discord
uses: nobrayner/discord-webhook@v1
with:
github-token: ${{ secrets.github_token }}
discord-webhook: ${{ secrets.DISCORD_WEBHOOK }}
include-details: 'true'
color-success: '#7ED321'
color-failure: '#D0021B'
color-cancelled: '#9013FE'
avatar-url: 'https://octodex.github.com/images/Terracottocat_Single.png'
title: '${{ github.workflow }}'
description: '${{ github.workflow }} was triggered. It returned status: {{STATUS}}'
Workflow file for deleting artifacts to save GitHub storage space:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# trigger workflow to run after listed workflows are run
name: 'Delete Old Artifacts'
on:
workflow_run:
workflows: [Deploy Blog, Deploy Blog (DEV)]
types:
- completed
# delete all leftover artifacts since GitHub's lowest retention time period is 24 hours
jobs:
delete-artifacts:
runs-on: ubuntu-latest
steps:
- uses: kolpav/purge-artifacts-action@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
expire-in: 0 # Setting this to 0 will delete all artifacts
Caddyfile example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
{
email email@email.com
# acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
debug
log {
output file /var/log/caddy/caddy.log {
roll_size 12mb
roll_keep 10
roll_keep_for 24h
}
format json {
message_key msg
level_key debug
time_key ts
name_key name
time_format wall
}
}
}
(tls) {
tls {
dns cloudflare {env.CF_API_KEY}
}
}
blog.alexsguardian.net {
import tls
root * /opt/sites/blog/
file_server
handle_errors {
rewrite * /404.html
file_server
}
}
This post is merely an example for the GitHub Actions guide series. I actually use the above workflow to deploy this blog from GitHub so it does work! If you have any questions, or a suggestion, feel free to reach out via a comment or email!
Affiliate Links
FTC: Some links in this post are income generating.