It is time I gave my personal space the same professional discipline I bring to my day job.
806 words (Approximately a 4 minute read)
This website has been a constant companion for over twenty years. It started as a hobby project, hand-coded in Notepad during the early days of the web and uploaded manually with FileZilla. Over the decades, it evolved into my professional portfolio, but like many personal projects, it often became an afterthought—lacking the proper care, attention, and rigorous engineering standards I dedicate to client work.
Recently, I decided it was time to change that. I wanted to treat this personal space with the same professional discipline I bring to my day job, bringing it in line with modern development workflows. That meant moving away from manual FTP uploads and ad-hoc fixes to a robust, automated pipeline.
My Jekyll site uses:
@theme configurationHowever, automating deployments came with a significant risk. My server hosts over twenty years of accumulated artifacts—files, projects, and data that exist alongside the main site but aren’t part of the Git repository. A misconfigured pipeline could easily wipe out decades of history in seconds, so ensuring the safety of these legacy files was paramount.
I needed a deployment pipeline that would:
The workflow handles two separate build processes that need to run in sequence:
# 1. Setup Ruby for Jekyll
- name: 💎 Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2' # Compatible with Jekyll 4.1.1
# 2. Setup Node.js for Tailwind/PostCSS
- name: 🟢 Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
Key learning: Ruby 3.3 had compatibility issues with Jekyll 4.1.1’s logger initialization, so I downgraded to Ruby 3.2 which works reliably.
Migrating to Tailwind v4 introduced several challenges:
@import "tailwindcss" directive requires @tailwindcss/postcss plugin, not the old tailwindcss PostCSS pluginpostcss-scss parser to handle SCSS syntax before Tailwind processes it@theme weren’t generating utilities correctly, so I moved them to the JS config fileThe final PostCSS config:
module.exports = {
plugins: [
require('@tailwindcss/postcss'),
// cssnano disabled due to CSS optimization warnings
// process.env.NODE_ENV === 'production' && require('cssnano')({ preset: 'default' }),
].filter(Boolean),
};
And the build command uses the SCSS parser:
"css:build": "NODE_ENV=production postcss _sass/main.scss --parser postcss-scss --output assets/css/style.css"
My hosting uses SFTP on a non-standard port, which ruled out most GitHub Actions that only support FTP/FTPS. I used lftp, a command-line FTP/SFTP client that supports password authentication and recursive directory syncing.
- name: 📂 Install SFTP tools
run: sudo apt-get update && sudo apt-get install -y lftp
- name: 🚀 Deploy to server via SFTP
run: |
echo "🚀 Deploying site to production..."
lftp -c "
set sftp:auto-confirm yes
set sftp:connect-program 'ssh -a -x -oStrictHostKeyChecking=no'
set cmd:verbose no
open -u $,$ -p $ sftp://$
lcd _site
cd public_html
mirror --reverse --delete --parallel=4 --only-newer \
--exclude-glob .git* \
--exclude-glob .github* \
# ... many more excludes
quit
"
Safe Deployment Strategy: While lftp supports dry runs, I’ve opted for a direct deployment strategy that relies on a comprehensive exclude list to prevent accidental deletions.
Exclude list: A comprehensive list of files and directories that should never be deleted, including:
.htaccess, .htpasswds)While this setup is powerful, it’s important to acknowledge that using GitHub Actions does come with some “lock-in”:
.yml configuration is specific to GitHub. You cannot simply copy-paste this pipeline to GitLab, Bitbucket, or Jenkins.ruby/setup-ruby ties you further to the ecosystem.For a personal portfolio, the convenience of having code and CI/CD in one place usually outweighs these cons. However, if portability is a major concern, there are strategies to mitigate this, which I will cover in a follow-up post.
This setup provides a robust, safe, and transparent deployment pipeline that gives me confidence when pushing changes to production, finally bringing my personal portfolio up to the professional standards I value in my work.