Skip to content

Commit 9482eb0

Browse files
author
Ryan Coleman
authored
replace alb with http-gw and cloud map (#2)
This commit replaces the load balancer and target group mechanics with cheaper and more cloud-native HTTP Gateway and Cloud Map service discovery resources. However this infrastructure change will cost far less to run (something like 10x to 2x cheaper per month as a demo site) and represents a cleaner, more cloud native approach to hybrid container + serverless infrastructure that will be easier to scale and reason about. Also: * clean up VPC SGs and secrets policy * adjust private namespace for cloudmap to be more reusable * add ghostProxyRoute to auto-enable integration and cleanup container subnets * cleanup syntax for ghostProxyRoute Target * simplify API with openapi definition instead of integration & route resources * make ECS::Service unique with AWS::StackName variable * rely on default ECS naming and be explicit about the /ghost route * rebase on upstream gatsby starter changes
1 parent 3930a86 commit 9482eb0

File tree

11 files changed

+2553
-1753
lines changed

11 files changed

+2553
-1753
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ typings/
5858

5959
# dotenv environment variables file
6060
.env
61+
.env.development
62+
.env.production
6163

6264
# IDE
6365
.idea/*
@@ -75,4 +77,4 @@ yarn-error.log
7577
.netlify/
7678

7779
# Local Netlify folder
78-
.netlify
80+
.netlify

gatsby-config.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,11 @@ module.exports = {
6161
`gatsby-transformer-sharp`,
6262
{
6363
resolve: `gatsby-source-ghost`,
64-
options:
65-
process.env.NODE_ENV === `development`
66-
? ghostConfig.development
67-
: ghostConfig.production,
64+
options: {
65+
apiUrl: process.env.GHOST_API_URL,
66+
contentApiKey: process.env.GHOST_CONTENT_API_KEY,
67+
version: 'v3'
68+
}
6869
},
6970
/**
7071
* Utility Plugins

gatsby-ghost-basic-howto.md

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
---
2+
title: Ghost CMS with Gatsby JAMstack
3+
id: jamstack-ghostgatsby
4+
---
5+
6+
This tutorial will guide you through the process of deploying and hosting a [Ghost CMS](https://ghost.org/) with [Gatsby](https://www.gatsbyjs.org/) front-end using the serverless approach.
7+
8+
## What We're Building
9+
10+
By the end of the tutorial, you will have deployed into your AWS account:
11+
* An AWS Fargate ECS cluster, service, and [Docker Task](api/nodes/DockerTask.md) for the Ghost CMS
12+
* An AWS ELB application load balancer in front of the Ghost CMS service
13+
* An AWS Aurora MySql Serverless [Database](api/nodes/Database.md) behind the Ghost CMS service
14+
* An AWS CodeBuild Project for Gatsby as a [Website Builder](api/nodes/Website.md)
15+
* The Amazon S3 Bucket for Gatsby content to serve as a CDN origin [Object Store](api/nodes/ObjectStore.md)
16+
* An Amazon CloudFront distribution that delivers your static web content to users quickly and securely as a [CDN](api/nodes/CDN.md)
17+
* A Lambda [EdgeFunction](api/nodes/EdgeFunction.md) to assert secure headers in requests to CloudFront
18+
19+
You can deploy the whole infrastructure in minutes, resulting in a fully functional CMS-sourced static site available through a CDN. Modest template additions for a custom domain and ssl certificates are missing but otherwise this architecture suitable for real workloads.
20+
21+
> __These are intricate stacks and more than the hello world template. Meaningful AWS fees may apply. We suggest deploying them for an hour to have fun with the possibilities of the Serverless JAMstack, but don't forget to delete the CloudFormation stack before considering the long-term costs with https://calculator.aws__
22+
>
23+
> The brilliant thing about this architecture is that the expensive Ghost infrastructure is only necessary when you're editing or building content. Once a build is delivered to the CDN, Ghost could be shut down.
24+
25+
### Ghost CMS Architecture
26+
27+
Feel free to play around:
28+
29+
<iframe id='canvas'
30+
title='Stackery canvas of AWS SAM template'
31+
width='100%'
32+
height='500'
33+
frameBorder='0'
34+
src='https://app.stackery.io/editor/design?owner=ryanycoleman&repo=gatsby-starter-ghost&file=ghostTemplate.yaml'>
35+
</iframe>
36+
37+
<br>
38+
This stack provides the Ghost CMS web client and API through an Elastic Load Balancer. It's a heavyweight stack by serverless tutorial standards, providing a load-balanced container service and MySQL cluster. Because we're using AWS managed services, AWS SAM, and CloudFormation, everything can be replicated as infrastructure-as-code and there are zero servers to manage.
39+
40+
### Gatsby Front-end Architecture
41+
42+
<iframe id='canvas'
43+
title='Stackery canvas of SAM template'
44+
width='100%'
45+
height='500'
46+
frameBorder='0'
47+
src='https://app.stackery.io/editor/design?owner=ryanycoleman&repo=gatsby-starter-ghost&file=gatsbyTemplate.yaml'>
48+
</iframe>
49+
50+
51+
## Setup
52+
53+
### Project Repository
54+
55+
The following repository is referenced throughout this tutorial:
56+
57+
- <a href="https://github.com/ryanycoleman/gatsby-starter-ghost" target="_blank" alt="GitHub repository">Gatsby Starter - Ghost, with AWS SAM templates</a>
58+
59+
### Requirements and Assumptions
60+
61+
The following software is used in this tutorial:
62+
63+
- <a href="https://www.gatsbyjs.org/docs/quick-start#use-the-gatsby-cli" target="_blank" alt="Gatsby Docs">You'll want to consider installing the Gatsby CLI for local development</a> but you can deploy a functional blog without it
64+
- [Sign up](https://app.stackery.io/sign-up) for a Stackery account if you haven't already. This guide assumes an environment named 'development' linked to an AWS account of your choosing
65+
- This guide will create AWS infrastructure in your default VPC and default ECS Fargate cluster, etc.
66+
67+
#### Deploy Quick Start
68+
69+
You can deploy each example stack into your own AWS account using these three Stackery CLI commands:
70+
71+
`stackery create` will initialize a new repo in your GitHub account, initializing it with the contents of the referenced blueprint repository.
72+
73+
```text
74+
stackery create --stack-name 'jamstack-ghost' \
75+
--git-provider 'github' \
76+
--blueprint-git-url 'https://github.com/ryanycoleman/gatsby-starter-ghost'
77+
```
78+
79+
`stackery deploy` will deploy the newly created stack into your AWS account.
80+
81+
```text
82+
stackery deploy --stack-name 'jamstack-ghost' \
83+
--env-name 'development' \
84+
--git-ref 'master'
85+
```
86+
87+
Once this stack is deployed, you'll need to retrieve a few pieces of data from the AWS Console and provide them as [Stackery Environment parameter data](https://docs.stackery.io/docs/using-stackery/environments/) which will be used by the following Gatsby stack.
88+
89+
First, identify the Elastic Load Balancer address in the AWS Console for EC2. It'll look like `http://ghostapilb-#number.#region.elb.amazonaws.com/`.
90+
91+
You'll then setup your Ghost CMS via that URL /ghost but note that because this stack isn't configured with a custom domain, the ELB is not protecting your data over SSL. Use throwaway information for this step or refactor the SAM template to use a custom domain and SSL certificate.
92+
93+
Once you've signed up for the admin account, navigate to '/ghost/#/settings/integrations'. Here you'll add a custom integration named Gatsby. You'll want to retrieve the 'Content API Key' and 'API URL' which you'll use to create environment data similar to the following example.
94+
95+
```code
96+
{
97+
"ghostApiUrl": "API URL Here",
98+
"ghostContentApiKey": "Content API Key Here"
99+
}
100+
```
101+
102+
Once saved into your Stackery environment, you can create and deploy the Gatsby stack.
103+
104+
```text
105+
stackery create --stack-name 'jamstack-ghost' \
106+
--git-provider 'github' \
107+
--blueprint-git-url 'https://github.com/ryanycoleman/gatsby-starter-ghost'
108+
```
109+
110+
`stackery deploy` will deploy the newly created stack into your AWS account.
111+
112+
```text
113+
stackery deploy --stack-name 'jamstack-ghost' \
114+
--env-name 'development' \
115+
--git-ref 'master'
116+
```
117+
118+
The Website Builder resource in this stack will connect the Gatsby static site generator to the Ghost CMS, source content from it, and deliver that built content to an S3 bucket which serves as a CloudFront origin. That's it! You're all done. At this point you can edit/add content in Ghost CMS, log into CodeBuild and re-run the stack-provisioned job to rebuild your site. Future revisions of this stack could include a webhook to automatically rebuild Gatsby when Ghost changes.

gatsbyTemplate.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ Resources:
2727
Value: !Ref EnvConfigghostContentApiKeyAsString
2828
- Name: SITEURL
2929
Value: !GetAtt gatsbyCDN.DomainName
30+
31+
- Name: NODE_ENV
32+
Value: 'production'
3033
ServiceRole: !GetAtt gatsbyBuildRole.Arn
3134
Source:
3235
Type: NO_SOURCE

package.json

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"url": "git+https://github.com/tryghost/gatsby-starter-ghost.git"
1111
},
1212
"resolutions": {
13-
"sharp": "0.25.4"
13+
"sharp": "0.26.0"
1414
},
1515
"engines": {
1616
"node": ">= 10.13.0"
@@ -32,29 +32,29 @@
3232
},
3333
"devDependencies": {
3434
"babel-eslint": "10.1.0",
35-
"eslint": "7.6.0",
35+
"eslint": "7.11.0",
3636
"eslint-plugin-ghost": "1.5.0",
37-
"eslint-plugin-react": "7.20.5"
37+
"eslint-plugin-react": "7.21.4"
3838
},
3939
"dependencies": {
40-
"@tryghost/helpers": "1.1.28",
41-
"@tryghost/helpers-gatsby": "1.0.32",
40+
"@tryghost/helpers": "1.1.31",
41+
"@tryghost/helpers-gatsby": "1.0.35",
4242
"cheerio": "1.0.0-rc.3",
43-
"gatsby": "2.24.26",
43+
"gatsby": "2.24.73",
4444
"gatsby-awesome-pagination": "0.3.6",
45-
"gatsby-image": "2.4.14",
46-
"gatsby-plugin-advanced-sitemap": "1.5.5",
47-
"gatsby-plugin-catch-links": "2.3.11",
48-
"gatsby-plugin-feed": "2.5.11",
45+
"gatsby-image": "2.4.21",
46+
"gatsby-plugin-advanced-sitemap": "1.5.6",
47+
"gatsby-plugin-catch-links": "2.3.15",
48+
"gatsby-plugin-feed": "2.5.14",
4949
"gatsby-plugin-force-trailing-slashes": "1.0.4",
50-
"gatsby-plugin-manifest": "2.4.21",
51-
"gatsby-plugin-offline": "3.2.21",
52-
"gatsby-plugin-react-helmet": "3.3.10",
53-
"gatsby-plugin-sharp": "2.6.24",
54-
"gatsby-source-filesystem": "2.3.23",
55-
"gatsby-source-ghost": "4.0.5",
56-
"gatsby-transformer-sharp": "2.5.12",
57-
"lodash": "4.17.19",
50+
"gatsby-plugin-manifest": "2.4.34",
51+
"gatsby-plugin-offline": "3.2.31",
52+
"gatsby-plugin-react-helmet": "3.3.14",
53+
"gatsby-plugin-sharp": "2.6.40",
54+
"gatsby-source-filesystem": "2.3.34",
55+
"gatsby-source-ghost": "4.2.0",
56+
"gatsby-transformer-sharp": "2.5.17",
57+
"lodash": "4.17.20",
5858
"react": "16.13.1",
5959
"react-dom": "16.13.1",
6060
"react-helmet": "6.1.0"

plugins/gatsby-plugin-ghost-manifest/gatsby-node.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,21 +33,21 @@ function generateIcons(icons, srcIcon) {
3333
}
3434

3535
exports.onPostBuild = /*#__PURE__*/function () {
36-
var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(_ref2, pluginOptions) {
37-
var graphql, icon, manifest, _ref3, data, siteTitle, iconPath;
36+
var _ref2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(_ref, pluginOptions) {
37+
var graphql, icon, manifest, _yield$graphql, data, siteTitle, iconPath;
3838

3939
return _regenerator.default.wrap(function _callee$(_context) {
4040
while (1) {
4141
switch (_context.prev = _context.next) {
4242
case 0:
43-
graphql = _ref2.graphql;
43+
graphql = _ref.graphql;
4444
icon = pluginOptions.icon, manifest = (0, _objectWithoutPropertiesLoose2.default)(pluginOptions, ["icon"]);
4545
_context.next = 4;
4646
return graphql(pluginOptions.query);
4747

4848
case 4:
49-
_ref3 = _context.sent;
50-
data = _ref3.data;
49+
_yield$graphql = _context.sent;
50+
data = _yield$graphql.data;
5151
siteTitle = data.allGhostSettings.edges[0].node.title || "No Title";
5252
manifest = (0, _extends2.default)({}, manifest, {
5353
name: siteTitle
@@ -95,6 +95,6 @@ exports.onPostBuild = /*#__PURE__*/function () {
9595
}));
9696

9797
return function (_x, _x2) {
98-
return _ref.apply(this, arguments);
98+
return _ref2.apply(this, arguments);
9999
};
100100
}();

plugins/gatsby-plugin-ghost-manifest/gatsby-ssr.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ exports.onRenderBody = function (_ref, pluginOptions) {
1818
var favicon = icons && icons.length ? icons[0].src : null;
1919

2020
if (favicon) {
21-
headComponents.push(_react.default.createElement("link", {
21+
headComponents.push( /*#__PURE__*/_react.default.createElement("link", {
2222
key: "gatsby-plugin-manifest-icon-link",
2323
rel: "shortcut icon",
2424
href: (0, _gatsby.withPrefix)(favicon)
@@ -27,7 +27,7 @@ exports.onRenderBody = function (_ref, pluginOptions) {
2727
} // Add manifest link tag.
2828

2929

30-
headComponents.push(_react.default.createElement("link", {
30+
headComponents.push( /*#__PURE__*/_react.default.createElement("link", {
3131
key: "gatsby-plugin-manifest-link",
3232
rel: "manifest",
3333
href: (0, _gatsby.withPrefix)("/manifest.webmanifest")
@@ -37,7 +37,7 @@ exports.onRenderBody = function (_ref, pluginOptions) {
3737
var insertMetaTag = Object.keys(pluginOptions).includes("theme_color_in_head") ? pluginOptions.theme_color_in_head : true;
3838

3939
if (insertMetaTag) {
40-
headComponents.push(_react.default.createElement("meta", {
40+
headComponents.push( /*#__PURE__*/_react.default.createElement("meta", {
4141
key: "gatsby-plugin-manifest-meta",
4242
name: "theme-color",
4343
content: pluginOptions.theme_color
@@ -47,7 +47,7 @@ exports.onRenderBody = function (_ref, pluginOptions) {
4747

4848
if (pluginOptions.legacy) {
4949
var iconLinkTags = icons.map(function (icon) {
50-
return _react.default.createElement("link", {
50+
return /*#__PURE__*/_react.default.createElement("link", {
5151
key: "gatsby-plugin-manifest-apple-touch-icon-" + icon.sizes,
5252
rel: "apple-touch-icon",
5353
sizes: icon.sizes,

plugins/gatsby-plugin-ghost-manifest/package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
"version": "0.0.1",
55
"author": "Ghost Foundation",
66
"dependencies": {
7-
"@babel/runtime": "7.10.5",
7+
"@babel/runtime": "7.11.2",
88
"bluebird": "3.7.2",
9-
"sharp": "0.25.4"
9+
"sharp": "0.26.0"
1010
},
1111
"devDependencies": {
12-
"@babel/cli": "7.10.5",
13-
"@babel/core": "7.10.5",
14-
"babel-preset-gatsby-package": "0.5.2",
12+
"@babel/cli": "7.11.6",
13+
"@babel/core": "7.11.6",
14+
"babel-preset-gatsby-package": "0.5.3",
1515
"cross-env": "7.0.2"
1616
},
1717
"keywords": [
@@ -24,7 +24,7 @@
2424
"pwa"
2525
],
2626
"resolutions": {
27-
"sharp": "0.25.4"
27+
"sharp": "0.26.0"
2828
},
2929
"license": "MIT",
3030
"main": "index.js",

0 commit comments

Comments
 (0)