TL;DR —
CloudFormation template that autodeploys MacksMind.io.
Download from GitHub.
Why CloudFormation?
Setting up static deploy to S3 usually involves many steps in the AWS UI, then using aws s3 sync locally to deploy changes.
Here I demonstrate how to automate jekyll build in CodePipeline/CodeBuild, and also how to create all the resources in a single CloudFormation template.
It creates between 12 and 20 resources depending on your selections.
Yes it’s overkill for a Jekyll site, but it’s also a starting point for related use cases that only need a single pipeline from a single GitHub branch.
What follows is section by section commentary to help you with that adaptation.
Parameters
Template sharing & reuse is all about parameters. If you’re deploying a Jekyll site, you might not have to change a thing. Some of the less obvious bits are:
Store the token in AWS Secrets Manager as “Other type of secrets” without automatic rotation
Construct a parameter resembling {{resolve:secretsmanager:macksmind.io:SecretString:github-token}}, in which macksmind.io is the secret name, and github-token is the key pointing to the token
Of course you can paste the token directly into the parameter if you remove AllowedPattern, but you shouldn’t
ChatbotSlackArn
To receive Slack notifications when deploys start and finish, link to Slack using these instructions
Once the channel is configured, paste the ARN into the parameter
Metadata
Controls parameter display in CloudFormation UI. Without this, parameters display alphabetically. 🙁
Conditions
Conditions and parameters are separate namespaces, permitting a Boolean such as Route53 to match the String parameter it’s derived from
Note the condition chaining using Fn::And
Certificate
Manual Step Alert!!
Certificate validation via DNS requires creation of a cryptic CNAME in the root domain
Once creation of your stack is under way, visit the Route53 console to view the pending certificate
Expand the certificate to view the CNAME
Create the CNAME in your DNS or if your’re using Route53, AWS will create it for you with the push of a button
Deploy Bucket
WebsiteConfiguration defines handling for requests that don’t have an exact match
AWS::NoValue essentially unsets ErrorDocument, but be warned the default 404 is cryptic
DeployBucketPolicy bravely lets all the world read, but not list
CloudFront Distribution
Compress turns on automatic compression
MinTTL sets the cache to 24 hours even though browsers see cache-control: no-cache
ViewerProtocolPolicy handles https redirection while OriginProtocolPolicy avoids attempting https with S3
DomainName uses the public hostname of the bucket, because WebsiteConfiguration has no effect on S3 access using AWS internals
DNS RecordSets
Condition: Route53 makes these resources optional
A & AAAA to cover IPv4 and IPv6
Using Route53 aliases instead of CNAME for a shorter lookup
If your DNS is not at Route53, you’ll need CNAMEs pointing to the CloudFront DomainName
CodePipeline
The magic starts here.
PollForSourceChanges turns off polling since we define a Webhook below
Pipeline Bucket
CodePipeline stores the code here for CodeBuild
CodeBuild also caches here as you’ll see below
Pipeline Role & Policy
All the perms needed to orchestrate the build
Webhook
Automatically manages webhooks from your GitHub repo.
CodeBuild Project
The magic continues here.
Cache property, bundle config set path, and cache key in BuildSpec avoid unneeded gem installs
node_modules and npm install would work in a similar way
Simulate rm -rf node_modules by deleting the cache from the Pipeline Bucket
--cache-control='no-cache' is set during aws s3 sync
aws cloudfront create-invalidation refreshes the cache
CodeBuild Role & Policy
Lots of perms for all the CodeBuild things.
Slack Notification
AWS sends status updates and a link to the build
Love to see that ✅
Redirect Root Domain
A few key changes allow redirection to the primary hostname.
This Certificate also needs a CNAME for validation
RedirectAllRequestsTo in S3 is the key difference from the deploy bucket
https redirect is handled by S3 instead of CloudFront for fewer round trips
Oddly the bucket doesn’t need to be public ¯\_(ツ)_/¯