Building a blazing fast self-hosted blog for free

Building a blazing fast self-hosted blog for free

I’ve been wanting to create my own blog for a while. Let's make that finally happen.

What am I looking to build?

Anybody in the world should be able to read and follow my tutorials if they so desire.

With that in mind, let's start by laying down some ground rules:

  1. It’s gotta be hosted on my own platform. I don’t mind re-publishing content on other platforms (like Medium) but the original must not be behind a paywall or subject to the whims of some corporation's Algorithmic Hammer of Doom (I’m looking at you, YouTube 👀).
  2. It’s gotta load fast. I mean, it’s just a blog. There’s no excuse for it being slow. This isn’t some friggin’ distributed realtime machine learning website on The Blockchain™️.
  3. It’s gotta be easy for me to publish posts. I don’t wanna hard-code every blog post. Just gimme a markdown editor with autosaving and be done with it.
  4. It’s gotta be maintenance-free. I don’t mind spending some time setting things up, but it's gotta be self-sufficient for a long time after initial set up.
  5. It’s gotta be free to host. A few years ago, this would have felt like an impossible ask (and for good reason, too). But nowadays with sooooo many free hosting services available (like Netlify, GitHub Pages, Firebase, even Heroku), it feels wasteful to not utilize them.
  6. It’s gotta be easy to read and follow the tutorials. I’m looking to write a lot of technical posts with a lotta code, so it must be easy for people to follow along. That means code syntax highlighting and consistent code formatting (I’m looking at you, Medium 😾).

Ideally, I’d like painless cross-posting to different platforms (like Medium, FreeCodeCamp,… but I don’t know what that involves yet. That’s a problem for future me.

How I’m building it

Building a blog which meets all of the above criteria is big ask (particularly with the free to host part). Here’s how I’m thinking it’ll work:

  1. I write an article in Ghost (using the admin interface)
  2. When I publish the article, Ghost tells Netlify that a new website release is ready
  3. Netlify fetches the latest article content from Ghost and the latest front-end code from GitHub
  4. Netlify merges the content & code to generate a static website (with the articles baked into it) then serves it to the world as a blazing fast static website

This diagram from explains it well:

Image from

What does the tech stack look like?

Web front-end running on Gatsby.js

Somewhere to host the front-end, like Netlify or GitHub Pages.

Headless CMS for writing and storing articles.

  • FreeCodeCamp uses Ghost so it should be sufficient for my needs. Note there are a few alternatives out there too, like Prismic

There’s only one question left: if I don’t want to pay for Ghost Pro, where do I host the headless CMS?

Thanks to Jose Browne’s article, it turns out that we can host it locally using serveo to proxy the connection to Netlify! We’ll go into more detail on that later.

Let’s build this blog


Make sure you have the following installed:

A code editor like VS Code or Atom

And you’ll need to create accounts on:

Setting up the Gatsby front-end

Open up Terminal and install the Gatsby CLI (command-line interface):

yarn global add gatsby-cli

Download the Ghost + Gatsby sample project using Git:

git clone

Enter the new project folder:

cd gatsby-starter-ghost

Install the project's dependencies using yarn:

yarn install

Start a local development server (to preview your blog's front-end):

gatsby develop

Now visit localhost:8000 to see your new blog in action!

Where did these blog posts come from? By default, the project pulls articles from We’ll change that in a bit.

Customising the blog

Let’s open up this Gatsby project in a code editor (my favourite right now is VS Code) and make it our own!

Open up the project in a code editor (for me, that's code . to open the current folder in VS Code).

Open src/utils/siteConfig.js and adjust some of the values to whatever you want. Here are the values I changed:

module.exports = {
  siteTitleMeta: `2 Fwd 1 Back.`,
  siteDescriptionMeta: `With buckets of optimism and a dash of realism, we'll figure it out.`,
  shortTitle: `2fwd1back`
  // I left everything else as default...

And that’s it! If you reload your localhost:8000 page again, you might’ve noticed something: barely anything has changed! Only the site title (in the tab at the top of your browser) will have changed.

Everything else is configured within Ghost, our headless CMS. Before we set up Ghost, let’s deploy our site to Netlify so we know what our site's free URL will be.

Pushing our code to a GitHub repository

Before we can deploy to Netlify, we need to push our front-end code to a GitHub repository (so Netlify knows how to generate our website).

To do this, create a new repository on GitHub - I’ve called my blog and made it public since there’s nothing sensitive in the codebase.

Creating the GitHub repository

Next, we need to push our code to this new GitHub repository:

# Remove the original remote repository URL to gatsby-starter-ghost.git
git remote remove origin

# Add a new remote repository called origin, which points to your GitHub repo
# For example, I did: git remote add origin
git remote add origin YOUR_GIT_URL

# Push the code on your local machine to the GitHub repository
git push -u origin master

But wait, we haven't committed our change yet, right? Let's do that now then push again:

git commit -am "Edited site config"

git push

Now refresh your GitHub repository page. It should contain all your project’s files!

Deploying to Netlify

Our repository is already configured to work well with Netlify (learn more here), so this should be quick:

  1. Visit here to create a new site on Netlify
  2. Connect Netlify to your GitHub account
  3. Select your blog repository
  4. Leave the site on the default settings (which were pulled from our repository’s netlify.toml file)
  5. Click Deploy site

Now we just wait a few mins for it to deploy. Wasn’t that ridiculously easy?

Updating our site URL

While you wait for it to be deployed, click on Domain settings and have a look at the random subdomain that Netlify has assigned you.

Mine was I’m changing it to

After changing your subdomain and waiting for the build to be deployed, visit the URL. Your front-end should be live! (note: it might take a few mins for the DNS update to propagate)

Ghost's default home page & posts

Now that we have a domain, let’s revisit our siteConfig.js file and update the siteUrl:

module.exports = {
  siteUrl: ``,
  // Leave everything else as it is...
Make sure to change your siteUrl to your actual domain (not!

Next just save, commit, and push your changes:

git commit -am "Adjusted site URL in config"

git push

If you take a look back at the Deploys tab in Netlify, it should now automatically be building your updated site!

Deploying the front-end in Netlify

Setting up Ghost: our Headless CMS

Now that our blog is technically live and ready to roll, we just need to set up a CMS so we can edit and publish posts.

The CMS will be storing our articles, tags, site header… all of the dynamic stuff. Where should the CMS be hosted? We have a few options:

  • Pay $29 / mo for Ghost Pro and support the creators of Ghost
  • Pay a $5 / mo to rent a server from DigitalOcean or UpCloud and then follow this guide
  • Run Ghost locally for development and production 😲

By running Ghost locally, we don’t need to pay hosting costs or have to deal with setting up & maintaining any server virtual machines. Let’s get started.

Install the Ghost CLI:

yarn global add [email protected]

Create a new folder for your Ghost admin interface (outside your blog repository's folder!)

mkdir ../blog-admin
cd ../blog-admin

Install your Ghost admin interface:

ghost install local

Done! Now visit localhost:2368/ghost to set up the admin panel.

Ghost setup screen

Note: you can use ghost start and ghost stop to turn your Ghost Admin server on and off.

While you’re in the admin interface, you might want to go through and start customising things. Have some fun making it your own!

A few moments later

While you’re at it, create new post (called something random like Testo Pesto). This’ll help us check that it’s working properly in the next step.

Now that your site is configured to your tastes, let’s link it to the front-end!

Linking Ghost to the front-end (for development)

Visit your Ghost admin panel at localhost:2368/ghost then click on Integrations then Add custom integration

Give your integration a name, like Gatsby Content API, create it, then copy your Content API Key.

Next, open up .ghost.json  (from your front-end project folder) in a code editor and update the development details:

  "development": {
    "apiUrl": "http://localhost:2368",
    "contentApiKey": "YOUR_API_KEY"
  // Leave the rest as it is for now...
Creating a custom Gatsby Content API integration in Ghost

Now let's test this!

Restart your Gatsby server and visit localhost:8000. Your new post should be there!

Linking Ghost to the front-end (for production)

Rather than hosting the admin interface on a server somewhere in 'The Cloud™️', we can just host it off our own machine. (Be aware that this approach means you'll only have access to the panel when your machine is turned on & running the Ghost server).

Okay, so... I'll host Ghost on my own machine. But how does Netlify talk with the server on my machine?

Serveo. Serveo lets us create a temporary tunnel between a subdomain and our local machine. This lets us connect our local machine to Netlify and trigger new builds.

To do so, we just run this in Terminal (no installation necessary!):

ssh -R SOME_NAME_HERE:80:localhost:2368

# For example:
# ssh -R daniel-ghost-admin:80:localhost:2368

For the curious: Serveo is just like Ngrok but you don’t need to install anything :D

Now you can access the Ghost admin panel via your new Serveo tunnel. In my example, my URL was:

Next, add this domain to your front-end's  .ghost.json config so it knows where to look for your blog's content:

  "production": {
    "apiUrl": "",
    "contentApiKey": "YOUR_API_KEY"
  // Leave the rest as it is...
Make sure to insert your Content API Key from earlier (the same one that we used for development)

Save the file, commit, then push. This should trigger a new Netlify build so make sure your admin panel is running (via serveo!). This will let Netlify fetch content from your new admin panel.

Netlify deploying the new content update
Our new post is live on Netlify
And now you're pretty much done! There's only got a couple optional steps left:

Add a Netlify webhook from your admin panel (so Netlify will automatically rebuild when you make changes in your CMS). If you don't do this, you'll need to manually click 'Deploy' in the Netlify dashboard every time you update or publish a new post.

Add a CDN to store images (right now they'll be stored on your machine, so they'll only be available when your machine is running & serving the Ghost admin interface & connected to serveo).

We'll dive into these in the next article.