<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.2.0">Jekyll</generator><link href="https://www.kickboxfrownyface.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.kickboxfrownyface.com/" rel="alternate" type="text/html" /><updated>2022-08-17T13:50:52+00:00</updated><id>https://www.kickboxfrownyface.com/feed.xml</id><title type="html">Kickbox Frownyface</title><subtitle>We make games</subtitle><author><name>Johnny Kickbox</name><email>johnny@kickboxfrownyface.com</email></author><entry><title type="html">Accurate Atomic Counters with Amazon DynamoDB</title><link href="https://www.kickboxfrownyface.com/tech/dynamo-atomic/" rel="alternate" type="text/html" title="Accurate Atomic Counters with Amazon DynamoDB" /><published>2021-08-14T12:43:07+00:00</published><updated>2021-08-14T12:43:07+00:00</updated><id>https://www.kickboxfrownyface.com/tech/dynamo-atomic</id><content type="html" xml:base="https://www.kickboxfrownyface.com/tech/dynamo-atomic/">&lt;p&gt;&lt;a href=&quot;https://aws.amazon.com/dynamodb&quot;&gt;Amazon DynamoDB&lt;/a&gt; is a fully managed key-value 
database at AWS. It’s great in a lot of ways: it’s serverless, it scales out
incredibly wide, and it’s incredibly cheap for what it is. Good use cases for 
Dynamo are player preferences, game status, and low-cost global leaderboards.&lt;/p&gt;

&lt;p&gt;But if you’re using Dynamo for an atomic counter under heavy load, that 
absolutey &lt;em&gt;has&lt;/em&gt; to be accurate, it can be tricky to get right. In this post
we’ll give an easy solution for incrementing a counter without losing updates.&lt;/p&gt;

&lt;p&gt;To show this, we’ll imagine a common use case: adding one to a “likes”
counter on an image, say. In this example, we have a DynamoDB table
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;images&lt;/code&gt; which has a single primary key of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt;, an integer. Each item in
this table has, among other things, a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;likes&lt;/code&gt; field which is supposed to store
the number of likes that images has.&lt;/p&gt;

&lt;p&gt;How can we implement an atomic increment to that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;likes&lt;/code&gt; column for any image
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt;?&lt;/p&gt;

&lt;h2 id=&quot;the-easy-but-sometimes-wrong-way&quot;&gt;The Easy But (Sometimes) Wrong Way&lt;/h2&gt;

&lt;p&gt;Google &lt;em&gt;DynamoDB atomic counters&lt;/em&gt; and you’ll immediately get to AWS’s documentation
for &lt;a href=&quot;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithItems.html#WorkingWithItems.AtomicCounters&quot;&gt;atomic counters&lt;/a&gt;.
The advice is to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ADD&lt;/code&gt; where you simply add one to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;likes&lt;/code&gt; field of
an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;images&lt;/code&gt; item.&lt;/p&gt;

&lt;p&gt;In Python it looks like this:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;boto3&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;dynamodb&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boto3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'dynamodb'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;add_like&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Add one like to image `id`&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dynamodb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;update_item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;TableName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'images'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'id'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'N'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}},&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;UpdateExpression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'ADD likes :num'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ExpressionAttributeValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;':num'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'N'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'1'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So all that means is, “Add one to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;likes&lt;/code&gt; where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; = id”.
Simple! And we like simple over here at Kickbox Frownyface.&lt;/p&gt;

&lt;p&gt;However, there’s a problem here: this update isn’t actually atomic, in that if
you have two Lambda functions (say) calling this function at exactly the same
time, one of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ADD&lt;/code&gt; functions is effectively dropped.&lt;/p&gt;

&lt;p&gt;For example let’s say that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;likes&lt;/code&gt; for this adorable kitten picture
is at 5 and both Bob and Alice click “like” at pretty much the same time.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/tech/dynamo-atomic/kitten.jpg&quot; alt=&quot;Cute kitten&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you are unlucky, you may end up with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;likes&lt;/code&gt; just set to 6 instead of 7,
because both Alice and Bob were adding one to 5 at precisely the same time.
(If you don’t believe me, try it out – fan out 1000 or so Lambdas and you’re
pretty likely to end up with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;likes&lt;/code&gt; less than the expected 1000.)&lt;/p&gt;

&lt;p&gt;Is that bad? Well, that really depends. If you’re OK with the occasional 
dropped update, then leave well enough alone and move on – you have bigger
fish to fry!&lt;/p&gt;

&lt;p&gt;But if your counters just &lt;em&gt;must&lt;/em&gt; be correct, let’s make a Conditional Write.&lt;/p&gt;

&lt;h2 id=&quot;adding-a-conditional-expression&quot;&gt;Adding a Conditional Expression&lt;/h2&gt;

&lt;p&gt;What we want to do is change the function from “just add one” to 
“just add one to what I think it is right now”. In our example, when Bob
clicks, the function will run “Add one to likes so long as the current
number of likes is five” and when Alice clicks, the function will &lt;em&gt;also&lt;/em&gt; run
“Add one to likes so long as the current number of likes is five.”
If both calls happen simultaneously, one will go through, and the other
will be rejected because the conditional is no longer satisfied.&lt;/p&gt;

&lt;p&gt;The code is pretty similar, we just do a fetch then a conditional update:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;boto3&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;dynamodb&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boto3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'dynamodb'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;add_like&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Add one like to image `id`&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;existing&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dynamodb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;TableName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'images'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;Key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'id'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'N'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dynamodb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;update_item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;TableName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;table_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'id'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'N'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}},&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;UpdateExpression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'ADD likes :num'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ConditionExpression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'likes = :current'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ExpressionAttributeValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;':num'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'N'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'1'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;':current'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'N'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;existing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'Item'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'likes'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'N'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This code is fetching the current record for image &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; and storing that
in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;existing&lt;/code&gt;. Then it does an update on that image, adding 1 to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;likes&lt;/code&gt;
&lt;em&gt;so long as the current likes value is equal to the value we just fetched&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Now, if Alice and Bob both add their likes
at precisely the same time, one of them will go through and the other
will have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ConditionalCheckFailedException&lt;/code&gt; thrown.&lt;/p&gt;

&lt;p&gt;Are we done? Not quite! We still have the number of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;likes&lt;/code&gt; for that 
image set to six! Either Alice’s or Bob’s like wasn’t recorded.
But now at least we know it was dropped, so let’s retry the failed one
with code like this:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;add_like_safely&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tries&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;attempts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;success&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attempts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tries&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;attempts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attempts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;add_like&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;success&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;
       &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ClientError&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'Error'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'Code'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'ConditionalCheckFailedException'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; 
                &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.01&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;success&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This way, if both Alice and Bob call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;add_like_safely&lt;/code&gt; at precisely the
same time, one of them will get through first… but the exception will 
be caught and the other will try again after a short delay to give the 
data time to propagate out.&lt;/p&gt;</content><author><name>Johnny Kickbox</name><email>johnny@kickboxfrownyface.com</email></author><category term="tech" /><summary type="html">Amazon DynamoDB is a fully managed key-value database at AWS. It’s great in a lot of ways: it’s serverless, it scales out incredibly wide, and it’s incredibly cheap for what it is. Good use cases for Dynamo are player preferences, game status, and low-cost global leaderboards.</summary></entry><entry><title type="html">Automatically Building and Deploying Jekyll on AWS with CodePipeline</title><link href="https://www.kickboxfrownyface.com/tech/jekyll-codepipeline/" rel="alternate" type="text/html" title="Automatically Building and Deploying Jekyll on AWS with CodePipeline" /><published>2021-04-23T17:32:45+00:00</published><updated>2021-10-17T09:32:25+00:00</updated><id>https://www.kickboxfrownyface.com/tech/jekyll-codepipeline</id><content type="html" xml:base="https://www.kickboxfrownyface.com/tech/jekyll-codepipeline/">&lt;p&gt;In our &lt;a href=&quot;/tech/static-hosting-jekyll-amzn/&quot;&gt;last post&lt;/a&gt; we covered how to use 
S3 and CloudFront to host your Jekyll site on AWS statically, which keeps it
fast and very inexpensive to operate. In this post, we will cover 
how to set up CodePipeline and CodeBuild to automatically rebuild the site
and publish it to the web whenever you commit a change to your
CodeCommit repository. When you’ve hooked up your website in this way, you
will never want to go back to the old way: you can just concentrate on 
adding content and designing your site without having to concern yourself
with how it actually gets live on the web. It’s a wonderful way to work.&lt;/p&gt;

&lt;h2 id=&quot;building-the-jekyll-site-automatically&quot;&gt;Building the Jekyll Site Automatically&lt;/h2&gt;

&lt;p&gt;Whenever we make a change to our Jekyll site – adding a blog post, making
a change to the theme, whatever – we need to rebuild it using the command
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jekyll build&lt;/code&gt;. To do this on AWS, we use the &lt;a href=&quot;https://aws.amazon.com/codebuild/&quot;&gt;CodeBuild&lt;/a&gt;
service. We’ll only pay for the compute power in the cloud we use as we use it, by
the second, so it’s a very cost effective solution.&lt;/p&gt;

&lt;p&gt;Create a new file in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;infra&lt;/code&gt; directory called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;codebuild.yml&lt;/code&gt;.
If you’ve been following along, you’ll recognize in the CloudFormation 
template below some variables that the Serverless Framework will fill in for
us, for example &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;${self:custom.account}&lt;/code&gt; is our account number.&lt;/p&gt;

&lt;p&gt;Here’s the file:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# The build project that runs buildspec.yml in the tree&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Resources&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;CodeBuildServiceRole&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AWS::IAM::Role&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;AssumeRolePolicyDocument&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;Version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;2012-10-17&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;Statement&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Effect&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Allow&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;sts:AssumeRole&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Principal&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;Service&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;codebuild.amazonaws.com&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;CodeBuildServicePolicy&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AWS::IAM::Policy&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;PolicyName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;CodeBuildServicePolicy&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Roles&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;!Ref&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;CodeBuildServiceRole&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;PolicyDocument&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;Version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;2012-10-17&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;Statement&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Effect&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Allow&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;logs:CreateLogGroup&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;logs:CreateLogStream&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;logs:PutLogEvents&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Resource&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;arn:aws:logs:${self:provider.region}:${self:custom.account}:*&quot;&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Effect&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Allow&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;codecommit:GitPull&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Resource&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;arn:aws:codecommit:${self:provider.region}:${self:custom.account}:${self:custom.repo}&quot;&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Effect&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Allow&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;s3:GetObject&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;s3:GetObjectVersion&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;s3:PutObject&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Resource&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;arn:aws:s3:::${self:custom.s3Bucket}*&quot;&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Effect&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Allow&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cloudfront:CreateInvalidation&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Resource&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;*&quot;&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;CodeBuildProject&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AWS::CodeBuild::Project&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${self:custom.project}-${self:custom.stage}&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Description&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Build for ${self:custom.subdomain}.${self:custom.domain}&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;ServiceRole&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;!GetAtt&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;CodeBuildServiceRole.Arn&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Artifacts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;CODEPIPELINE&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;linuxContainer&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ComputeType&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;BUILD_GENERAL1_SMALL&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;aws/codebuild/amazonlinux2-x86_64-standard:3.0&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;EnvironmentVariables&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;CLOUDFRONT_DISTRO_ID&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${self:custom.cloudfront_id}&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;S3_BUCKET&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${self:custom.s3Bucket}&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;LogsConfig&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;CloudWatchLogs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;Status&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ENABLED&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;GroupName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/build/${self:custom.project}-${self:custom.stage}&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Source&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;CODEPIPELINE&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;TimeoutInMinutes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;60&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;It’s a pretty long file, it’s true, but there’s nothing complicated here.&lt;/p&gt;

&lt;p&gt;The first two stanzas are policies that give the CodeBuild service the privileges
it needs to do its work: the ability to write to CloudWatch logs, to 
pull source code from our git repo that we set up last time, and the ability
to copy the created HTML files (the actual website) to the static S3 bucket
that houses them.&lt;/p&gt;

&lt;p&gt;Then the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CodeBuildProject&lt;/code&gt; resource is the CodeBuild project itself, which tells
AWS what kind of EC2 instance we need to do the build, and where it should be
getting the source code from (CodePipeline; see below). Note that there are
two environment variables that we’re setting for the instance: S3 bucket
(where the static HTML files that Jekyll builds should go) and the CloudFront
Distribution that’s fronting it.&lt;/p&gt;

&lt;h2 id=&quot;triggering-the-build-on-a-code-commit&quot;&gt;Triggering the Build on a Code Commit&lt;/h2&gt;

&lt;p&gt;Now we want to set up our &lt;a href=&quot;https://aws.amazon.com/codepipeline/&quot;&gt;CodePipeline&lt;/a&gt;,
which will automatically trigger that build whenever we push any changes to the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt; branch of our repo.&lt;/p&gt;

&lt;p&gt;Create a file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;codepipeline.yml&lt;/code&gt; in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;infra&lt;/code&gt; directory with this content:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;na&quot;&gt;Resources&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;CodePipelineBucket&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AWS::S3::Bucket&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;BucketName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${self:custom.s3Bucket}-codepipeline&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;PublicAccessBlockConfiguration&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;BlockPublicAcls &lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;BlockPublicPolicy &lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;IgnorePublicAcls &lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;RestrictPublicBuckets &lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
        
  &lt;span class=&quot;na&quot;&gt;CodePipelineServiceRole&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;AWS::IAM::Role&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;AssumeRolePolicyDocument&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;Version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;2012-10-17&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;Statement&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Effect&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Allow&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Principal&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;Service&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;codepipeline.amazonaws.com&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;sts:AssumeRole&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Policies&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;PolicyName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;CodePipelineServicePolicy&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;PolicyDocument&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;2012-10-17&quot;&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Statement&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Effect&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Allow&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;codecommit:CancelUploadArchive&quot;&lt;/span&gt;
                  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;codecommit:GetBranch&quot;&lt;/span&gt;
                  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;codecommit:GetCommit&quot;&lt;/span&gt;
                  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;codecommit:GetUploadArchiveStatus&quot;&lt;/span&gt;
                  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;codecommit:UploadArchive&quot;&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;Resource&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;arn:aws:codecommit:${self:provider.region}:${self:custom.account}:${self:custom.repo}&quot;&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Effect&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Allow&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;codebuild:BatchGetBuilds&quot;&lt;/span&gt;
                  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;codebuild:StartBuild&quot;&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;Resource&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;*&quot;&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Effect&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Allow&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;s3:*&quot;&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;Resource&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;arn:aws:s3:::${self:custom.s3Bucket}-codepipeline/*&quot;&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;AmazonCloudWatchEventRole&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;AWS::IAM::Role&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;AssumeRolePolicyDocument&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;Version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;2012-10-17&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;Statement&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Effect&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Allow&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Principal&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;Service&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;events.amazonaws.com&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;sts:AssumeRole&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Policies&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;PolicyName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;CodePipelineExecutionPolicy&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;PolicyDocument&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;2012-10-17&quot;&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Statement&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Effect&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Allow&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;codepipeline:StartPipelineExecution&quot;&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;Resource&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;  &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;arn:aws:codepipeline:${self:provider.region}:${self:custom.account}:${self:custom.project}-${self:custom.stage}&quot;&lt;/span&gt;
  
  &lt;span class=&quot;na&quot;&gt;AmazonCloudWatchEventRule&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;AWS::Events::Rule&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;EventPattern&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; 
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;aws.codecommit&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;detail-type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; 
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;CodeCommit Repository State Change&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;resources&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; 
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;arn:aws:codecommit:${self:provider.region}:${self:custom.account}:${self:custom.repo}&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;detail&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;referenceCreated&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;referenceUpdated&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;referenceType&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; 
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;branch&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;referenceName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; 
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;master&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Targets&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Arn&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;arn:aws:codepipeline:${self:provider.region}:${self:custom.account}:${self:custom.project}-${self:custom.stage}&quot;&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;RoleArn&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;!GetAtt&lt;/span&gt; 
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AmazonCloudWatchEventRole&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Arn&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;Id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;codepipeline-AppPipeline&lt;/span&gt;


  &lt;span class=&quot;na&quot;&gt;AppPipeline&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;AWS::CodePipeline::Pipeline&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${self:custom.project}-${self:custom.stage}&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;RoleArn&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;!GetAtt&lt;/span&gt; 
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;CodePipelineServiceRole&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Arn&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Stages&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Source&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;Actions&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;SourceAction&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;RunOrder&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;ActionTypeId&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;Category&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Source&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;Owner&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AWS&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;Version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;Provider&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;CodeCommit&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;Configuration&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;BranchName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;master&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;RepositoryName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${self:custom.repo}&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;PollForSourceChanges&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;OutputArtifacts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;SourceArtifact&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Build&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;Actions&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;BuildAction&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;RunOrder&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;ActionTypeId&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;Category&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Build&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;Owner&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AWS&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;Version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;Provider&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;CodeBuild&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;InputArtifacts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;SourceArtifact&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;OutputArtifacts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;BuildArtifact&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;Configuration&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;ProjectName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${self:custom.project}-${self:custom.stage}&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;ArtifactStore&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;S3&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;Location&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${self:custom.s3Bucket}-codepipeline&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This is another long CloudFormation template, but again there’s nothing
conceptually challenging here.&lt;/p&gt;

&lt;p&gt;The first stanza just sets up an S3 bucket for the pipeline to place the
source code in, and from which &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CodeBuild&lt;/code&gt; picks it up automatically. We
configure that bucket to be private to us.&lt;/p&gt;

&lt;p&gt;Then we set up a policy for the pipeline. It needs the ability to work with
CodeCommit, our git repo; to start builds in CodeBuild; and of course to
put source code into that S3 bucket we just created.&lt;/p&gt;

&lt;p&gt;We also need to give CloudWatch the ability to trigger the builds
themselves. Whenever a file is created or updated in the repo, that will kick
off the pipeline itself.&lt;/p&gt;

&lt;p&gt;Finally we have the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AppPipeline&lt;/code&gt; pipeline itself. This sets up two distinct
phases: a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Source&lt;/code&gt; phase, which does a full pull from git and which copies the
output into the code pipeline bucket. This is called the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SourceArtifact&lt;/code&gt;. That
artifact is referenced as input in the second &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Build&lt;/code&gt; phase.&lt;/p&gt;

&lt;p&gt;So, to sum up: whenever there’s a push done to the git repo, the Pipeline
will kick in. It will do a full get on the source tree, put a copy of it in
S3, and will then trigger the Build.&lt;/p&gt;

&lt;h2 id=&quot;the-buildspec-file&quot;&gt;The BuildSpec File&lt;/h2&gt;

&lt;p&gt;But how does CodeBuild know how to build a Jekyll website? 
Well, we need a &lt;em&gt;build specification file&lt;/em&gt; that tells CodeBuild what steps exactly
it should take.&lt;/p&gt;

&lt;p&gt;This is pretty simple: essentially, in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build&lt;/code&gt; phase we tell CodeBuild to take the source
code that was given to it by CodePipeline, and run Jekyll on it with the production 
configuration settings. That will put the appropriate files in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_site&lt;/code&gt; directory
on the EC2 instance doing the build.&lt;/p&gt;

&lt;p&gt;Then, in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;post_build&lt;/code&gt; phase, copy the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_site&lt;/code&gt; directory over to our S3 bucket for serving. Finally,
invalidate the CloudFront cache so that our reading public can get the latest and the
greatest right away.&lt;/p&gt;

&lt;p&gt;If you’ve been following along, your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;buildspec.yml&lt;/code&gt; file should be at the root of your
git repo and should look like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0.2&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;phases&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;install&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;commands&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;gem install jekyll bundler&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cd website&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle install&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;commands&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;JEKYLL_ENV=production bundle exec jekyll build --config _config.yml,_config_production.yml&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;post_build&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;commands&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;aws s3 sync _site s3://$S3_BUCKET&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_DISTRO_ID --paths &quot;/*&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Note that the build is getting the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S3_BUCKET&lt;/code&gt; and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLOUDFRONT_DISTRO_ID&lt;/code&gt; as environment
variables. Those were placed there by CodeBuild, in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EnvironmentVariables&lt;/code&gt; section.&lt;/p&gt;

&lt;h2 id=&quot;revisiting-the-serverless-framework-configuration&quot;&gt;Revisiting the Serverless Framework Configuration&lt;/h2&gt;

&lt;p&gt;As we discussed last time, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;serverless.yml&lt;/code&gt; file includes the configuration
settings to make all of this work properly. We need to make a few adjusments
so that when we deploy our infrastructure stack, everything is hooked up properly.&lt;/p&gt;

&lt;p&gt;Here’s how ours looks now:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;na&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jekyll-web&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;custom&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jekyll-web&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;account&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[YOUR&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;ACCOUNT&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;ID]'&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;certificate&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CERTIFICATE ID&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;YOUR DOMAIN&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;sslCertArn&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;arn:aws:acm:us-east-1:${self:custom.account}:certificate/${self:custom.certificate}&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${opt:stage, self:provider.stage}&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;subdomain&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${self:provider.environment.${self:custom.stage}_subdomain}&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;s3Bucket&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jekyll-web-${self:custom.stage}&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;cloudfront_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;  &lt;span class=&quot;s&quot;&gt;${self:provider.environment.${self:custom.stage}_cloudfront_id}&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;repo&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;YOUR REPO NAME&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;provider&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;aws&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;prod&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;region&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;YOUR PREFERRED AWS REGION&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;dev_subdomain&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;dev&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;dev_cloudfront_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; 
    &lt;span class=&quot;na&quot;&gt;prod_subdomain&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;www&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;prod_cloudfront_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;YOUR CLOUDFRONT ID&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;stackTags&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Project&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Corporate&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;resources&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${file(s3.yml)}&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${file(cloudfront.yml)}&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${file(codebuild.yml)}&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${file(codepipeline.yml)}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We’ve added our CodeBuild and CodePipeline infrastructure pieces to the bottom, and we’ve
added some custom variables that help us parameterize the scripts so we can use them for
other purposes.&lt;/p&gt;

&lt;p&gt;Note that the Certificate Arn and the CloudFront ID have to be manually added to the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;serverless.yml&lt;/code&gt; file for this to work. That’s kind of lame, I admit – but whatever. It works.&lt;/p&gt;

&lt;h2 id=&quot;add-the-files-to-git&quot;&gt;Add the Files to Git&lt;/h2&gt;

&lt;p&gt;OK, we have a number of new files that we’ve added. If you’re doing it exactly the same
way we are, your file tree should look like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.
├── buildspec.yml
├── infra
│   ├── cloudfront.yml
│   ├── codebuild.yml
│   ├── codepipeline.yml
│   ├── s3.yml
│   └── serverless.yml
├── README.md
└── website
    ├── 404.html
    ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Be sure to add all these files to your git repo.&lt;/p&gt;

&lt;p&gt;As soon as you push your commit to the git repo, CodePipeline will recognize that there’s
been a change to the tree and will spring into action. If you log into your AWS console
and navigate to your new CodePipeline, you should see something like this:&lt;/p&gt;

&lt;h1 id=&quot;deploying-and-triggering-the-build&quot;&gt;Deploying and Triggering the Build&lt;/h1&gt;

&lt;p&gt;Deploy all that infrastructure using the Serverless Framework:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ sls deploy 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It will take some time for Serverless to build the CodeBuild and CodePipeline
infrastructure elements in AWS.&lt;/p&gt;

&lt;p&gt;To test it you can make a trivial change to your Jekyll
site – change the site description in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_config.yml&lt;/code&gt; file, for example – and push
it up to git. You should be able to watch CodePipeline start processing.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/tech/jekyll-codex/running-pipeline.png&quot; alt=&quot;Code Pipeline In Action&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;conclusion-what-have-we-wrought&quot;&gt;Conclusion: What Have we Wrought?&lt;/h2&gt;

&lt;p&gt;We’ve done a lot in this three-step blog series, so it’s worth reviewing.&lt;/p&gt;

&lt;p&gt;First, we set up a Jekyll development environment fully in the cloud: we have a Cloud9 
environment as our Jekyll IDE, for which we only pay when we are actually using it.
The source of our Jekyll site is safely stored in a fully managed git repo in 
CodeCommit. When we make changes to our Jekyll site and commit those changes to
git, a pipeline springs into being and builds static HTML files for the site
automatically. These files are then automatically copied over to an S3 bucket, from
which our site is securely served behind an SSL certificate. And these HTML files 
are distributed by CloudFront, a Content Delivery Network, for extremely speedy 
rendering no matter where in the world your readers are.&lt;/p&gt;

&lt;p&gt;Not only does that save a &lt;em&gt;ton&lt;/em&gt; of time over the long run, but it does it
&lt;em&gt;extremely&lt;/em&gt; cheaply and reliably.&lt;/p&gt;

&lt;p&gt;We hope this blog series has been useful to you.&lt;/p&gt;</content><author><name>Johnny Kickbox</name><email>johnny@kickboxfrownyface.com</email></author><category term="tech" /><summary type="html">In our last post we covered how to use S3 and CloudFront to host your Jekyll site on AWS statically, which keeps it fast and very inexpensive to operate. In this post, we will cover how to set up CodePipeline and CodeBuild to automatically rebuild the site and publish it to the web whenever you commit a change to your CodeCommit repository. When you’ve hooked up your website in this way, you will never want to go back to the old way: you can just concentrate on adding content and designing your site without having to concern yourself with how it actually gets live on the web. It’s a wonderful way to work.</summary></entry><entry><title type="html">Hosting Your Jekyll Site on AWS with S3 and CloudFront</title><link href="https://www.kickboxfrownyface.com/tech/static-hosting-jekyll-amzn/" rel="alternate" type="text/html" title="Hosting Your Jekyll Site on AWS with S3 and CloudFront" /><published>2021-03-19T14:22:08+00:00</published><updated>2021-10-17T12:06:08+00:00</updated><id>https://www.kickboxfrownyface.com/tech/static-hosting-jekyll-amzn</id><content type="html" xml:base="https://www.kickboxfrownyface.com/tech/static-hosting-jekyll-amzn/">&lt;p&gt;In our &lt;a href=&quot;/tech/install-jekyll-amzn/&quot;&gt;last post&lt;/a&gt; we described how to install Jekyll
on a Cloud9 environment, with the website source stored in git in CodeCommit.
In this post, we’ll describe how to host that site on AWS statically, via S3
and CloudFront. This is an incredibly cost effective way to host a site – and,
as you’ll see, your web pages will load incredibly fast from anywhere in the
world.&lt;/p&gt;

&lt;h2 id=&quot;ssl-certificates-with-aws-certificate-manager&quot;&gt;SSL Certificates with AWS Certificate Manager&lt;/h2&gt;

&lt;p&gt;Even for static websites like this one, the public expects a secure browsing
experience, so you simply &lt;em&gt;must&lt;/em&gt; host your site over https. To do this, you 
need an SSL certificate for your domain, properly configured for the
webserver that is hosting up your site. In the past, that was a time consuming and
expensive process, but AWS has you covered here: so long as you actually use
the certificate with your website hosted on AWS, it’s free. And it’s pretty
easy.&lt;/p&gt;

&lt;p&gt;Navigate to your AWS console, then go to the AWS Certificate Manager
service. Click on &lt;em&gt;Provision Certificate&lt;/em&gt;, and then – this is very important –
&lt;em&gt;switch to the us-east-1 region&lt;/em&gt;, no matter what your standard region is.
We will be configuring CloudFront to use this cert, and that only works 
with certs that are in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;us-east-1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We’ll be hosting the website at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www.[YOUR DOMAIN].com&lt;/code&gt;, but we may as well
ask for a wildcard cert as long as we are doing this. Further, you’ll want
to protect your bare domain (i.e., with no subdomain) in the same cert:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/tech/jekyll-s3-cf/cert-add-domain.png&quot; alt=&quot;Specify Domains&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Next, you will have to prove that you have ownership of the domain. You can
do this in one of two ways: either via an email process, or a DNS process.
The DNS one is cleaner; all you need to do is set up a CNAME with the 
key and value that ACM provides you with. The Kickbox domain uses DreamHost
as DNS provider, because it’s cheap and good enough, so we created a CNAME
on the domain with the details that ACM gave us:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/tech/jekyll-s3-cf/cert-cname.png&quot; alt=&quot;Adding a CNAME&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It can take some time for your DNS provider to serve it up, and for AWS to notice
that it exists – so you can either get a cup of coffee and come back,
or move on to the next stage while you’re waiting.&lt;/p&gt;

&lt;h2 id=&quot;serverless-framework-on-cloud9&quot;&gt;Serverless Framework on Cloud9&lt;/h2&gt;

&lt;p&gt;Here at Kickbox Frownyface we are big fans of the 
&lt;a href=&quot;https://www.serverless.com/&quot;&gt;Serverless Framework&lt;/a&gt;, which standardizes, to 
some extent, how you interact with cloud providers. Even though we are
all in on AWS, we think it really simplifies maintaining infrastructure
as code. And it’s free.&lt;/p&gt;

&lt;p&gt;So let’s install Serverless on our development box.&lt;/p&gt;

&lt;p&gt;Log into your Cloud9 instance, and then issue this from the terminal:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ npm install -g serverless
...
$ sls --version
Framework Core: 2.18.0
Plugin: 4.4.2
SDK: 2.3.2
Components: 3.4.7
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;configure-your-s3-bucket&quot;&gt;Configure your S3 Bucket&lt;/h2&gt;

&lt;p&gt;We will be using S3’s static website hosting feature for the site that
Jekyll is going to build for us. Instead of going into the AWS console
and clicking around, we are going to create a YAML file that describes
our S3 bucket fully, and then have Serverless take care of spinning it
up and maintaining it for us.&lt;/p&gt;

&lt;p&gt;The infrastructure configuration files for our website are going to go
into a directory &lt;em&gt;sibling&lt;/em&gt; of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;website&lt;/code&gt;. So create a directory parallel
to where our Jekyll work goes:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;├── kickbox-web
│   ├── infra
│   └── website
│       ├── 404.html
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Into this infra directory we’ll put &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;s3.yml&lt;/code&gt;, which will specify an
S3 bucket that will contain the Jekyll site as built (i.e., the static
HTML pages, styleshets, images, and so on for our site). It looks like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;na&quot;&gt;Resources&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# The bucket holding the static site&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;JekyllBucket&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AWS::S3::Bucket&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;BucketName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${self:custom.s3Bucket}&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;AccessControl&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;PublicRead&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;WebsiteConfiguration&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;IndexDocument&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;index.html&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ErrorDocument&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;index.html&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;CorsConfiguration&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;CorsRules&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;AllowedHeaders&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;*'&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;AllowedMethods&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;GET&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;HEAD&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;AllowedOrigins&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;*'&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;JekyllBucketPolicy&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;AWS::S3::BucketPolicy&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;DependsOn&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;JekyllBucket&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Bucket&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;!Ref&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;JekyllBucket&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;PolicyDocument&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;Statement&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Sid&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;PublicReadGetObject&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Effect&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Allow&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Principal&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;*&quot;&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;s3:GetObject&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Resource&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;arn:aws:s3:::${self:custom.s3Bucket}/*&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This sets up a bucket that S3 can host statically, with public read
access. Note that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;${self:custom.s3Bucket}&lt;/code&gt; and the like are going
to be filled in appropriately by Serverless when we deploy our 
infrastructure.&lt;/p&gt;

&lt;p&gt;(A quick note about public read: normally you would want to have
CloudFront, our CDN, to be the only entity allowed to read from that
S3 bucket. However, the way Jekyll builds out directories and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;index.html&lt;/code&gt;
files means this won’t fly. A public S3 bucket is OK for us because
our website is fully public anyway – but if your use case is different,
you may want to make a different choice.)&lt;/p&gt;

&lt;h1 id=&quot;configure-cloudfront-as-cdn&quot;&gt;Configure CloudFront as CDN&lt;/h1&gt;

&lt;p&gt;Next, we will set up CloudFront to serve files from that S3 bucket.
To do this, we’ll create another YAML configuration file, this time for
the distribution:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;na&quot;&gt;Resources&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;## The CF distribution for the front end&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;JekyllCloudFrontDistribution&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AWS::CloudFront::Distribution&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;DistributionConfig&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;Origins&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;DomainName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${self:custom.s3Bucket}.s3-website.${self:provider.region}.amazonaws.com&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;WebApp&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;CustomOriginConfig&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;HTTPPort&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;80&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;HTTPSPort&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;443&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;OriginProtocolPolicy&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;http-only&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;Enabled&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;Aliases&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${self:custom.subdomain}.${self:custom.domain}&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;DefaultRootObject&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;index.html&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;CustomErrorResponses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;ErrorCode&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;404&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;ResponseCode&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;200&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;ResponsePagePath&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/404.html&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;# The default cache behavior: standard pages&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;DefaultCacheBehavior&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;AllowedMethods&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;GET&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;HEAD&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;OPTIONS&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;## The origin id defined above&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;TargetOriginId&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;WebApp&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;ForwardedValues&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;QueryString&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;Cookies&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;Forward&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;none&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;ViewerProtocolPolicy&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;redirect-to-https&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;Compress&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;DefaultTTL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2628000&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;MinTTL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2628000&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;## The certificate to use when viewers use HTTPS to request objects.&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ViewerCertificate&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;AcmCertificateArn&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${self:custom.sslCertArn}&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;MinimumProtocolVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;TLSv1.2_2018&quot;&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;SslSupportMethod&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;sni-only&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;## In order to print out the hosted domain via `serverless info` we need to define the DomainName output for CloudFormation&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Outputs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;JekyllCloudFrontDistributionOutput&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Fn::GetAtt'&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;JekyllCloudFrontDistribution&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;DomainName&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;A few things to note about this file: we have a very long “time to live”
set up for these files. That’s because our site is, after all, static – and
performance will be better (and cost lower) the longer those files are at
CloudFront’s edge. This means every time we rebuild our Jekyll site, we need
to invalidate the CloudFront cache. (We will do this automatically in our
next step; stay tuned.)&lt;/p&gt;

&lt;h1 id=&quot;serverless-configuration&quot;&gt;Serverless Configuration&lt;/h1&gt;

&lt;p&gt;Finally, we a single configuration file for the Serverless framework that 
references these two files. The standard name for this file is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;serverless.yml&lt;/code&gt;,
so create one in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;infra&lt;/code&gt; directory that looks like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;na&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jekyll-web&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;custom&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;account&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[YOUR&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;ACCOUNT&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;ID]'&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;certificate&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CERTIFICATE ID&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;YOUR DOMAIN&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;sslCertArn&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;arn:aws:acm:us-east-1:${self:custom.account}:certificate/${self:custom.certificate}&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${opt:stage, self:provider.stage}&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;subdomain&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${self:provider.environment.${self:custom.stage}_subdomain}&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;s3Bucket&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;BUCKET NAME THAT WILL HOLD YOUR SITE&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-${self:custom.stage}&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;provider&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;aws&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;prod&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;region&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;YOUR PREFERRED AWS REGION&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;dev_subdomain&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;dev&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;prod_subdomain&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;www&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;resources&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${file(s3.yml)}&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${file(cloudfront.yml)}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;What wer’e doing in this file is setting up values for the variables in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;s3.yml&lt;/code&gt;
and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cloudfront.yml&lt;/code&gt; files. You will obviously need to edit this for your own circumstances:
you will need to put in your AWS account ID, what region you run your infrastructure
in, and so on.&lt;/p&gt;

&lt;p&gt;The only non-obvious part step here is that you need the SSL certificate ARN (that stands for
&lt;em&gt;AWS Resource Name&lt;/em&gt;), so that CloudFront knows how to secure your site. How do we get this?&lt;/p&gt;

&lt;p&gt;By now, Amazon Certificate Manager should have verified that you own the domain in 
question, by looking for that CNAME setting in DNS that you set up at the start of this
process. So pop back over to your console and see if the certificate has been issued.
If so, you can click on &lt;em&gt;Details&lt;/em&gt; and see this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/tech/jekyll-s3-cf/cert-details.png&quot; alt=&quot;Certificate Details&quot; /&gt;)&lt;/p&gt;

&lt;p&gt;You want the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Identifier&lt;/code&gt;, smudged out here.&lt;/p&gt;

&lt;h2 id=&quot;deploy-your-infrastructure&quot;&gt;Deploy Your Infrastructure&lt;/h2&gt;

&lt;p&gt;At this point our project directory looks like the following, with the Jekyll source
in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;website&lt;/code&gt; and the infrastructure support in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;infra&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;├── kickbox-web
│   ├── infra
│   │   ├── cloudfront.yml
│   │   ├── s3.yml
│   │   └── serverless.yml
│   └── website
│       ├── 404.html
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now that the hard work is done, you can simply tell Serverless Framework that you want
your site built out:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ sls deploy 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s it! Serverless will use these files to create CloudFormation scripts and post
them as stacks to your AWS account, using the privileges in your Cloud9 instance.
This will generate an S3 bucket, configured for static website hosting, and a CloudFront
distribution pointing to it, properly configured to be serving up content, securely
via your SSL certificate, at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://www.[YOUR DOMAIN]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;(Of course, you will have to add a CNAME for www pointing to your CloudFront domain,
so that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www.[YOUR DOMAIN]&lt;/code&gt; actually points at your website. If you don’t know how to 
do this, check with your domain registrar.)&lt;/p&gt;

&lt;p&gt;But how, you might ask, do we get our Jekyll site up into that S3 bucket?
Well, that is the topic of our next post,
&lt;a href=&quot;/tech/jekyll-codepipeline&quot;&gt;automatically building and deploying Jekyll on AWS with CodePipeline&lt;/a&gt;.&lt;/p&gt;</content><author><name>Johnny Kickbox</name><email>johnny@kickboxfrownyface.com</email></author><category term="tech" /><summary type="html">In our last post we described how to install Jekyll on a Cloud9 environment, with the website source stored in git in CodeCommit. In this post, we’ll describe how to host that site on AWS statically, via S3 and CloudFront. This is an incredibly cost effective way to host a site – and, as you’ll see, your web pages will load incredibly fast from anywhere in the world.</summary></entry><entry><title type="html">Installing Jekyll on AWS Cloud9 with CodeCommit</title><link href="https://www.kickboxfrownyface.com/tech/install-jekyll-amzn/" rel="alternate" type="text/html" title="Installing Jekyll on AWS Cloud9 with CodeCommit" /><published>2021-02-25T23:40:19+00:00</published><updated>2021-02-25T23:40:19+00:00</updated><id>https://www.kickboxfrownyface.com/tech/install-jekyll-amzn</id><content type="html" xml:base="https://www.kickboxfrownyface.com/tech/install-jekyll-amzn/">&lt;p&gt;Hosting a website like this one on serverless AWS is easy, fast, and dirt cheap.
Combine that with a static website generator like &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; and you have the best of
both worlds: a static website that is easy to contribute to, easy to theme, and which
loads fast no matter where in the world your users are.&lt;/p&gt;

&lt;p&gt;But there’s no doubt that getting everything set up to get everything automated
can be a bit of a headache. So we offer this, the first in a series that shows how we 
at Kickbox Frownyface build our corporate website on AWS, starting with the
&lt;a href=&quot;https://aws.amazon.com/cloud9/&quot;&gt;Cloud9 development environment&lt;/a&gt; for editing, 
and &lt;a href=&quot;https://aws.amazon.com/codecommit/&quot;&gt;CodeCommit&lt;/a&gt; for source code control.&lt;/p&gt;

&lt;p&gt;It should only take you ten or fifteen minutes to follow these steps.&lt;/p&gt;

&lt;p&gt;We hope you find it useful.&lt;/p&gt;

&lt;h2 id=&quot;cloud9-as-jekyll-ide&quot;&gt;Cloud9 as Jekyll IDE&lt;/h2&gt;

&lt;p&gt;It’s shouldn’t surprise anyone that we have a 
&lt;a href=&quot;https://www.kickboxfrownyface.com/about-us/&quot;&gt;pretty small development team at Kickbox Frownyface&lt;/a&gt;,
and we can’t spend a lot on hardware. Cloud9 is a perfect fit for us when it comes to
editing the website: there’s no hardware to install or machines to configure, and we only
pay for the IDE by the minute, for the time we spend editing the website. 
We don’t need to install a virtual machine or have a Linux
box of our own, or any of that nonsene. And
we don’t have to worry about dirtying up our own game dev machines by installing
other software, which is a huge win for us.&lt;/p&gt;

&lt;h3 id=&quot;create-the-cloud9-environment&quot;&gt;Create the Cloud9 Environment&lt;/h3&gt;

&lt;p&gt;Your first step is to create a new Cloud9 environment. This will be the Linux box that we’ll
install Jekyll to.&lt;/p&gt;

&lt;p&gt;Sign in to your AWS console and then navigate to the Cloud9 service. Click the 
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Create Environnment&lt;/code&gt; button, and fill out the form. Here are our settings:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/tech/jekyll-on-aws/cloud9-environment-settings.jpg&quot; alt=&quot;Create a Cloud9 Environment&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We choose the cheapest instance type, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;t2.micro&lt;/code&gt;, because Jekyll doesn’t need much
horsepower. We name ours &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kickbox-web&lt;/code&gt; because of our internal standards.&lt;/p&gt;

&lt;h3 id=&quot;install-ruby-and-jekyll&quot;&gt;Install Ruby and Jekyll&lt;/h3&gt;

&lt;p&gt;Open the Cloud9 IDE. You’ll see a terminal window in the lower part
of your screen. This is a Linux prompt that we can use to install all the tools we’ll
need onto the EC2 instance that is backing our Cloud9.&lt;/p&gt;

&lt;p&gt;Here are the steps we took to do this. First we installed Ruby, which Jekyll needs:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ sudo amazon-linux-extras install ruby2.6
$ sudo yum install ruby-devel -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If that worked Ruby will tell you its version, something like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ ruby -v
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next up is Jekyll:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ gem update --system
$ gem install jekyll bundler
$ bundle config path ~/.gem
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If that worked Jekyll will report its version too:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ jekyll -v
jekyll 4.2.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you run into trouble, things may have changed since we wrote this post. Check out the 
&lt;a href=&quot;https://jekyllrb.com/docs/installation/&quot;&gt;Jekyll installation instructions&lt;/a&gt;,
which will have up to date instructions.&lt;/p&gt;

&lt;h2 id=&quot;code-commit&quot;&gt;Code Commit&lt;/h2&gt;

&lt;p&gt;Next up, we’ll create a git repository which will store our website source – that is,
the Jekyll website files that Jekyll will build into HTML files for our website.&lt;/p&gt;

&lt;p&gt;First, you need to make sure that 
the user you are running Cloud9 as has git privs to code commit so you can just &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git push&lt;/code&gt; to 
the repo from the IDE. So make sure that your IAM user, and the other editors’ users, belong to a
group that includes the correct policies. To do this:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Navigate to the IAM service&lt;/li&gt;
  &lt;li&gt;Choose &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Groups&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Select an existing group, or click on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Create New Group&lt;/code&gt; and type in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;website-editors&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Check the box next to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AWSCodeCommitFullAccess&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Complete the wizard and create the group&lt;/li&gt;
  &lt;li&gt;Click into the group and click on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Add Users to Group&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Add yourself and any other IAM users to the group&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Next, head back over to your AWS console and navigate to CodeCommit. 
You will create a new Git repository, which will hold the source code and content for the
Jekyll website. We call ours &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kickbox-web&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/tech/jekyll-on-aws/code-commit-new-repo.jpg&quot; alt=&quot;Create a new repo&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now we just go back into Cloud9 and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git clone&lt;/code&gt; it into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/environment&lt;/code&gt; directory that you 
have on that Linux box. Copy the HTTPS path and then issue:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ git clone https://git-codecommit.[YOUR REGION].amazonaws.com/v1/repos/[YOUR REPO NAME]
Cloning into '[YOUR REPO NAME]'...
warning: You appear to have cloned an empty repository.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The warning doesn’t bother us, because it is expected to be empty. Let’s finish configuring
your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git&lt;/code&gt; settings and push up a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;README.md&lt;/code&gt; file:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ cd [YOUR REPO NAME]
$ git config --global user.name &quot;[YOUR NAME]&quot;
$ git config --global user.email [YOUR EMAIL]
$ cat &amp;gt;README.md
A website.
[CTRL-D]
$ git add README.md 
$ git commit -m &quot;Initial commit&quot;
$ git push
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now that our tools are all set up, let’s just create a new empty Jekyll website and verify
that everything is ready.&lt;/p&gt;

&lt;h2 id=&quot;create-the-new-website&quot;&gt;Create the New Website&lt;/h2&gt;

&lt;p&gt;We will ask Jekyll to build its new site in a directory named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;website&lt;/code&gt;, rather than the root.
The reason for this will become clear in future steps – we also want an infrastructure directory,
into which we can put our &lt;a href=&quot;https://aws.amazon.com/cloudformation/&quot;&gt;CloudFormation&lt;/a&gt; scripts.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ jekyll new website
...
New jekyll site installed in /home/ec2-user/environment/[YOUR REPO NAME]/website. 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If that worked, you’ll see that your file tree in Cloud9 is now full of the files that are
part of a standard Jekyll website.&lt;/p&gt;

&lt;p&gt;Let’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cd&lt;/code&gt; into that directory and ask Jekyll to host it during our development:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ cd website
$ jekyll serve -H $IP -P $PORT --baseurl &quot;&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Keep your eyes open – Cloud9 will show you a little window with a hyperlink on it to your
website running in dev. If you click on it you should see your new Jekyll website:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/tech/jekyll-on-aws/welcome-jekyll.jpg&quot; alt=&quot;New website&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;commit-to-git&quot;&gt;Commit to Git&lt;/h2&gt;

&lt;p&gt;Don’t forget to commit the starter website to CodeCommit.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ git add .
$ git commit -m &quot;Initial empty Jekyll site&quot;
$ git push
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;So what have we accomplished? Well, we have a new cloud-based IDE all set up for us to 
build out our Jekyll website, and we have a git repository in which to store it.&lt;/p&gt;

&lt;p&gt;Next step: &lt;a href=&quot;/tech/static-hosting-jekyll-amzn/&quot;&gt;hosting it on S3 and Cloudfront&lt;/a&gt;.&lt;/p&gt;</content><author><name>Johnny Kickbox</name><email>johnny@kickboxfrownyface.com</email></author><category term="tech" /><summary type="html">Hosting a website like this one on serverless AWS is easy, fast, and dirt cheap. Combine that with a static website generator like Jekyll and you have the best of both worlds: a static website that is easy to contribute to, easy to theme, and which loads fast no matter where in the world your users are.</summary></entry></feed>