5.7 KiB
title | slug | date | tags | |
---|---|---|---|---|
Self-hosting this site | /self-hosting-this-site/ | 2025-07-21 |
|
Previously this site
was deployed as follows. I used Gatsby.js to generate a
static website from React JavaScript. When changes to the main
branch were
pushed to the remote GitHub repository, a GitHub action would execute, building
the source files and transferring them to an S3 bucket. The HTML generated was
then served using AWS Cloud Front.
As I am now trying to self-host as many services as I can, it was time remove the dependency on AWS and GitHub and instead serve the site from my VPS and manage deployment via my self-hosted Forgejo instance.
As part of this process I went on a bit of a side-quest to rebuild the site without Gatsby.
When I tried to update to the lastest version of Gatsby I faced several problems mostly due to Gatsby trying to foist server-side rendering on every project. For such a small blog site with next to zero traffic this is unnecessary. Moreover, I realised that I could also do without all the bloat that Gatsby adds via its plugin ecosystem. (Perhaps if frontend bundles weren't so large, SSR wouldn't be needed lol.)
So I decided to simplify the blog and build it as a Vite React application that gets its content from a JSON index that is generated via a pre-build script.
I
wrote a script
in JavaScript that loops through all the blog posts in Markdown format from a
/posts
directory at the project root. It creates a JSON array with an entry
for each post. For example, this post would be represented as follows:
[
{
"slug": "self-hosting-this-site",
"title": "Self-hosting this site",
"date": "2025-07-22T00:00:00.00Z",
"tags": ["self-hosting"],
"html": "<p>Previously this site was..."
}
]
To convert the raw markdown to HTML I used marked. I also added the following steps:
- convert
<code>
tags into syntax highlighted blocks using shiki - compress and resize images and then transfer them to the
public/
directory where Vite sources static assets when deployed - find and replace all image
src
attributes in the resulting HTML with thepublic/
file path
When I run npm run build:posts
this generates the post-index.json
file and
saves it to /public
so that the React application can read from it when
rendering the site content.
I created a custom hook called usePosts
that fetches the index and saves it to
the session storage to avoid unnecessary network requests. You can see this
being invoked in the
template component for blog posts.
Having rebuilt the site without the dependency on Gatsby, the next step was to deploy it.
I wanted to retain the "push and deploy" model that I previously achieved via
the GitHub Action. This was easy because the syntax for Forgejo Actions is
practically identical. You place the YAML declaration in the .forejo/
directory of the project and Forejo picks it up on each push to the remote.
Here's the file:
name: Deploy Blog
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
container: node:18
steps:
- uses: actions/checkout@v3
- run: npm install
- run: npm run build:posts
- run: npm run build
- run: |
echo "${{ secrets.SSH_FORGEJO_KEY }}" > /tmp/ssh_key
chmod 600 /tmp/ssh_key
ssh -i /tmp/ssh_key -o StrictHostKeyChecking=no ${{ vars.VPS_USER }} "bash -c 'rm -rf /var/www/systemsobscure.blog/*'"
scp -i /tmp/ssh_key -o StrictHostKeyChecking=no -r dist/* ${{ vars.VPS_USER }}:/var/www/systemsobscure.blog/
rm /tmp/ssh_key
Before compiling the build I first run the pre-build script to generate the post
index. Then I remove the existing source files for the site in var/www/
over
ssh
and then transfer the new source files using scp
. (While I run most
third-party services on my VPS via Docker, I prefer to keep it simple with my
own applications and not use containers.)
In order for the site to be served from var/www/systemsobscure.blog
, I had to
give my Docker instance of nginx running on the VPS access to this directory by
adding it to the volume mappings
in the docker-compose.
Then I added an
nginx .conf
file for the blog.
This file specifies the SSL certificate to use and sets index.html
as the
document root. It also adds some default caching and compression, along with
security headers.
Finally I
updated my SSL certificate generation script
to include systemsobscure.blog
.
And that's it. My personal website is now self-hosted on my VPS and is automatically deployed via pushes to my self-hosted Git forge. No more GitHub, no more AWS.