Part 1 - Ghost Installation

Friends don't let friends install Wordpress.

If you're looking for the best way to publish your or  your organization's content, several factors need to be taken into consideration: what you would like to accomplish, your budget, your experience with publishing platforms, and ability to configure and customize the existing tools out there. If you'd like to avoid the technical dumpster fire that is Wordpress or the careless Wordpress hosting ecosystem littered with stability and security issues, you are already likely looking into alternatives. Ghost, a self-described 'fully open source, adaptable platform for building and running a modern online publication' is one such alternative that might pleasantly surprise you. They have a ghost hosting option which looks very compelling, and with a small amount of technical understanding it is easy to self-host. In this article, I'll explain how to set up Ghost on Amazon Web Services, the largest cloud provider. I'll show you how to minimize first year costs utilizing mostly AWS free tier components. We'll be doing all of this using the Cloudformation template I developed to create this site, which is located here.

Let's get started!

Create an AWS Account and Prepare Credentials

Our first step in preparation is to get your AWS account and credentials set up so that we can deploy Ghost.

  1. Account Setup

First we'll need to get your account itself.

Log into your AWS Account. If you don't already have an account, you will need to create a free tier account. At minimum, Ghost requires a single server with 1GB of memory, which is exactly what the free tier account allows in continuous (1 EC2 t2.micro) instance usage. The free tier also includes database (1 RDS t2.micro) instance usage for a year which we will be utilizing. You'll be able to run your blog free for a year, after which the on-demand pricing will increase based on the region your server and database are located in to around $15 a month.

  1. Deploy User Creation

Now that we have your account set up, we need to create a user which will be used to deploy the cloudformation template.

After signing in to the AWS console, locate the Find Services search box and type in IAM. Once in the IAM console, click Add User to add a user.

Adding an IAM User

For the User name, enter ghost-deployer. Select the Programmatic access checkbox only as we will be using the API to deploy infrastructure only with these credentials. Click Next: Permissions.

  1. Policy Selection and Attachment

Now that we have a user created to deploy everything, we need to give that user the permissions required to create what is required for Ghost.

Set Permissions

In the Set permissions step, select Attach existing policies directly. In the Filter policies search box, type in adminstrator. Of the results, select the top policy: 'AdministratorAccess'. This will effectively give this user we are creating for the sole purpose of deploying ghost administrator access. This could be fine-tuned later to only include the IAM policy permissions required to perform the operations required in the deployment process, but for now we are keeping it general. Click Next: Tags.

  1. Finalize Setup and Review Choices

In the tags page, skip adding tags for now. Click Next: Review.

Review Choices

This will bring you to a review page. Validate the choices that have been made, and then Create user.

  1. User Creation Success Confirmation
    This will create the user, with a success message being displayed. It is on this screen that you need to download the credentials we will be using later to deploy the server.

Download credentials .csv file

Click Download .csv, which will download a small .csv file containing the credentials we need to continue.

Install and Configure the AWS CLI

Now that we have our AWS account set up, we now need to get your laptop or desktop computer ready configured to use the Command Line Interface (CLI) so that we can more easily run the code that sets up what we need in AWS to run Ghost.

  1. Follow the instructions for the platform you are using in order to get the AWS CLI up and running: Mac, Windows, or Linux. Note that with Mac, if you already have homebrew installed you can install it by typing
$ brew install awscli
  1. Now that we have the AWS CLI installed, we need to configure it to use the credentials we generated above. Open a command prompt or terminal and type aws configure. This will prompt you to enter credentials for the user we created, which we downloaded in the .csv file. Locate this file and open it with a text editor, and then enter the appropriate credentials as listed in the file, replacing the example Access Key ID and AWS Secret Access Key below with the values in your file. For the default region name, type in the coded designation for the region closest to you. All current regions are listed here. For 'Default output format', type in json.
$ aws configure
AWS Access Key ID [None]: <enter your access key acquired above>
AWS Secret Access Key [None]: <enter your secret key acquired above>
Default region name [None]: us-east-1
Default output format [None]: json

Once you run this command, it will write a file containing your credentials which is located at ~/.aws/config on Linux, macOS, or Unix, or at C:\Users\ USERNAME \.aws\config on Windows. Once configured, you should be able to issue aws commands without error. Here we get the version, list all cloudformation stacks in the us-east-1 region, and bring up the help message which demonstrates how to get help on any specific command

$ aws --version
aws-cli/1.14.30 Python/3.6.4 Darwin/18.2.0 botocore/1.8.34
$ aws cloudformation list-stacks --region us-east-1
{
    "StackSummaries": []
}
$ aws
usage: aws [options] <command> <subcommand> [<subcommand> ...] [parameters]
To see help text, you can run:

  aws help
  aws <command> help
  aws <command> <subcommand> help
aws: error: the following arguments are required: command

We are now ready to move on to getting our server deployed and configured!

Download and Configure the Ghost Cloudformation Template

  1. Download the repository using git

To get our source template required to deploy the cloudformation template, use the git source version control system to pull down the code here

$ git clone https://github.com/projectscribe/ghost-cloudformation.git
Cloning into 'ghost-cloudformation'...
remote: Enumerating objects: 7, done.
remote: Counting objects: 100% (7/7), done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 7 (delta 1), reused 4 (delta 1), pack-reused 0
Unpacking objects: 100% (7/7), done.
$ cd ghost-cloudformation

If you do not have Git installed, very good instructions can be found here on how to install it). If you do not want to install git, you can just download a zipfile here

  1. Open up Template Configuration File to Customize our Install

In a text editor, open parameters.json which is in the directory we just changed into of the repository just downloaded. This file defines Cloudformation parameters, which are the inputs into the template which are used like constants to configure the resources and scripts that define Cloudformation resources. It is an ordinary JSON document that is an array of key/value objects, each being a parameter. We will be editing these parameters as we go, so keep the editor open.

[
  {
    "ParameterKey": "KeyName",
    "ParameterValue": "ghost-deploy"
  },
  {
    "ParameterKey": "InstanceType",
    "ParameterValue": "t2.micro"
  },
  {
    "ParameterKey": "DbName",
    "ParameterValue": "ghost"
  },
  {
    "ParameterKey": "DbUser",
    "ParameterValue": "ghostuser"
  },
  {
    "ParameterKey": "DbPassword",
    "ParameterValue": "<your-instance-db-password>"
  },
  {
    "ParameterKey": "DbRootPassword",
    "ParameterValue": "<your-rds-database-master-password>"
  },
  {
    "ParameterKey": "SSHLocation",
    "ParameterValue": "0.0.0.0/0"
  },
  {
    "ParameterKey": "FromAddress",
    "ParameterValue": "<email-address-approved-as-SES-sender>"
  },
  {
    "ParameterKey": "DomainName",
    "ParameterValue": "<domain-name-used-in-route-53-hosted-zone>"
  },
  {
    "ParameterKey": "SslEmail",
    "ParameterValue": "<the-letsencrypt-certificate-SSL-email-address>"
  },
  {
    "ParameterKey": "HostedZoneId",
    "ParameterValue": "<route-53-hosted-zoneid>"
  }
]
  1. Create an instance ssh key within the AWS console and put it in parameters.json

Bring up the AWS Web Console. Once you are in, click Services in the navbar and in the search bar that appears type EC2. Once in the EC2 configuration screen, follow the left nav link Key Pairs under Network and Security.

nav-to-keypairs

Once in the following page, click Create Key Pair

new-key-pair-button

And give the key pair a name that you will use in your configuration

create-key-pair-key-pair-name

Once you click Create, a .pem file is downloaded. Move this file to your .ssh directory in your home folder (or ~/.ssh), and ensure that it has the proper permissions. On OSX, this might look like the following, if I created a key called 'test-key'.

$ mv ~/Downloads/test-key.pem ~/.ssh
$ chmod 600 ~/.ssh/test-key.pem

Finally, we edit our parameters.json config file to reflect the name of our key that we will be using to access the Ghost instance. In this case, a key was created called ghost-deploy (resulting in a key called ghost-deploy.pem which we moved to our ~/.ssh config directory)

{
  "ParameterKey": "KeyName",
  "ParameterValue": "ghost-deploy"
},
  1. Configure Your RDS Credentials and SSH source CIDR

Ghost requires MySQL 5.7 as a database for the application. As previously mentioned, we are going to be using the AWS RDS (Relational Database Service), which is a service that you pay for by the hour which presents a database endpoint for your application to consume. This gives us benefits such as snapshots (automated point-in-time backups), while also freeing up resources on the Ghost server instance to run the application itself.

We need to feed our Cloudformation some parameters that specify credentials used to configure the RDS instance as well as the Ghost config for connecting to the database. We do this in parameters.json, which you should still have open in your editor. In it, set up your ghost database name (DbName), Ghost database app user (DbUser), and Ghost app user password (DbPassword). Finally, choose an RDS database master user password (DbRootPassword). All of these values are configurable, so choose a meaningful DbUser name and make strong passwords. These will be used in the cloudformation template to configure the credentials required by Ghost.

{
    "ParameterKey": "DbName",
    "ParameterValue": "ghost"
  },
  {
    "ParameterKey": "DbUser",
    "ParameterValue": "ghostuser"
  },
  {
    "ParameterKey": "DbPassword",
    "ParameterValue": "<your-instance-db-password>"
  },
  {
    "ParameterKey": "DbRootPassword",
    "ParameterValue": "<your-rds-database-master-password>"
  }

(restrict SSH Access - optional)

If you'd like to restrict SSH access to your Ghost server, enter a network CIDR which is specific to the network you will access Ghost from. If you leave the default 0.0.0.0/0 value, you will be able to SSH to your ghost server from any public network.

  {
    "ParameterKey": "SSHLocation",
    "ParameterValue": "0.0.0.0/0"
  }
  1. Set up an SES Email for Ghost System Mail Delivery

Now we need to set up an e-mail address for SES, the AWS Simple Email Service, which is required to send mail through their SMTP (Simple Mail Transport Protocol) gateway. This is the e-mail address that Ghost will send mail as. Amazon requires a verification process to ensure that mail sent via their service using an e-mail address is in fact owned.

Back in the AWS Console, Click Services, and type in Simple Email Service in the search box. Navigate to this using the dropdown that appears.

nav-to-ses

Once in the SES Screen, navigate on the left to Email Addresses, and then click the Verify New Email button.

email-addresses-and-verify-new-email-button

Next, fill in the modal dialog that appears with the e-mail that you'd like to use SES to send e-mail on behalf of. It needs to be an actual e-mail address that you have control of and are able to verify by response. Click Verify E-mail Address.

verify-new-email-address-dialog

After this, a message is displayed confirming that a verification e-mail has been sent to the address you used. Click close, and check your e-mail from the account that you used. In the e-mail are instructions to click a link to verify the address.

verification-email-confirmation

Once you click the verification e-mail, go back into the AWS Console SES Email Addresses section to validate the status of the e-mail address you just confirmed. It should be listed with Verification Status Verified.

Now that we have configured SES to allow the usage of this e-mail, we are ready to configure our CFT to use it in the configuration of Ghost. Back in our parameters.json file, edit the following section to reflect this e-mail address.

  {
    "ParameterKey": "FromAddress",
    "ParameterValue": "<email-address-approved-as-SES-sender>"
  }
  1. Set Up Your Domain Name in Route 53

To have an externally accessible Ghost install, you need a domain name. We are going to use AWS as a registrar and register a domain through them such that we have it available to programmatically create and delete records using Route 53, their Domain Name API. This will allow us to use cloudformation to point a domain name to our newly created server's attached Elastic IP address (EIP).

*Note: if you would rather use an existing domain that you own that you registered with another registrar and want to transfer it to Route 53, follow this process. Alternatively, if you want to keep that domain with your current registrar but want to make Route53 the DNS Service for that domain, do the following.

In the AWS Console, click Services, and in the dropdown search input box type Route 53. Follow the autocomplete link to the Route 53 service, which is an AWS service for controlling and managing domains that you own.

nav-to-route-53

In the Route 53 Console, toward the right side of the screen is a section for registering a new domain name. Click Register Domain.

domain-registration

Within the domain registration, choose the domain name that you would like to use for your new Ghost site. Type something into the input box, select a suffix, and then click Check to see if it is available. Once you have settled upon what you'd like that is available, click Add to Cart. You will then be prompted to select the duration that you'd like to register it for, after which you can scroll down and click the Continue button.

choose-a-domain-name

In the following screen, fill in the primary contact information for the domain. Here you can indicate that the Registrant, Administrative and Technical contacts are all the same, or split them out to separate organizational contacts if that is more appropriate for your site. Ensure that the Privacy Protection radio button is set to Enable and click Continue.

After filling in your contact information, you are presented with a confirmation page. After verifying the information you have entered, agree to the Terms and Conditions by clicking the radio box. Finally, Follow the instructions to verify the contact e-mail address you provided in the previous step. You will need to check those e-mail accounts and click a verification link. Once this is done you can verify this by clicking Refresh Status, and click Complete Purchase to finalize the domain purchse. Once this is done, you will have the domain available to you in Route 53 as a hosted zone resource is created for you automatically.

Back in the Route 53 Console, navigate on the left to Hosted Zones.

hosted-zones

Here you will see an entry from the domain name you just registered. On the right column there is a value called Hosted Zone ID. Copy this, and enter it in the appropriate place in your parameters.json file. While you are at it, enter the DomainName that you just registered (name+suffix, e.g. radghostsite.com), and the e-mail address that you'd like to be listed in the SSL certificate that will be configured for this domain. This needs to be a valid e-mail: you can always use the same e-mail here as was used previously for sending mail from the site.

{
    "ParameterKey": "DomainName",
    "ParameterValue": "<domain-name-used-in-route-53-hosted-zone>"
  },
  {
    "ParameterKey": "SslEmail",
    "ParameterValue": "<the-letsencrypt-certificate-SSL-email-address>"
  },
  {
    "ParameterKey": "HostedZoneId",
    "ParameterValue": "<route-53-hosted-zoneid>"
  }

We should now have parameters.json into the shape that we want it in. Save the file from your editor.

Deploy the Cloudformation Template to AWS

Now that we have all of the parameters which will be used as inputs into our cloudformation template, we are ready to kick off its deployment. This is where all of the resources specified within our template are actually created.

Back at the command line, we are now prepared to kick off our Cloudformation template. We will be using the aws command line to create a stack, which is the name given to each invocation of the cloudformation template. If we wanted two ghost installs we'd execute this twice with different parameters, which would result in two separate stacks running Ghost. In order to kick this off, execute the following

$ aws cloudformation create-stack --stack-name ghost --region us-east-1 --template-body file://./ghost.template --parameters file://./parameters.json --capabilities CAPABILITY_NAMED_IAM

Here we are evoking the AWS CLI cloudformation service create-stack command, which kicks off a deploy of all of the infrastructure components in the ghost.template template that is specified in the --template-body option. We specify the us-east-1 region, which can be changed to reflect your desired region. The --parameters options specifies the parameters.json file that we just saved that contains all of the parameters we are passing in to the cloudformation template. Finally, we are creating some permissions in our cloudformation file, so the --capabilities option must be set with CAPABILITY_NAMED_IAM.

Note: if you want to create multiple stacks using the same template, be sure that you use a different --stack-name argument as the stack name needs to be unique.

Once you execute this command, navigate back to the AWS console. Click Service and type in Cloudformation, clicking on the autocomplete link that appears below the input box to navigate to the Cloudformation Console.

cloudformation-nav

Once in the Cloudformation Console, you are presented with a list of your stacks, which should only have your entry for our Ghost stack which is being created. Click on the entry for this stack.

cloudformation-stack-list

Once in your new stack details, scroll down to the Events section. Expanding this, you can see each AWS component provisioned to support the functioning of your new Ghost site. We will walk through each of these components part 2 of this series, but for now note the status of each: they move from CREATE_IN_PROGRESS when being created, but then CREATE_COMPLETE once they are ready and available. If for some reason the process runs into a problem, you will see ROLLBACK and DELETE_IN_PROGRESS statuses.

stack-detail

If everything went well with your stack creation, there is a final message that the AWS::CloudFormation::Stack itself is in CREATE_COMPLETE. If something specific went wrong, there are details that can be expanded that usually explain exactly where things went wrong.

With your stack deployed, open up a browser and navigate to your domain name. It should work both with and without the www subdomain prepended to it, with www being redirected to the root domain and non-SSL requests redirecting to the SSL root domain. This is common expected behavior in the modern web.

Navigate to https://<your-domain>/ghost first, to create your owner account. Once you have created this you will be logged in to the admin console. To validate that your e-mail configuration worked, navigate on the left to Labs, and then Test email configuration and Send to test sending e-mails.

If there was a problem in the creation of the stack, you can delete it in its entired from the CLI

$ aws cloudformation delete-stack --stack-name ghost --region us-east-1

This can also be done in the UI Cloudformation Console. Bring it up, select the stack, and then select Actions and Delete Stack.

Summary

Congratulations, you're ready to begin customizing your site! The admin console under https://<your-domain>/ghost is very straightforward, so all main configuration with your site is very easily done. Be sure to check out the Ghost docs if you get stuck. There is also a robust collection of integrations, which will help you develop your site's functionality more fully. If you would like a different theme, there is a Ghost theme marketplace with plenty of good, responsive ghost templates. Give the robust Ghost editor which is integrated into the admin section a spin, and is one of the things that makes the Ghost blog platform so great.

In part 2, I will explain the AWS Cloudformation template used to do our Ghost install, and how you might change it to suit changing requirements over time, or to automate the install of other applications