Automating blog deployment with GitHub Actions
Every time I published a new post I had to do the same thing: SSH into the server, navigate to the right folder, run hugo build, and disconnect. It worked, but it was manual work that didn’t need to be manual.
GitHub Actions fixes that. Now every time I push a change to GitHub, the deployment happens automatically — no SSH, no manual commands, nothing.
Here’s how I set it up.
What GitHub Actions actually is
GitHub Actions is a CI/CD tool built into GitHub. You define a workflow in a YAML file and GitHub runs it automatically when something happens — in this case, when I push new content.
The workflow does three things:
- Detects a push to the
mainbranch - SSHs into the AWS VM
- Runs
git pullandhugo build
That’s it. Simple, but it removes the manual step completely.
Step 1 — Create a dedicated SSH key for GitHub Actions
Instead of using my personal SSH key, I created a separate one specifically for this:
ssh-keygen -t ed25519 -C "github-actions" -f ~/.ssh/github_actions -N ""
cat ~/.ssh/github_actions.pub >> ~/.ssh/authorized_keys
The public key stays on the server (authorized_keys). The private key goes to GitHub. If it ever gets compromised, I can revoke it without touching my personal access.
Step 2 — Add the public key to GitHub
Go to github.com → Settings → SSH and GPG keys → New SSH key and paste the public key:
cat ~/.ssh/github_actions.pub
This allows the VM to authenticate with GitHub using SSH instead of a username and password.
Step 3 — Configure SSH on the VM
Tell git to use this specific key when connecting to GitHub:
cat > ~/.ssh/config << 'EOF'
Host github.com
IdentityFile ~/.ssh/github_actions
StrictHostKeyChecking no
EOF
chmod 600 ~/.ssh/config
Without this step, git pull inside the workflow would either fail silently or prompt for credentials — neither of which works in an automated pipeline.
Step 4 — Store deployment credentials as GitHub secrets
In the repository settings under Secrets and variables → Actions, add three secrets:
SSH_PRIVATE_KEY— the private key generated in Step 1SSH_HOST— the server IPSSH_USER—ec2-user
These are encrypted and never exposed in logs.
Step 5 — Create the workflow file
name: Deploy Blog
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy to VM
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd ~/lucianosblog
git pull origin main
hugo build
This file lives at .github/workflows/deploy.yml. Every push to main triggers it.
Three things that caught me out
Token permissions — when I pushed the workflow file, GitHub rejected it:
refusing to allow a Personal Access Token to create or update workflow
without `workflow` scope
Fix: regenerate the token and add the workflow scope alongside repo.
Security Group — GitHub Actions connects from external IPs, so port 22 needs to be open to 0.0.0.0/0 in the AWS Security Group. I only had my own IP whitelisted, which caused a timeout error.
Silent git pull failure — the workflow showed green but nothing deployed. The issue was that git pull was using HTTPS and had no credentials to authenticate. Switching to SSH (Steps 2 and 3) fixed it.
The result
The publishing workflow is now:
git add .
git commit -m "New post"
git push
30 seconds later the site is live. No SSH, no manual build, no context switching.
The entire blog also lives in GitHub — content, theme, config, everything. If the server goes down, redeploying on a new instance takes minutes.
Questions or feedback? Connect with me on LinkedIn.