If you're like me, you've had that domain for a while now, you've toyed with the idea several times... Even got a WordPress setup! And that's when things got scary! But allow me to introduce to ghost: the fastest way to get people to read you.


TL;DR for those that don't want the explanation

Create a Google Cloud F1-micro instance on ant US region (excluding Northern Virginia [us-east4]). The account and the running instance are completely free!

Promote the instance's external IP from ephemeral to static, take note of it and change your domain's A record accordingly.

Copy the nginx config file replacing the occurences of `chida.me` with your own domain, and restart nginx.

Start ghost (on a docker container), and voilà!

Alternatively, you can just follow the images and code snippets.

So, why ghost?

By the time I decided I want a blog, my Pavlovian reflex dictated that I should get a WordPress setup. And so I did. Having worked with it's headless version (no front-end), I was confident that nothing could really stop me... But I was wrong.

For someone that only wants to start writing about software and share it right away, it's rather frustrating to find several hinderances from the beginning:

  • The default theme is not great design-wise;
  • Choosing a theme can be really troublesome: there are those with beautiful designs but meant for e-shops;
  • Installing a theme can be conflicting with some other plugins you may want to have;
  • You do need a plugin if you want to start type nicely formatted code and there are quite some choices;
  • The whole procedure feels like an overkill.

After all, what I wanted was the nice reading and writing experience of Medium but without the nuisance of a paywall for my readers, or Medium's censorship and political bias.

Ghost offers an open-source fully-featured blogging platform with the ease of use that an amateur blogger like me needs but with the power-display that a professional would show. After trying it out and noticing that it was the engine of some of my favourite blogs I was convinced this is what I needed.

The catch: base deployments start at USD 29/month. But There's always a way...

What We Are Going To Do

This guide aims to provide full details of every step taken here. But if you are in a hurry, and already know your way around GCP and the Cloud Shell, skip to the images and code snippets directly (this is guaranteed to take less than 15 minutes).

The steps are as follows:

  1. Create the instance on GCP;
  2. Pointing your domain;
  3. Install the necessary software: `nginx`, `certbot`, and `docker`.

The last step is the meaty part of this guide and, therefore, the longest. But as stated before, as long as you follow the code snippets in order, you'll be fine.

So, let's start!

Creating the instance

We are about to create an instance within the Google Cloud Platform Free Tier, ie it won't cost you anything.

Go to GCP's menu > Compute Engine > VM instances, and change only the following:

  • Name: whatever you want, eg `ghost-vm`;
  • Region: pick any that starts with `us` except for `us-east 4`;
  • Zone: whichever you want, it won't make much of a difference;
  • Machine type: f1-micro;
  • Boot disk: Debian GNU/Linux 10 (buster), and Size (GB): 30 (still free);
  • In the Firewall options: Allow HTTP traffic, and Allow HTTPS traffic.
Options for a free F1-micro instance on GCP

Pay attention for a second to the floating text on the right-hand side. It should read something like 'Your first 744 hours of f1-micro instance usage are free this month.'

GCP confirming us that the instance will indeed be free

Pointing your domain to this instance

It's now time to get one of those dusty domains the exposure they deserve. We will do this by means of changing its A record to point to the instance's external IP.

If you don't have a domain, I suggest you to get one at the registrar of your choice, or consider these:

But before we log in to our registrar, we need to know what our freshly created instance's IP address is, and change it to a static one given that we are already on it.

Go to GCP's menu > Compute Engine > VM instances, select the instance you just created, and press the EDIT button on the navigation bar. Scroll down to Network interfaces, and press the pencil icon to edit it.
In the section External IP click to display the dropdown menu and press Create IP Address. Enter a name for it, eg `my-domain-ip`.

Changing the external IP of our instance to static

Whilst an ephemeral IP gets deleted right when the instance to which it is assigned is deleted. You cannot choose which external IP any of your instances will get unless you have it reserved (for your project) which is what we just did. This means that even if your instance is deleted, you can always spin up a new one and assign it the same IP.
A note about the cost of reserved IP addresses: they only cost when they are not assigned to anything, so we are on the safe side.

Newly assigned static IP address

We take note of our static IP set the A record in our registrar. While every registrar is different, the general rule is:

  • Finding the '(Advanced) DNS configuration';
  • Setting the only A record with host `@` (or whatever the all-character is) and target our previously noted IP.
Setting our A record in namecheap

The propagation while take a while but it's alright; we still have work to do.

Installing the necessary software

We are about to install the following:

  • `nginx` as a reverse proxy to redirect all HTTP request to our server as HTTPS requests, and to serve the directory needed by certbot;
  • `certbot` to get a free SSL certificate issued by Let's Encrypt;
  • `docker` to run `ghost` on  a container.

Let's start by accessing our instance via SSH. The easiest way is to use the GCP Cloud Shell. Go to the details page of your instance and click on the SSH button under the Remote Access section.

A shell in a browser – how cool is that!

Once in our server, let's update our local package index:

sudo apt update

Installing and configuring nginx

Start by running the following command:

sudo apt install nginx

Now that nginx is up and running, we need to instruct it on how to serve requests.

We need to remove the generic configuration file and replace it for the snippet below. For that, run:

sudo rm -r /etc/nginx/sites-enabled/default &&\
sudo nano /etc/nginx/sites-enabled/default

And while in the editor, paste the following content replacing the five (5) occurrences of `chida.me` with your domain:

server {
        server_name     chida.me;
        listen          80;
        location /.well-known/ {
                root /var/www/chida.me/.well-known/;
        location / {
                return 301 https://$server_name$request_uri;
server {
        server_name     chida.me;
        listen          443 ssl;
        location / {
                proxy_pass    ;
                proxy_set_header        X-Real-IP $remote_addr;
                proxy_set_header        Host $http_host;
                proxy_set_header        X-Forwarded-Proto https;
                proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        ssl_certificate /etc/letsencrypt/live/chida.me/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/chida.me/privkey.pem;
        ssl on;
Content of /etc/nginx/sites-enabled/default
Attention: remember that this guide is accurate Debian 10. In some other Linux distributions, the file may actually need to be instead in `/etc/nginx/conf.d/default`.

In order to save and exit press CTRL+X, Y, Enter in that order.

So what are we doing here? Well we are actually declaring two servers: one listening to port 80 (default port for HTTP), and another one to port 443 (for HTTPS).

The first server declaration states the following:

  • If someone requests access to the path `/.well-known/` serve them the files in `/var/www/chida.me/.well-known/`;
  • If someone requests access to any other path, return the 301 status code (Moved permanently) whose target is the same but with protocol `https`.

The first location entry is for certbot's magic to get us the SSL certificate. The second is for all requests to our blog be forced as HTTPS.

The second server declaration states that:

  • We will listen to port 443 using the SSL protocol;
  • The SSL certificate can be found in `/etc/letsencrypt/live/chida.me/fullchain.pem`;
  • The key to encrypt the decrypt the communication can be found in `/etc/letsencrypt/live/chida.me/privkey.pem`;
  • If someone requests anything at any location pass the request ``, with those special headers.

The SSL certificate and private key are not there yet since we haven't run certbot (but we will soon), and the location statement is saying 'let ghost handle the request.' The latter happens since ghost will be serving http (no 's') requests within the confinement of on port 2368.

Since we just changed the configuration of our nginx, let's restart it:

sudo systemctl restart nginx

Getting a Let's Encrypt certificate with certbot

Installing certbot is as easy as you would expect:

sudo apt-get install certbot

Then, we just ask it to actually get us what we want from it:

sudo certbot certonly --webroot

Follow the instructions; pay special attention to writing the name of your domain as you did before in the nginx configuration file,

What this command does is:

  • Generating a private key in PEM format in `/etc/letsencrypt/live/<your_domain>/privkey.pem`;
  • Generate the response to Let's Encrypt challenge in `/.well-known/acme-challenge` for the Let's Encrypt to accredit that we have control over the server serving the domain;
  • Downloading the newly generated certificate for your domain in `/etc/letsencrypt/live/<your_domain>/fullchain.pem`.

In other words, pure magic! Believe it or not, it wasn't always that easy.

Additionally, certbot will automatically renew your certificate. If you want to find out more about it, read Section 5. Test automatic renewal of this guide.

Installing and running ghost

As said before, will run ghost on docker. This is a multistep process but all of the steps are simple:

Install the dependencies:

sudo apt -y install apt-transport-https ca-certificates curl gnupg2 software-properties-common

Import Docker GPG key used for signing Docker packages:

curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -

Add the Docker repository, which contains the latest stable releases of Docker Community Edition:

sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/debian \
   $(lsb_release -cs) \

Finally, updating again the apt package index (given that we just added the docker repo), and installing docker:

sudo apt update &&\
sudo apt -y install docker-ce docker-ce-cli containerd.io

The moment of truth has finally arrived! It's time to run the ghost docker container with the following command (substituting `chida.me` with your domain):

docker run -d\
--name ghost\
-v ~/ghost:/var/lib/ghost/content\
-e url=https://chida.me\

If the propagation of your domain's A record has already reached your computer, you will be able to access your blog now!

Freshly created ghost blog with SSL certificate

If you've reached this step, congratulations! 🥳
You are now the proud owner of a new, completely free, GCP-hosted, SSL-secured ghost blog. And you did it on your own 💪

What's next?

First, you'll need to access `https://<your_domain>/ghost` to set up your credentials.

And then... Well, obviously start writing!

If at any point you find yourself in troubles, I recommend visiting: