How I set up this blog

The blogging technologies I settled with

I have done web development professionally, but, I want to get this blog up and spinning and jump straight to writing. So, despite still having nightmares about my friend Krish, asking me in highschool why I couldn’t have just used wix for my 6 months long full stack project, this time, I will stand on the shoulders of blogging platforms (he has since written a 20 page apology letter, and if I’ve had a lapse of memory, he certainly should).

I chose to use Ghost as the base of my blog. For my non technical readers, think of Ghost as a large two-parted process running on some remote computer: One process displays your blog, the other allows you to manage your blog, so adding posts, sending newsletters, etc, all from an admin panel. For my technical readers, Ghost uses NGINX to serve your blog, provides a like editor to create Markdown posts, and maintains a robust API for everything to be done programmatically.  

So, my only blogging technology is Ghost.

Self hosting Ghost

If you don’t self host Ghost, you are asking to host your blog and use’s servers. The pros? A sense of security. is going to keep the Ghost software (your blog) running on their servers up to date. does the madness of managing SSL certificates, CDN networks, and mailing services for you. The cons? It’s costlier than self-hosting ($25/mo for creator option, $9/mo for starters).

If you self host Ghost, it’s a bit cheaper ($6/mo on DigitalOcean). Extra pros? A sense of freedom. You can scale up your servers as you need, ssh into the servers, and add any customization (WARNING! Customization may be euphemism for fragility).

So, I followed the instructions here to host my blog on DigitalOcean. It’s a “one click” configuration, but I still need to manually do the following:

  • Generate SSL certficates so that my blog uses HTTPS.
  • Configuring subdomains to go to my main domain. For example, if user goes to, I want to route them to

Note that at this point, after following the hyperlinked instructions, I have pointing at my DigitalOcean server’s IP address.

The former bullet point is done by directly using https in my URL during the initial “one click” configuration. If I accidentally used the http version, I can just run ghost stop and ghost setup again and input the https URL. Ghost, under the hood, uses Let’s Encrypt to generate SSL certificates and add to NGINX for me. If I don’t want to setup everything, I could specify the step to ghost setup, like ghost setup nginx ssl.

The latter is accomplished by first pointing Ghost to the non main URL (my main URL is the non-www one, so,, you probably want to do the same, since www is just a subdomain)

ghost config url

Then, since this happens to be https, I setup a SSL certificate

ghost setup nginx ssl

Now, I nano the NGINX configuration file at /etc/nginx/sites-enabled/, append a 301 redirect to the main URL using return 301$request_uri;, and remove the server block. My configuration file now looks like:

server {
    listen 80;
    listen [::]:80;
    root /var/www/ghost/system/nginx-root; # Used for SSL verification (

    location ~ /.well-known {
        allow all;

    client_max_body_size 50m;

    return 301$request_uri;

Since this happens to be https, I also made the edits to /etc/nginx/sites-enabled/

I used this list in my own research:

Developing a custom Ghost blog theme

The official Journal theme is almost perfect.

But, I want to replace the subscribe buttons with discord invite buttons. So, I git clone the official Journal repo, delete all occurrences of “subscribe”, and replace them with

// default.hbs
<a class="gh-author-discord" href="" target="_blank" rel="noopener"></a>

// icons/discord

// override.css
.gh-author-discord { 
	width: 32px; 

Now, there are limits. For example, it seems like my  css overrides on the subscription portal are ignored. I don’t care since I am not using it. But, that’s a benefit to self hosting, you can go the extra mile to find out where Ghost ignores certain overrides and disable that line!

At last, I gulp zip and upload the zip files in the admin portal to deploy the changes.

Custom theme CI/CD

The last sentence of the previous paragraph might not have many words, but will certainly weigh heavy on my devops engineers. Instead of having to run a command, open up the admin portal, and drag a drop a folder, I automate theme uploading with Ghost’s Javascript SDK:

const api = new GhostAdminAPI({
  key: <your_staff_key>,
  version: "v5.0",
const data = { file: "dist/<your_package_json_name>.zip" };
api.themes.upload(data).then(({ name }) => api.themes.activate(name));

To push this one step further (pun intended), I uploaded my theme repo to github and added this script to a github workflow that runs on push to main. The script is a little different on my repo since I am using environment variables that get its values from github workflow injected secrets.