This article is largely inspired by https://www.clever-cloud.com/blog/features/2019/10/21/ghost-hosting-clever-cloud/

Photo by Steinar Engeland on Unsplash

We are Kalvad, a tech company, and we like to blog about what we do. Bad ponit: we don't like any blog engine, except ghost. WAT? Ghost is in their own words:

A fully open source, adaptable platform for building and running a modern online publication. We power blogs, magazines and journalists from Zappos to Sky News.

Second part: we don like to manage server, we prefer to let some people deal about it for us, a very competent team, named Clever Cloud. So we need to make it work for their platform.

Your local blog installation

The first thing to do is create your blog locally. Ghost comes with a CLI tool you can install with npm or yarn. Simply create a folder, change directory:

  1. mkdir myblog
  2. cd myblog

Then you should create a file package.json with the following content:

{
    "name": "ghost-clever-cloud",
    "version": "0.1.0",
    "description": "",
    "scripts": {
        "start": "ghost run --dir ghost"
    },
    "devDependencies": {},
    "dependencies": {
        "ghost-cli": "1.13.1",
        "ghost-storage-adapter-s3": "2.8.0"
    }
}

To finish:

npm install
/node-modules/.bin/ghost install local

You should see some logs and right at the end an invitation to go to http://localhost:2368/ghost/. This is your new blog. You should go through a configuration wizard to setup everything. It's using the development configuration right now, with a local SQL lite database and logs written to disk. Now you can stop the local blog by running ./node-modules/.bin/ghost stop.

If you list the content of your directory you should see the following:

$ ls
content/  [email protected]  versions/  config.development.json

All the specifics of your blog are stored in content. current is a symbolic link to the latest Ghost release downloaded in the versions folder. There is another symbolic link in content/themes/casper that points to the latest capser theme built in Ghost. This link will likely be broken once the blog is pushed to Clever Cloud because of the way we will install Ghost on it. So what you can do right now is replace that link with the actual content of the theme, and add a couple of other themes for good measure. It's also the time to create our git repository. Because the new themes are going to be git submodules. This way it can easily be updated, while controlling the version.

git init
cd content/themes/
git submodule add https://github.com/TryGhost/Casper.git casper

To test the new theme let's start Ghost in debug mode: ghost run -D. If you go back to the admin page under http://localhost:2368/ghost/, click on the Design tab and you will see your new installed themes at the end of the page. Once you are done you can stop the server by simply hitting Ctrl-c.

Now you have a running installation locally, let's see how to move it to the Cloud.

Your blog on Clever Cloud

To interact with Clever Cloud I recommend using our wonderful CLI clever-tools available easily on most distributions as you can see in our documentation. This is the tool I will use in the example. You can of course do the same thing using Clever Cloud's Web Console.

In a terminal, inside your blog folder, run the following command:

clever create --type node myblog # create a node application
clever addon create mysql-addon --plan s_sml myblogsql # create a MySQL database
clever service link-addon myblogsql # Link the database to the application
clever addon create cellar-addon --plan S myblogstorage
clever service link-addon myblogstorage
clever env set NODE_ENV production # setup environment variable for production
clever env set CC_POST_BUILD_HOOK clevercloud-post-build-hook.sh # run a specific script before the build phase
clever env set CC_PRE_RUN_HOOK clevercloud-pre-run-hook.sh # run a specific script before the build phase
clever env set SMTP_FROM <email from>
clever env set SMTP_SERVER <smtp server>
clever env set SMTP_PORT 465
clever env set SMTP_USER <smtp user>
clever env set SMTP_PASSWORD <smtp password>
clever env set url https://mysuperblog.cleverapps.io/
Clever env set BUCKET_NAME <bucket name on Clever Cloud Cellar> #don’t forget to create the bucket on clever cloud

Now there is an existing application and a database on Clever Cloud, configured to run Ghost. But then we still need to create a bunch of files.

Installing Ghost for your Clever Cloud Application

You have seen in the previous step an environment variable pointing to a post build hook. It allows us to run arbitrary code before or after the build and run phases. So let's create that executable file with touch clevercloud-post-build-hook.sh and chmod +x clevercloud-post-build-hook.sh at the root of our repository. And as for the content here it is:

#!/bin/sh
set -e
set -x

# Add S3 Storage
mkdir -p ./content/adapters/storage
cp -r ./node_modules/ghost-storage-adapter-s3 ./content/adapters/storage/s3
mkdir ghost # create a folder for a new local instance of Ghost
cd ghost
../node_modules/.bin/ghost install local
../node_modules/.bin/ghost stop
# generate production config file
# generate config file
cd ..
./clevercloud-pre-run-hook.sh

Then let's create another executable file with touch clevercloud-pre-run-hook.sh and chmod +x clevercloud-pre-run-hook.sh at the root of our repository. And as for the content here it is:

#!/bin/sh
set -e
set -x
cat <<EOF > ghost/config.production.json
{
    "server": {
        "port": 8080,
        "host": "0.0.0.0"
    },
    "storage": {
        "active": "s3",
        "s3": {
            "accessKeyId": "$CELLAR_ADDON_KEY_ID",
            "secretAccessKey": "$CELLAR_ADDON_KEY_SECRET",
            "region": "US",
            "bucket": "$BUCKET_NAME",
            "assetHost": "https://$BUCKET_NAME.$CELLAR_ADDON_HOST",
            "endpoint": "$CELLAR_ADDON_HOST"
        }
    },
    "database": {
        "client": "mysql",
        "connection": {
            "host": "$MYSQL_ADDON_HOST",
            "port": $MYSQL_ADDON_PORT,
            "user": "$MYSQL_ADDON_USER",
            "password": "$MYSQL_ADDON_PASSWORD",
            "database": "$MYSQL_ADDON_DB"
        },
        "pool": {
            "min": 2,
            "max": 50
        }
    },
    "mail": {
        "transport": "SMTP",
        "from": "$SMTP_FROM",
        "options": {
            "service": "Mailgun",
            "host": "$SMTP_SERVER",
            "port": $SMTP_PORT,
            "secureConnection": true,
            "auth": {
                "user": "$SMTP_USER",
                "pass": "$SMTP_PASSWORD"
            }
        }
    },
    "process": "local",
    "logging": {
        "level": "debug",
        "transports": [
            "stdout"
        ]
    },
    "paths": {
        "contentPath": "../../../content/"
    }
}
EOF

After that script we will have installed ghost-cli and the latest version of Ghost available. This script can be modified to have a specific version installed if you wanted to. But the thing is I like using upstream code: fail early, fail often.

Ignoring local development files

There are a bunch of files we should not add to our git repository. Basically all the files related to our local deployment. So I added them to a .gitignore file and invite you to do the same.

.ghost-cli
config.development.json
current
Versions
ghost

Git push to Production

Now you should have a fairly simple git add to do:

git add clevercloud-post-build-hook.sh clevercloud-pre-run-hook.sh package.json package-lock.json content
git commit -m "Initial commit"
clever deploy

If you go to https://ghost-v3.cleverapps.io/ghost/ you will run through the setup again, but this time storing things in the MySQL database. Take a look at the configuration options for Ghost, there are still a bunch of things you can do like setting up a proper email SMTP server.

And what about the traditional Production configuration from Ghost's documentaiton like SSL Configuration, reverse proxy and such? Well the beauty of using Clever Cloud is that everything is taken care of for you. Nothing else to do.

And there you have it, your new blog is online. Deployed the immutable way.