Hosting Ghost on PikaPods + Cloudflare

When I rebuilt my website, I wanted to use something simple that allowed me to easily publish my thoughts without a lot of technical work. Sure, I could have built it myself, but that's time I didn't want to spend.

Fortunately, I found Ghost. To get started quickly, I used Ghost's own paid hosting service. Honestly, it's great, but at $31 a month it always felt a bit pricey. Since Ghost is open source, I looked for ways to host it myself that didn't add a bunch of overhead I was trying to avoid.

Turns out there are some great alternatives, such as Magic Pages, that are dedicated for Ghost. However, to save cost, I wound up going with PikaPods. They have a bunch of open source apps that you can spin up with a click, including Ghost. All of this for around $2 a month? Perfect.

Here's how I set everything up, using Cloudflare for my DNS provider.

Setting up PikaPods

Add a new pod, selecting Ghost as your app. Name the pod whatever you'd like.

Ghost requires some minimum resources in terms of CPU and memory, so make sure you set those appropriately. My site is basic, so I'm using half of a CPU core, 1 GB of RAM, and 10 GB of storage.

Once everything is set up, you will be assigned a random pod domain name, such as example.pikapod.net. You can rename that if you'd like, but make sure to jot it down as you'll need it in the next step.

Setting up Cloudflare DNS

I've heard over the years that hosting your website at the www subdomain is useful for search engines, so that's what I usually do. I also wanted anyone who visited timcheadle.com to be redirected to www.timcheadle.com

In order to make this work, I had to set Cloudflare DNS up in a very specific way.

In the DNS interface for your domain in Cloudflare, we need to add a CNAME record for our root domain. Note that it's important that proxy is enabled for this domain, as we need Cloudflare to be able to redirect it (in a later step).

Type: CNAME
Name: @
Target: www.timcheadle.com
Proxy status: Proxied
TTL: Auto

We also need to add a CNAME record for the www subdomain. It's important that proxying is disabled for the subdomain, as it will break SSL certificates with PikaPods.

Type: CNAME
Name: www
Target: example.pikapod.net
Proxy status: DNS Only
TTL: Auto

When you're all done, it should look something like this:

Add custom domain to PikaPods

Once those records are in place, you need to set up a custom domain with your pod. Fortunately it's dead simple. Click the gear icon in your pod admin, go to Domain, and add your full domain name, such as www.timcheadle.com

Redirecting the root domain to www

The last step is making sure any traffic that goes to your your root domain is redirected to the www subdomain. Cloudflare has a super extensive rules engine that can be intimidating, but fortunately our example is pretty simple.

In Cloudflare's navigation, go to Rules, then Redirect Rules. Create a new rule, give it a description such as "Redirect to www". You want a "Custom filter expression" that matches the hostname and redirects, like this:

If:
    Hostname equals timcheadle.com
Then:
    URL redirect
    Type: Dynamic
    Expression: concat("https://www.timcheadle.com", http.request.uri.path)
    Status code: 301
    Preserve query string: true

The "dynamic" redirect will make sure any path names stay in tact, so someone that visits timcheadle.com/about/ will be redirected to www.timcheadle.com/about/

It should look like this when you're done:

Click save, wait a few minutes, then everything should be working!

Testing with curl

Just to ensure things are sorted, I like to use curl to make sure the URLs are responding with what we expect. Open a terminal window and check the headers.

For the root domain, we should get an HTTP 301 code with the redirect:

➜  curl -I https://timcheadle.com/
HTTP/2 301
date: Wed, 07 Aug 2024 16:02:14 GMT
content-type: text/html
content-length: 167
location: https://www.timcheadle.com/
cache-control: max-age=3600
expires: Wed, 07 Aug 2024 17:02:14 GMT
report-to: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=z0oRn4Utoqih5VMsbTf0iYLnbuDXNnxtvihruTNDXT7Si0GFtgCqRfiI182U5S5mbCbpteScguzKiTGtLZjpw9zjM4%2FR8VJUOG9gEmjJKsMJ6BPpsuhoPMtN0jR7%2Bi%2BOIQ%3D%3D"}],"group":"cf-nel","max_age":604800}
nel: {"success_fraction":0,"report_to":"cf-nel","max_age":604800}
server: cloudflare
cf-ray: 8af87d68e8b2bd3a-ATL

For the www subdomain, we should get an HTTP 200 with the page itself:

➜  curl -I https://www.timcheadle.com/
HTTP/2 200
alt-svc: h3=":443"; ma=2592000
cache-control: public, max-age=0
content-type: text/html; charset=utf-8
date: Wed, 07 Aug 2024 16:14:42 GMT
etag: W/"17873-Mt340fW+6Mk06cdXFfZcmdIEj44"
server: Caddy
strict-transport-security: max-age=31536000;
vary: Accept-Encoding
x-powered-by: Express
content-length: 96371

Hope this helps! Please contact me if you have any questions.