If you ever deployed a blog or website to the public internet, you know that running a live site usually incurs operational costs. For example, a managed blog site hosted on Ghost.org starts at $29/month.
You can considerably cut down such expenses by using a self-hosted solution, but a publicly reachable server is not free. A self-hosted solution typically costs around $5/month. Even a relatively inexpensive solution that was described in the Ghost CMS on Hetzner Cloud tutorial here on Jamify.org cost you at least $3/month.
As Jamify sites are static sites they can be deployed to a global CDN and do not require traditional servers. CDN deployments are extremely cost efficient, therefore you can benefit from the free tiers of Netlify or other providers and run your live site at no costs on a global CDN.
However, up until recently, you still needed at least a self-hosted Ghost CMS installation on a public server. Why? Because not all images were included in your Gatsby bundle, so they had to be served from your Ghost CMS! That's why you had to make sure your CMS is publicly reachable and available 24/7, rendering all cost benefits of the static site approach useless.
With the new Jamify plugin gatsby-rehype-inline-images
you can now integrate all images into your static site bundle, so you do not need the CMS for serving images anymore. This opens up the possibility to deploy your blog from localhost for free.
Ghost CMS on localhost
Besides costs, being able to run your headless Ghost CMS on localhost has a couple of notable advantages:
- Easier setup as all production concerns fall away.
- No need to worry about security (firewalls, proxies, ports, authentication, SSL certificates, etc.).
- You can build your Jamify website offline and achieve fast build times that are not limited by network bandwidth (unless your blog incorporates external images).
Of course, a local CMS installation also has some limitations and cannot cover all use cases:
- Inviting team members is not possible (no public access).
- Member functions (e.g. newsletter subscription and distribution) cannot be used (no publicly available backend).
Even if you need a public Ghost CMS installation at some point in time, a localhost installation is still the number one choice in a lot of testing scenarios.
Local CMS install
Let's start with installing a Ghost CMS on your local machine. The following instructions have been tested on a Linux system running on Fedora, but should work on other Linux flavors too, including MacOS.
If you are using Windows it is recommended to install the Windows Subsystem for Linux (WSL 2) and use Ubuntu 20.04 LTS.
Prerequisites
To install Ghost locally make sure you install Node.js, the package manager yarn and the ghost-cli
. Even if Node.js is already installed, check that you have a recent version:
$ node -v
v12.18.2
Node comes bundled with the package manager npm
. Make use of it right away and install yarn
and the ghost-cli
with it:
$ sudo npm -g install yarn ghost-cli@latest
Install Ghost
With the needed tools available, you can now create a new directory and install Ghost CMS in it:
$ mkdir ghost-local
$ cd ghost-local
Use the previously installed ghost-cli
for performing the actual install:
[ghost-local]$ ghost install local
It may take a minute to download and install all files. After a successful install, you can visit the CMS under http://localhost:2368/ghost/ and complete the setup process in three simple steps:
For a local install, you can safely enter a bogus email address and skip the staff user invite as you won't be able to work in teams anyway. After completing the final step, you should be brought to the Ghost Admin dashboard.
Create API keys
The initial setup is now complete. You can make further customizations in the admin interface, but this is not required at this point in time.
In other tutorials on Jamify.org you may read the recommendation to turn on headless mode. For a local installation this is not needed as the installation will never be available publicly, so you can safely skip this step.
In order to be able to connect to your Ghost instance with Jamify later, you need to generate a content API key. Go to Integrations -> Add custom integration
and click Create in order to generate a new key:
Starting and Stopping
The installation routine automatically starts the local Ghost server. You can stop it with the following command:
[ghost-local]$ ghost stop
You will also have to restart your server after every reboot:
[ghost-local]$ ghost start
If you want to check whether or not Ghost is currently running you can use this handy command:
$ ghost ls
Keep the current Ghost server running if you want to follow along with this tutorial.
Importing Content
If you are already running another Ghost instance, you may want to import your content to your local installation. The easiest way is to use the import/export functionality under the Labs section.
Unfortunately, images must be transferred manually. So, in addition to the above import/export for the textual content, copy all images in directory content/images/
from the source to the target Ghost instance.
Local Jamify install
With a local CMS install it also makes sense to generate your static site locally, in fact, you won't be able to build your site with a cloud provider because your localhost CMS is unreachable for them. Let's download the Jamify starter:
$ git clone https://github.com/styxlab/gatsby-starter-try-ghost.git jamify-starter
and change into the work directory:
$ cd jamify-starter
Adding Keys
The Jamify starter must be told to source in the CMS content from you Ghost CMS on localhost. For that, create a new file called .ghost.json
in your work directory and copy the previously generated content API keys in it:
{
"development": {
"apiUrl": "http://localhost:2368",
"contentApiKey": "2a087eea8fc3c9a3e7392625c0"
},
"production": {
"apiUrl": "http://localhost:2368",
"contentApiKey": "2a087eea8fc3c9a3e7392625c0"
}
}
Don't forget to replace the contentApiKey
with your own!
Remove members plugin
The Jamify starter includes some plugins for convenience, one of which is the gatsby-theme-ghost-members
plugin. As discussed earlier, the members plugin needs a public backend which currently must be a Ghost CMS, therefore the subscription flow won't work with a CMS on localhost. That's why you should remove this plugin:
[jamify-starter]$ yarn remove gatsby-theme-ghost-members
and also exclude it in your gatsby-config.js
:
// gatsby-config.js
plugins: [
//{
// resolve: `gatsby-theme-ghost-members`,
//},
]
The gatsby-theme-ghost-members
plugin could be enhanced to work with other backends or connect with serverless functions. If you want to contribute to this project, you are more than welcome to. Just let me know.
Add plugin for inline images
While feature and all meta images are included in the static bundle by default, all inline images within posts or pages are only included if you add the new gatsby-rehype-inline-images
plugin:
[jamify-starter]$ yarn add gatsby-rehype-inline-images
This is a sub-plugin of the gatsby-transformer-rehype
plugin and must be placed into your gatsby-config.js
as follows:
// gatsby-config.js
plugins: [
{
resolve: `gatsby-transformer-rehype`,
options: {
filter: node => (
node.internal.type === `GhostPost` ||
node.internal.type === `GhostPage`
),
plugins: [
{
resolve: `gatsby-rehype-ghost-links`,
},
{
resolve: `gatsby-rehype-prismjs`,
},
{
resolve: `gatsby-rehype-inline-images`,
},
],
},
},
]
Including this plugin may result in longer build times as all inline images must be downloaded. However, as you are downloading them from localhost you are not limited by network bandwidth.
The gatsby-rehype-inline-images
plugin also processes images, so they are lazy loaded and fade in with a nice blur-up effect known from Medium. See the plugin readme to learn more about the options this plugin provides.
Smoke test
You can now start your first test build with
[jamify-starter]$ yarn develop
and see if the build succeeds. Check the results on http://localhost:8000/. Next, add a new article in your Ghost CMS on localhost and press the Publish button. After that rebuild your project again:
[jamify-starter]$ yarn develop
and verify that the new article comes through:
Build your static site
While the development command is great for testing, you need to issue the build command to generate an optimized bundle that can be deployed to a CDN:
[jamify-starter]$ yarn build
Check that building succeeds without errors and start the build server:
[jamify-starter]$ gatsby serve
Visit your site at http://localhost:9000/ and verify that it looks exactly like in development mode.
Deploy to Netlify
Now its time to deploy your site to a global content delivery network (CDN). This step has been described in more detail in the getting started tutorial.
Here, I just repeat the simple steps. Once you have the Netlify CLI tool installed
$ sudo npm -g install netlify-cli
you can login with
[jamify-starter]$ netlify login
and deploy the previously build bundle from the public/
folder.
[jamify-starter]$ netlify deploy --prod
Answer the upcoming questions as follows:
Your site is manually deployed to Netlify, which means the generated bundle is uploaded and published.
This example was shortly available under https://eager-golick-336605.netlify.app. Yours should be on another subdomain. You can get a nicer subdomain, if you specify a site name above, however, it must be one that is currently not in use.
Congrats! 🎉 Believe it or not, your site is now running on a flaring fast CDN with zero operational costs. Plus, your site is SSL secured and enjoys all the Jamstack security benefits.
Custom Domain Names
Although optional, many people want to deploy their blog on their own custom domain. You can either purchase a new domain on Netlify or bring your own. A custom domain name will typically cost you around $1,5/month.
If you are a student, here is how you can get a free custom domain name! Sign up for GitHub Education. You may also get free custom domain names from name.com or namecheap.com.
Continuous Deployment
In another tutorial article, I showed how to set up a continuous deployment pipeline, where a content change in your CMS triggers a site rebuild which is then automatically propagated to your live site on the global CDN.
This setup heavily relies on online cloud services: your CMS must have a public endpoint that triggers a webhook on Gatsby Cloud which deploys the build to Netlify. With a CMS on localhost, this setup won't work anymore.
Webhooks on localhost
With a neat webhook open-source project from Adnan Hajdarević you can create a similar pipeline on localhost too. There are different methods available for installing this program, here I choose to download the webhook-linux-amd64.tar.gz
binary package, unpack the file and copy the executable in /usr/bin/
folder of my machine to make it globally available.
The webhook program consumes a hooks.json
definition file and spins-up a server on localhost. We need to define two actions:
- Build the jamify-starter with
yarn build
- Deploy the build package with
netlify deploy --prod
which can be combined in one script file called build-and-deploy.sh
that you should place in your work jamify-starter
directory.
#!/bin/sh
yarn clean
yarn build
netlify deploy --prod
Make this file executable with:
[jamify-starter]$ chmod +x build-and-deploy.sh
You can now create the hooks.json
in the same directory:
[
{
"id": "jamify-webhook",
"execute-command": "./build-and-deploy.sh",
"command-working-directory": "/home/styxlab/jamify-starter"
}
]
The working directory must be an absolute path, please change it to your own. You can now spin-up the webhook server:
[jamify-starter]$ webhook -hooks hooks.json -verbose -port 7000
Configuration is now complete. Test it out by triggering the webhook with curl
$ curl -X POST http://0.0.0.0:7000/hooks/jamify-webhook
Connect to CMS
You can add the webhook easily to your CMS on localhost, so it get's triggered whenever you make changes to your content:
Test your pipeline
Let's make a simple content change. For this test, I change the title of the first post and also exchange the feature image with another one from Unsplash. Make sure to hit the Update button after making these changes.
After the build has completed, your live site should then be automatically updated with the new content changes.
Summary
If you do not need member functions and mostly write your articles yourself, the described setup may be exactly what you need for running an up-to-date, super-fast, SSL-secured, shell-proof public blog site with 99.99% uptime at zero costs 🥳.
While this article focuses on the operating costs of a live blog site, it is worth mentioning that this solution makes blogging not only more affordable, it also makes blogging more accessible: The install process is much simpler, so that it is feasible for people who feel discouraged by traditional self-hosting solutions.
Furthermore, taking a public CMS server out of the equation will certainly reduce complexity, ongoing maintenance efforts and let your mind put at easy: without a server, you cannot be hacked.
Do you want early access to Blogody, the brand new blogging platform that I am creating? Just sign-up on the new Blogody landing page and be among the first to get notified!