136 lines
5.7 KiB
Markdown
136 lines
5.7 KiB
Markdown
---
|
|
title: "Self-hosting this site"
|
|
slug: /self-hosting-this-site/
|
|
date: 2025-07-21
|
|
tags: ["self-hosting"]
|
|
---
|
|
|
|
[Previously](https://systemsobscure.blog/posts/how-I-deploy-this-site) this site
|
|
was deployed as follows. I used [Gatsby.js](https://gatsbyjs.com) 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](https://forgejo.systemsobscure.net/thomasabishop).
|
|
|
|
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](https://vite.dev)
|
|
React application that gets its content from a JSON index that is generated via
|
|
a pre-build script.
|
|
|
|
I
|
|
[wrote a script](https://forgejo.systemsobscure.net/thomasabishop/systems-obscure/src/branch/main/scripts/generate-post-index.js)
|
|
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:
|
|
|
|
```json
|
|
[
|
|
{
|
|
"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](https://github.com/markedjs/marked). I also added the following steps:
|
|
|
|
- convert `<code>` tags into syntax highlighted blocks using
|
|
[shiki](https://shiki.style/)
|
|
- 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 the
|
|
`public/` 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.
|
|
|
|
<figure style="margin: 1rem 0">
|
|
<video controls>
|
|
<source src="./img/post-index-build-script.webm" type="video/webm">
|
|
Your browser does not support the video tag.
|
|
</video>
|
|
<figcaption>Running the pre-build script to generate the site content.</figcaption>
|
|
</figure>
|
|
|
|
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](https://forgejo.systemsobscure.net/thomasabishop/systems-obscure/src/commit/43eec03edd2e6330362cc5605a48a91387eb49d3/src/templates/BlogTemplate.tsx).
|
|
|
|
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:
|
|
|
|
```yaml
|
|
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](https://forgejo.systemsobscure.net/thomasabishop/self-host/commit/8c380b71735f278f4309bbd14ad96cb9a29104b7)
|
|
in the `docker-compose.`
|
|
|
|
Then I added an
|
|
[nginx `.conf` file for the blog](https://forgejo.systemsobscure.net/thomasabishop/self-host/src/branch/main/proxy/nginx/conf.d/systemsobscure.conf).
|
|
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](https://forgejo.systemsobscure.net/thomasabishop/self-host/commit/61cdbe43c2041d5961ef74416270794eb6fb91c6)
|
|
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.
|