Host a Ghost 5.0 Blog for Free on Fly.io using Docker and Amazon S3 storage

I found a few great articles on how to host Ghost using Fly.io but I also wanted to use external storage to manage the images instead of the default local storage which is difficult to scale or move later.

Using Docker directly you could also use any other extra packages supported by Ghost as well.

Installing Fly.io

You probably have fly.io installed already but if not it's amazingly straightforward, so much so that I won't cover it here.

Installing flyctl · Fly Docs
Documentation and guides from the team at Fly.io.

Getting started

First I just wanted to keep it tidy by creating a local directory for a few files to keep track of, by default you only need one .toml file but to customize the install and add a few things I'll be using Dockerfile instead of an image.

# create a folder
mkdir ghost-flyio && cd ghost-flyio

# creates the .toml file but don't deploy it yet, i'm specifying the image but we'll remove this in the next step, probably you can leave it out altogether.
flyctl launch --name ghost-flyio --image=ghost:5-alpine --region dfw --no-deploy

# create the local storage, by default it uses sqlite which is just fine for me, but you could change this as well
flyctl volumes create data --region dfw --size 1

# Create a blank Dockerfile
touch Dockerfile

Creating the Dockerfile

FROM ghost:5.2.3-alpine

# install S3
RUN npm install -g ghost-storage-adapter-s3 && \
  ln -s /usr/local/lib/node_modules/ghost-storage-adapter-s3 ./current/core/server/adapters/storage/s3

That's it.  Normally the .toml file will include the image inside it, but if you add a Dockerfile in the same directory it will use it.  Now that we're using Docker directly, we could also add any other optional packages supported by Ghost, for example, you could use another external storage other than S3, such as Google storage.

A couple of changes to make Ghost work

The defaults are mostly correct however there are two changes you need to make to get Ghost to work correctly.

  • You need to change the default internal port from 8080 to 2368
  • You need to mount the drive we created previously
  • Add a few optional ENV variables
# fly.toml file generated for ghost-flyio on 2022-06-21T19:04:29+02:00

app = "ghost-flyio"
kill_signal = "SIGINT"
kill_timeout = 5
processes = []

# add this, also be sure and remove the [img] section to ensure it uses docker
[env]
  url = "https://ghost-flyio.fly.dev"
  storage__active = "s3"
  storage__s3__acl = "public-read"

[experimental]
  allowed_public_ports = []
  auto_rollback = true

# add the mounted storage
[mounts]
  destination = "/var/lib/ghost/content"
  source = "data"

[[services]]
  http_checks = []
  internal_port = 2368 # change this
  processes = ["app"]
  protocol = "tcp"
  script_checks = []
  [services.concurrency]
    hard_limit = 25
    soft_limit = 20
    type = "connections"

  [[services.ports]]
    force_https = true
    handlers = ["http"]
    port = 80

  [[services.ports]]
    handlers = ["tls", "http"]
    port = 443

  [[services.tcp_checks]]
    grace_period = "1s"
    interval = "15s"
    restart_limit = 0
    timeout = "2s"

At this point, you can go ahead and launch your app now and check that it works. It won't use S3 quite yet, but it will work (images might not as we add S3 in the settings, but if you remove this local storage will work fine to test with).

Adding your S3 credentials

You could add these to your .toml file which would probably be fine except if you end up committing your information somewhere, the better practice is to use Fly.io's secrets.  I should mention that they have different variable names.

I won't cover setting up the S3 bucket here, but you'll need to do this correctly for it to work correctly.  You could also fairly easily use any other external method that Ghost supports and the process would be more or less the same.

flyctl secrets set AWS_ACCESS_KEY_ID=YOUR-ID-HERE
flyctl secrets set AWS_SECRET_ACCESS_KEY=YOUR-KEY-HERE
flyctl secrets set AWS_DEFAULT_REGION=us-east-2

# optional
flyctl secrets set GHOST_STORAGE_ADAPTER_S3_PATH_BUCKET=cdn-ghost.yourdomain.com

Add your domain

You probably don't want to use

flyctl certs create blog.example.com

This step takes a couple of minutes.  It will generate two IP addresses for you, IP4 & IP6 which you can point to using Cloudflare or any other service.  You could also use the CNAME but since they provide the IP option this seems like a better route.

References

Host a Ghost 5.0 Blog for Free on Fly.io — In 1 Minute
Ghost is one of the fastest-growing publishing platforms. However, for those looking to dip their toes in the water, one aspect can be off-putting: the shiny Ghost(Pro) hosting (referral link) starts at $11/month or $108/year — $300/year a year if you want to use your own theme