How to start your Ghost Blog for almost FREE in cloud

Introduction

Resources

Image by Google on Cloud Platform Compute Engine Free Tier Usage Limits

Enable Compute Engine API

Create a Disk

  1. Navigate to Compute Engine, Under Storage, click Disks
  2. CREATE DISK
  3. Under name, ghostvol
  4. Location, Single zone. Region, us-west1. Zone doesn’t matter
  5. Disk source type, Blank disk
  6. Disk type, Standard persistent disk. We choose this as Google covers 30GB-month standard persistent disk!
  7. Size, 10GB is enough for 90% of use cases. You can always extend it later so no worries!
  8. (Optional but highly recommend to do it) Enable snapshot schedule, CREATE A SCHEDULE
    1. Name, schedule-past-3-days
    2. Schedule frequency, Daily
    3. Start time (UTC), choose any time you want or just leave it as default
    4. Autodelete snapshots after, 3. It means you only keep the past 3 days backup which is more than enough!
    5. Deletion rule, Keep snapshots!
    6. Click on CREATE
  9. Finally click on CREATE

Create a VM Instance

  1. Navigate to Compute Engine, Under VM instances, CREATE INSTANCE
  2. Under Name — ghost
  3. Region — us-west1, Zone — us-west1-a (Pick the same zone as our Disk above)
  4. Under Machine configuration, Series — E2 (default), Machine type — Choose e2-micro. (Make sure you choose e2-micro to prevent unnecessary charge!)
  5. Under Boot disk, click CHANGE, Operating system — Debian, Version — Debian GNU/Linux 10 (buster), Boot disk type — Standard persistent disk, Size — 10GB. 10GB should be more than enough for our use case!
  6. Under Firewall, click Allow HTTP traffic and and Allow HTTPS traffic
  7. Under Disk, click on ATTACH EXISTING DISK, choose ghostvol. Everything else leave it as default, SAVE.
  8. Click on CREATE! 😻

Reserve Static IP

  1. Search for Exernal IP addresses in Google Cloud Platform Search console
  2. Click on RESERVE for your newly created VM
  3. Name — Put anything you want, I just put my domain name (e.g. chenming-io)

Mount the disk

# List the attached disk and format the disk, normally its /dev/sdb

sudo lsblk
# DANGER ZONE # Format the disk if it's a new disk! Don't do this to existing DISK!!! It will wipe out all your data!

sudo mkfs.ext4 -m 0 -E lazy_itable_init=0,lazy_journal_init=0,discard /dev/sdb
# Make the mount point and mount the disk to the mountpoint

sudo mkdir -p /mnt/ghostvol
sudo mount -o discard,defaults /dev/sdb /mnt/ghostvol
# Make sure can read/write of the volume

sudo chmod a+w /mnt/ghostvol
# Check that you can navigate to the directory now
# You should see a lost+found folder when you type ls

cd /mnt/ghostvol
# Check the id of the disk, copy down the UUID
# e.g. UUID="xxxxxxxxx"

sudo blkid /dev/sdb
# Backup

sudo cp /etc/fstab /etc/fstab.backup
# Replace the UUID with the id of the disk in previous step

sudo nano /etc/fstab

UUID=389615e9-8183-4846-a809-f55e0404383a /mnt/ghostvol ext4 discard,defaults,nofail 0 2

# Click Ctrl+X -> Y -> Enter
# Double check everything ok

cat /etc/fstab
# Optional, can do a reboot to see everything is working fine. It should auto mount when it reboots!
# Wait for 2 minutes and reconnect to the VM again

sudo reboot

Setup SSL Certificate

# Create nginx configuration folder

mkdir -p /mnt/ghostvol/nginx/conf
# cd to the directory and create the file

cd /mnt/ghostvol/nginx/conf
nano ghost.conf
# Paste the below content into the file
# Please replace all the your_domain_name with the domain you purchase

server {
listen 80;
server_name your_domain_name;
server_tokens off;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name your_domain_name;
gzip off;
ssl_certificate /etc/letsencrypt/live/your_domain_name/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your_domain_name/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
proxy_pass http://ghost:2368;
proxy_hide_header X-Powered-By;
}
location ~ /.well-known {
allow all;
}
client_max_body_size 50m;
}
# cd to the directory and create the file
cd /mnt/ghostvol/nginx/conf
nano www.conf
# Paste the below content into the file
# Please edit all the your_domain_name with the domain you purchase

server {
listen 80;
server_name www.your_domain_name;
server_tokens off;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://your_domain_name$request_uri;
}
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name www.your_domain_name;
gzip off;
ssl_certificate /etc/letsencrypt/live/your_domain_name/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your_domain_name/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
return 301 https://your_domain_name$request_uri;
}
location ~ /.well-known {
allow all;
}
client_max_body_size 50m;
}
mkdir -p /mnt/ghostvol/create-ghost-blog

cd /mnt/ghostvol/create-ghost-blog

nano init-letsencrypt.sh
# Please replace domains with your (domain_name) e.g. www.hello.io, hello.com
# Please replace your email to your email e.g. hello@gmail.com
# You might want to edit your data_path if you are using different mounting point
#!/bin/bash
if ! [ -x "$(command -v docker-compose)" ]; then
echo 'Error: docker-compose is not installed.' >&2
exit 1
fi
domains=(hello.io www.hello.io)
rsa_key_size=4096
data_path="/mnt/ghostvol/certbot"
email="hello@gmail.com" # Adding a valid address is strongly recommended
staging=0 # Set to 1 if you're testing your setup to avoid hitting request limits
if [ -d "$data_path" ]; then
read -p "Existing data found for $domains. Continue and replace existing certificate? (y/N) " decision
if [ "$decision" != "Y" ] && [ "$decision" != "y" ]; then
exit
fi
fi
if [ ! -e "$data_path/conf/options-ssl-nginx.conf" ] || [ ! -e "$data_path/conf/ssl-dhparams.pem" ]; then
echo "### Downloading recommended TLS parameters ..."
mkdir -p "$data_path/conf"
curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf > "$data_path/conf/options-ssl-nginx.conf"
curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem > "$data_path/conf/ssl-dhparams.pem"
echo
fi
echo "### Creating dummy certificate for $domains ..."
path="/etc/letsencrypt/live/$domains"
mkdir -p "$data_path/conf/live/$domains"
docker-compose run --rm --entrypoint "\
openssl req -x509 -nodes -newkey rsa:$rsa_key_size -days 1\
-keyout '$path/privkey.pem' \
-out '$path/fullchain.pem' \
-subj '/CN=localhost'" certbot
echo
echo "### Starting nginx ..."
docker-compose up --force-recreate -d nginx
echo
echo "### Deleting dummy certificate for $domains ..."
docker-compose run --rm --entrypoint "\
rm -Rf /etc/letsencrypt/live/$domains && \
rm -Rf /etc/letsencrypt/archive/$domains && \
rm -Rf /etc/letsencrypt/renewal/$domains.conf" certbot
echo
echo "### Requesting Let's Encrypt certificate for $domains ..."
#Join $domains to -d args
domain_args=""
for domain in "${domains[@]}"; do
domain_args="$domain_args -d $domain"
done
# Select appropriate email arg
case "$email" in
"") email_arg="--register-unsafely-without-email" ;;
*) email_arg="--email $email" ;;
esac
# Enable staging mode if needed
if [ $staging != "0" ]; then staging_arg="--staging"; fi
docker-compose run --rm --entrypoint "\
certbot certonly --webroot -w /var/www/certbot \
$staging_arg \
$email_arg \
$domain_args \
--rsa-key-size $rsa_key_size \
--agree-tos \
--force-renewal" certbot
echo
echo "### Reloading nginx ..."
docker-compose exec nginx nginx -s reload
chmod u+x /mnt/ghostvol/create-ghost-blog/init-letsencrypt.sh

Install docker and docker compose

  1. SSH into your VM
  2. Run following commands to install docker-compose
# You can just copy paste most of the commands except for the username
# Make sure you use your own username!!!
sudo apt-get update
sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg \
lsb-release
# Add Docker’s official GPG key:

curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# Set up the stable repository

echo \
"deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install the latest docker engine

sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io
# Add your user to docker and activate group
# Replace user_xyz with your username, can use command "whoami" to
check your username

whoami

sudo usermod -aG docker your_username

newgrp docker
# You should be able to run this command

docker ps
# Install docker compose

sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

sudo chmod +x /usr/local/bin/docker-compose

Update your domain

  1. Go to your domain provider and update your domain, we point both the root domain (@) and www domain to the IP address of the Virtual Machine.
Image by Author on Sample DNS Mapping from namecheap.com
Image by Author on DNS Check from dnschecker.org

Setup Docker Compose

cd /mnt/ghostvol/create-ghost-blog

nano docker-compose.yml
version: '3.1'services:
nginx:
image: nginx:1.21.6-alpine
restart: unless-stopped
volumes:
- /mnt/ghostvol/nginx/conf/ghost.conf:/etc/nginx/conf.d/ghost.conf:ro
- /mnt/ghostvol/nginx/conf/www.conf:/etc/nginx/conf.d/www.conf:ro
- /mnt/ghostvol/certbot/conf/:/etc/letsencrypt/:ro
- /mnt/ghostvol/certbot/www/:/var/www/certbot/:ro
- /mnt/ghostvol/www_data:/var/www/html
ports:
- "80:80"
- "443:443"
depends_on:
- ghost
- certbot
networks:
- nginx
command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
certbot:
image: certbot/certbot
restart: unless-stopped
volumes:
- /mnt/ghostvol/certbot/conf/:/etc/letsencrypt/:rw
- /mnt/ghostvol/certbot/www/:/var/www/certbot/:rw
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
ghost:
image: ghost:4.41.3-alpine
volumes:
- /mnt/ghostvol/ghost_data/:/var/lib/ghost/content
restart: always
environment:
database__client: mysql
database__connection__host: db-mysql
database__connection__user: root
database__connection__password: Ghostiscool@999
database__connection__database: ghost
url: https://your_domain_name
depends_on:
- db-mysql
networks:
- nginx
- db_mysql
db-mysql:
image: mysql:5.7
restart: always
environment:
MYSQL_ROOT_PASSWORD: Ghostiscool@999
volumes:
- /mnt/ghostvol/mysql_data:/var/lib/mysql
networks:
- db_mysql
networks:
db_mysql:
nginx:

Start Your Blog

cd /mnt/ghostvol/create-ghost-blog 

./init-letsencrypt.sh
cd /mnt/ghostvol/create-ghost-blog 

docker-compose up -d

Setup Your Blog

  1. Now you can login to your blog using https://your_domain_name/ghost, register and you are done! 🤗 You may begin your writing journey without worrying much about cost!
  2. (Optional) You can play around with http://your_domain_name or www.your_domain_name or http://www.your_domain_name or https://www.your_domain_name, they all will be pointing to the same address!

--

--

--

Certified Cloud Architect | https://chenming.io | Imperial MEng

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

READ/DOWNLOAD%? Low-Speed Wind Tunnel Testing FULL

Distributed Locust Testing on Google Kubernetes Engine (GKE)

Why does not Matlab use the full capacity of my computer while training a neural network?

Learn to properly define your own exceptions when built-in exceptions in python are insufficient

Towards Container-Based Development 🐬🐬🐬

We need to talk about User Stories.

Ruby on Rails algorithm interview questions

TWiGCP — “New log viewer, it’s raining big data and storage product features, and a new SRE book”

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Chenming Yong

Chenming Yong

Certified Cloud Architect | https://chenming.io | Imperial MEng

More from Medium

AI content writing tool

ONLINE COURSES: Worth it or bleh?

Charlii — Project Management Tool for Writers

brainstorming tools for writing, time management for writers, outline tool online, charliiapp

Wagtail blog part#1