Since the source code of my website is kept on Github, using Github Actions to automate building and deploying my website seems to be a natural choice. Plus, it’s relatively simple to setup.
Step 1: Create the Workflow YAML#
I’m using Hugo and Github provides a pretty good template for Hugo so it’s a good start. The only problem is that the template helps you deploy the website to Github Pages, but I need to deploy to my own server.
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
| name: Deploy Hugo site to mywebsite.com
on:
# Runs on pushes targeting the default branch
push:
branches: ["master"]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false
# Default to bash
defaults:
run:
shell: bash
jobs:
# Build the website and deploy the generated static site to the server
build-and-deploy:
runs-on: ubuntu-latest
env:
HUGO_VERSION: 0.112.5
steps:
- name: Install Hugo CLI
run: |
wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \
&& sudo dpkg -i ${{ runner.temp }}/hugo.deb
- name: Install Dart Sass
run: sudo snap install dart-sass
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
- name: Get short SHA
id: short_sha
run: echo "SHA_SHORT=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Build with Hugo
env:
# For maximum backward compatibility with Hugo modules
HUGO_ENVIRONMENT: production
HUGO_ENV: production
run: hugo --minify
- name: Deploy to mywebsite.com
env:
SSH_PRIVATE_KEY: ${{ secrets.MYWEBSITE_COM_KEY }}
run: |
mkdir ~/.ssh && echo "${SSH_PRIVATE_KEY}" > ~/.ssh/id_ed25519 && chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H mywebsite.com >> ~/.ssh/known_hosts
DEPLOY_DIR=anakinfoxe.com_${{ steps.short_sha.outputs.SHA_SHORT }}_$(date +%Y%m%d%H%M%S)
ssh deployer@mywebsite.com "mkdir -p /var/www/$DEPLOY_DIR"
rsync -avz --delete public/ deployer@mywebsite.com:/var/www/$DEPLOY_DIR
ssh deployer@mywebsite.com "ln -snf /var/www/$DEPLOY_DIR /var/www/mywebsite.com"
ssh deployer@mywebsite.com "find /var/www/ -maxdepth 1 -type d -name 'mywebsite.com_*' -mtime +180 -exec rm -rf {} \;"
|
Here is the main steps of the workflow:
branches: ["master"]
defines the condition to trigger the workflow: a push event onto the master
branch.- In
jobs
defines only one job: build-and-deploy
which will generate static website and deploy it to the server. Most configs and steps were provided in the template except:Get short SHA
will be used to name the folder uploaded to the serverDeploy to mywebsite.com
- Use the SSH private key to login to the server
- Use
rsync
to recursively sync generated static website to the server - Create symbolic link to the website root directory
- Remove previous uploads that are more than 180 days old
Why use one job to build and deploy#
Instead of using a build job and a deployment job? Because that will require me to upload the generated static website to somewhere (for example, Github Pages) once the build is done, and then download it when the deployment job starts. I feel it’s not necessary for this simple project.
Step 2: Prepare User#
An user “deployer” will be used to login to the server and does all the deployment work. I don’t want to use any existing users on the server so I can properly manage its permission and capability.
- Create the “deployer” user as I used in the workflow yaml, and create SSH folder
1
2
3
4
| sudo adduser --disabled-password --gecos "" deployer
sudo mkdir /home/deployer/.ssh
sudo chown deployer:deployer /home/deployer/.ssh
|
- Generate SSH keys for the user on the local computer
1
| ssh-keygen -t ed25519 -C "deployer@mywebsite.com" -f ~/.ssh/github_actions_deployer
|
Don’t provide any passphrase. Otherwise it needs to be entered during the workflow. Two files will be generated under ~/.ssh
:
github_actions_deployer
: private keygithub_actions_deployer.pub
: public key
- Copy the content of the public key into
/home/deployer/.ssh/authorized_keys
- On Github repository, go to “Settings” and in “Secrets and variables”, create a Repository Secret named
MYWEBSITE_COM_KEY
for Actions, with the content of private key - Add the user to the group that owns
/var/www
directory
1
2
| sudo usermod -aG www-data deployer
sudo chmod g+w /var/www
|
That’s it.