Hosting a Ghost blog on AWS Beanstalk with Docker

A few weeks ago I decided to use Ghost for my blog. Ghost is Open Source and free to download. But where should you host it? Ghost.org offers hosting from $19/month. You can see what you get compared to when you choose to host Ghost yourself in their comparison chart. Thanks to Amazon Web Services Free Tier I decided to go with AWS Elastic Beanstalk, at least for the first year. If it weren't for Free Tier I would probably pay about $20-$30 for the Ghost setup on AWS, described in this post.

Elastic Beanstalk

I had two options for deploying Ghost on Beanstalk - download Ghost and then deploy it as a Node.js app or use Docker. I've been using Docker on Beanstalk before, and I think it's always been very easy to get up and running. It's also easy to try it out locally as you don't really need to know anything about Node.js or the rest of the environment and other prerequisites. All you need is Docker. Another benefit is that it's super simple to update Ghost.

Docker

So, I decided to go with Docker. I found that there were quite a few Ghost images. I think it's always best to go with the official image when there is one, unless having strong reasons not to. In this case, the official image, was not perfect, but still good enough. As stated in the description of ptimof/ghost

The official container for Ghost is fine for running in development mode, but it has the wrong permissions for running in production. That, and the config file doesn't have any easy way to tweak
it.

I totally agree with this statement, but we'll do just fine anyway with a minor workaround.

Getting started locally

If you have Docker, then it's as simple as

docker run -p 8080:2368 ghost  

to get a Ghost (with default settings) up and running on http://localhost:8080. You can actually start blogging locally, because Ghost can export and import your content (beta feature).

Add a custom theme

If you want to, you can create your own or customize an existing theme, instead of the default Casper theme. In this tutorial I will not get into details, on how to create a theme, but I will show how to add Casper with a small change. With this knowledge, you are able to add comments to your blog.

Create a new directory for your blog and clone Casper:

git clone https://github.com/TryGhost/Casper.git  

Edit Casper/package.json and change name to Modified Casper and version to 1.0.0. Also edit Casper/post.hbs and make any noticeable change to verify the theme. In my case I added Content goes here: before {{content}}:

        <section class="post-content">
            Content goes here:
            {{content}}
        </section>

Configure Ghost

To configure our blog we will start with the default config file. Save this file to your blog directory. If you take a look at the file, you'll notice that development is the default environment. The environment can for example be changed to testing by adding -e NODE_ENV=testing to the docker run command.

A custom Docker image

To use the custom theme and config, we will create a new Docker image. This is simply done with the following Dockerfile:

FROM ghost

ADD Casper /var/lib/ghost/themes/Casper  
ADD config.example.js config.example.js

EXPOSE 2368  

To build it just run

docker build -t myghost .  

and then run it with

docker run -d -p 8080:2368 myghost  

To shut down and remove all containers:

docker kill `docker ps -q`;docker rm `docker ps -aq`  

Deploy to Beanstalk

As this post is not a Beanstalk tutorial, I will not get to deep into the details of creating a Beanstalk application and environment, but I will state the most important options that you will need to select.

Create the source bundle

To deploy a Docker app to Beanstalk, you need to create the file Dockerrun.aws.json. In this file you can specify the Docker image to use, volumes shared with the host, and so on, but for our Ghost blog, it can be minimal:

{
  "AWSEBDockerrunVersion": "1",
  "Ports": [
    {
      "ContainerPort": "2368"
    }
  ]
}

Now we have all we need to create the source bundle:

zip -FSr ghost.zip Dockerfile Dockerrun.aws.json config.example.js Casper -x *.git*  

The -FS option to zip is to synchronize any changed files in the archive. As you will most likely use this command a lot, I recommend saving it to a file, eg. build.sh.

If you at some point need a more advanced Beanstalk configuration of the instances, that is done by adding .config files to a directory called .ebextensions, which also needs to be included in the bundle. This way you can for example force https, but let's skip that for now.

Create the Beanstalk application and environment

In the AWS console, select Beanstalk and Create New Application. When you have selected a name it's time to create a new environment. Create a new Web Server Environment and choose Docker as platform. Also select Load balancing, auto scaling to get an ELB load balancer. It's not the most cost effective option, but once again thanks to Free Tier it will not generate any additional cost (the first year), and it's actually a bit simpler to setup a certificate in this way.

On next page, choose Upload your own as source, and upload the source bundle ghost.zip.

For the rest of the setup wizard, you are free to choose the values as you see fit, except that you should check that you want to create a RDS DB Instance. Also remember to select an instance type that is eligible Free Tier.

The RDS DB configuration will need an engine, username and password. Select mysql as engine, a username as you see fit, and I recommend you to generate a strong password. You will not type it anyway, so there is no reason not to make it really strong.

When you have reviewed the configuration and the deploy has finished, the Health should hopefully transition to Ok. Browse to the environment URL, which you can find in the top of the Dashboard. If everything has went well, you should be able to access your new blog, hosted on Beanstalk! But don't do too much until...

Configure Ghost to use the RDS DB

Your blog seems to work perfectly fine, and yes it does, but if the EC2 instance gets terminated, all changes will be gone, because as you can see in config.example.js, SQLite is going to be used by default, and the data will be written to the EC2 volume.

Check the RDS properties in the AWS console and change the database settings to:

database: {  
    client: 'mysql',
    connection: {
        host: <ENDPOINT>,
        user: <USERNAME>,
        password: <PASSWORD>,
        database: 'ebdb',
        charset: 'utf8'
    }
}

Then you need to create a new source bundle and deploy to your Beanstalk environment once more. If everything goes well, you will soon be able to access your blog again, and now all changes will be stored in the RDS DB and persist even when your EC2 instance gets terminated for whatever reason.

Now everything is ready to create your user and start blogging. I hope you'll enjoy your new blog!