<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Rolf Streefkerk</title>
    <description>The latest articles on Forem by Rolf Streefkerk (@rolfstreefkerk).</description>
    <link>https://forem.com/rolfstreefkerk</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F311542%2F1d2e6fa7-cab5-4277-90fb-7e299f32e6f9.jpeg</url>
      <title>Forem: Rolf Streefkerk</title>
      <link>https://forem.com/rolfstreefkerk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/rolfstreefkerk"/>
    <language>en</language>
    <item>
      <title>How to implement the Twitter - X API using Python FastAPI</title>
      <dc:creator>Rolf Streefkerk</dc:creator>
      <pubDate>Wed, 09 Oct 2024 09:08:07 +0000</pubDate>
      <link>https://forem.com/rolfstreefkerk/how-to-implement-the-twitter-x-api-using-python-fastapi-55da</link>
      <guid>https://forem.com/rolfstreefkerk/how-to-implement-the-twitter-x-api-using-python-fastapi-55da</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;While building my latest app, insightprose.com, I’ve gathered quite a few learnings in its 3 month development cycle. I wanted to start with Twitter integration since it’s a key component in the application feature-set of InsightProse.&lt;/p&gt;

&lt;p&gt;I’ll be discussing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the current state of X API v2.0,&lt;/li&gt;
&lt;li&gt;where the Twitter API came from (version 1.1) briefly,&lt;/li&gt;
&lt;li&gt;and how to implement OAuth2 auth flow with X API version 2.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In case you’re interested in a quick run-down of what InsightProse actualy is, keep reading on. Alternatively:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Twitter - X API

&lt;ul&gt;
&lt;li&gt;v1.1 API deprecation controversy&lt;/li&gt;
&lt;li&gt;v2.0 API Implementation&lt;/li&gt;
&lt;li&gt;Common questions and answers&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Conclusion&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  What is InsightProse?
&lt;/h3&gt;

&lt;p&gt;InsightProse is a social media and SEO content generator, using your original long form article, to distill one or more concepts into short articles.&lt;/p&gt;

&lt;p&gt;These short articles are called Insights, these can then be used to create:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Twitter threads&lt;/strong&gt;, generates a Twitter thread of maximum 3 tweets to enhance social media reach, and consequently improve readership of the original article. These threads can be scheduled up to 1 month in advance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LinkedIn Articles&lt;/strong&gt;, generates a short article of maximum 3000 characters that explains, in your branding style, a concept from the original article with the intention of getting people to read that original long form article.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blog posts&lt;/strong&gt;; can be placed on a blog or website in order to enhance SEO, improve keyword relevance, and act as more concise Questions and Answers article that references and mirrors the original article. This format includes original code blocks (if any) and images where relevant for optimal concept explanation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All of this content takes into account:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Your brand profile&lt;/strong&gt;:. In other words, how you formulate text, how you explain ideas, and the target audience. This is to ensure the generated content does not read like AI generated content, but it’s like you’ve written it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keywords from the original article:&lt;/strong&gt; In order to ensure relevance and SEO improvement, keywords from the original article are used appropriately and repeatedly in case you want to repost the insight prose on your blog for SEO purposes.&lt;/li&gt;
&lt;li&gt;It takes into account &lt;strong&gt;“Questions and Answers&lt;/strong&gt;” format that is used by many to search for answers using Google or other search engines. Again, with the purpose to improve relevance for those specific sets of keywords.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;InsightProse helps you promote your original content such that you can focus on long form content writing.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Twitter - X API
&lt;/h2&gt;

&lt;h3&gt;
  
  
  v1.1 API deprecation controversy
&lt;/h3&gt;

&lt;h4&gt;
  
  
  The moment of v1.1 deprecation
&lt;/h4&gt;

&lt;p&gt;Twitter / X used to be generous with its API usage towards developers, likely because they had income from advertisers primarily and no subscription services.&lt;/p&gt;

&lt;p&gt;Now, this model has changed to a subscription oriented model with most advertisers dropping off. That has also affected the “user friendliness” towards developers. In a very negative way.&lt;/p&gt;

&lt;p&gt;It started with the official announcement back in April 2023 the v1.1 API was being deprecated &lt;sup id="fnref1"&gt;1&lt;/sup&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Today, we are deprecating our Premium v1.1 API, including Premium Search and Account Activity API.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You’ll notice in the thread of this announcement that there’s no love for this change, and that’s because the fees are not reasonable whatsoever.&lt;/p&gt;

&lt;h4&gt;
  
  
  The new rate limits and pricing
&lt;/h4&gt;

&lt;p&gt;The issue starts with limits to posting Tweets on behalf of customers that have been severely reduced for the free access variant of the API &lt;sup id="fnref2"&gt;2&lt;/sup&gt; &lt;sup id="fnref3"&gt;3&lt;/sup&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;v1.1: 300 posts per 3 hours = 2400 posts per 24 hours.&lt;/li&gt;
&lt;li&gt;v2.0: 50 posts per 24 hours.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a factor of 48(!) reduction in Tweet post allowance. In order to mitigate some of these rate limiting issues, you can upgrade to the “Basic” X API.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;1667 Tweets per 24 hours costing 100USD/month &lt;sup id="fnref4"&gt;4&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can imagine if you’re running a small SAAS product. In this case, 100 USD, is double the price of my infrastructure running cost on Digital Ocean. Double!&lt;/p&gt;

&lt;p&gt;To further make the point, my infrastructure is a Kubernetes 2 Node cluster. For many developers that use Firebase and a free static site hosting solution such as AWS S3 or Cloudflare pages. They will pay near 0 USD per month to get bootstrapped.&lt;/p&gt;

&lt;h4&gt;
  
  
  The consequences, and my recommendation to X
&lt;/h4&gt;

&lt;p&gt;This pricing means that posting Tweets on behalf of your customer needs to be severely capped or put on higher pricing tiers to get to the sufficient revenue to make it sustainable to pay 1200 USD / year to X.&lt;/p&gt;

&lt;p&gt;I’m hoping that X will revise its pricing to considering smaller SAAS products and companies use-cases and enable them to integrate with X at reasonable prices.&lt;/p&gt;

&lt;p&gt;I would recommend the following subscription tier to be added:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The introduction of a “Startup” tier 20 USD/month subscription would cater to starting business owners that want to built a quality service around the X eco-system. The current “Basic” 100 USD/month subscription, and “Free” options, are too expensive and too restrictive respectively.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  v2.0 API Implementation
&lt;/h3&gt;

&lt;p&gt;The basic OAuth2.0 implementation flow&lt;sup id="fnref5"&gt;5&lt;/sup&gt; for X is demonstrated in the following diagram:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo9wdltlk4ip3ls0gh17u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo9wdltlk4ip3ls0gh17u.png" alt="Twitter - X API OAuth2.0 implementation flow" width="629" height="628"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Login with X button on your website.&lt;/li&gt;
&lt;li&gt;Login is redirected to your API service.&lt;/li&gt;
&lt;li&gt;Your API;
a. forwards X Authorization request by;
b. redirecting the user to the X website,
c. to request scope Authorization.&lt;/li&gt;
&lt;li&gt;The user has Authorized your application to access the requested scope of information and access to X user account details.&lt;/li&gt;
&lt;li&gt;Request authorization ‘code’ forwarded to your API ‘/auth/x’ service.&lt;/li&gt;
&lt;li&gt;Your auth X service receives the user data and X details such as username and refresh and access tokens through the coded response from the X API.&lt;/li&gt;
&lt;li&gt;Return your application refresh and access tokens to signal authorization is completed, user is logged in.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  API /login/x (Step 2, 3, and 4)
&lt;/h4&gt;

&lt;p&gt;The function of this API is to forward the user request to X such that they can authorize your APP to access the user’s account data and to post on behalf of this user.&lt;/p&gt;

&lt;p&gt;In your FastAPI implementation you probably have a centralized &lt;code&gt;api.py&lt;/code&gt; file that you add all individual API routes to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;api_router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;APIRouter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;api_router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include_router&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;login&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;login&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;./endpoints/login.py&lt;/code&gt; I would have the following route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@router.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/login/x&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;login_twitter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Handle Twitter login using redirect to Twitter.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;twitter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initiate_twitter_login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then to create the redirect url, we would do the following within the initiate_twitter_login function:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;generate_oauth_params&lt;/strong&gt;: create the required code verifier, challenge and state parameters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;create_authorize_url&lt;/strong&gt;: are used to create the full redirect url to X API, including the scope. The minimum scope to post Tweets on behalf of your users: tweet.read, users.read, tweet.write. The offline.access scope is to get a refresh_token from X.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;secrets&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi.responses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RedirectResponse&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_generate_oauth_params&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;code_verifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;token_urlsafe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;code_challenge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlsafe_b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code_verifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;rstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;token_urlsafe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;code_verifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code_challenge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_create_authorize_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code_challenge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;response_type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;client_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TWITTER_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;redirect_uri&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TWITTER_CALLBACK_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;code_challenge&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;code_challenge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;code_challenge_method&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;S256&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;scope&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tweet.read users.read tweet.write offline.access&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://x.com/i/oauth2/authorize?&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;urlencode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initiate_twitter_login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;code_verifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code_challenge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_generate_oauth_params&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;RedirectResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;_create_authorize_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code_challenge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This redirect will present you with a request screen from X;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;initiated by step 3,&lt;/li&gt;
&lt;li&gt;in step 4 the user authorizes the app,&lt;/li&gt;
&lt;li&gt;which leads to the request send by X as step 5:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5jqmyow3dt67qbkqu9zu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5jqmyow3dt67qbkqu9zu.png" alt="X Authorize user screen" width="400" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  API /auth/x (Steps 5, 6 and 7)
&lt;/h4&gt;

&lt;p&gt;This endpoint receives the authorization from X, this is why you need to configure the callback API in the X settings&lt;sup id="fnref6"&gt;6&lt;/sup&gt; such that X knows where to forward this API call to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@router.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/auth/twitter&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;auth_twitter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_db&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="n"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;refresh_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;twitter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handle_twitter_callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;RedirectResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FRONTEND_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/app/auth/callback?access_token=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;access_token&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;refresh_token=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;refresh_token&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This API takes care of validation of the OAuth state secret that we created in the first API, which should match here.&lt;/p&gt;

&lt;p&gt;Hence, we start with a &lt;code&gt;state&lt;/code&gt; check to ensure that the request was initiated from this session and not from somewhere else.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;code&lt;/code&gt; contains the X tokens, because we requested the &lt;code&gt;offline.access&lt;/code&gt; scope we also get a refresh token next to the regular access token.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_twitter_callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query_params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;oauth_state&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invalid state parameter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query_params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No authorization code provided&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;token_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;_exchange_code_for_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;code_verifier&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;twitter_user_info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;get_twitter_user_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;access_token&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="c1"&gt;# Create your Application user with X details here
&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;code_verifier&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;oauth_state&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# access_token, refresh_token
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_refresh_token&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To receive this; we execute &lt;code&gt;token_data = await _exchange_code_for_token(code, request.session.get('code_verifier'))&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_exchange_code_for_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code_verifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://api.x.com/2/oauth2/token&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;grant_type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;authorization_code&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;client_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TWITTER_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;redirect_uri&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TWITTER_CALLBACK_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;code_verifier&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;code_verifier&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;application/x-www-form-urlencoded&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BasicAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TWITTER_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TWITTER_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;_make_twitter_api_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the received access token, we can pull in the user data using &lt;code&gt;get_twitter_user_info(token_data['access_token'])&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_get_twitter_user_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://api.twitter.com/2/users/me&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user.fields&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id,name,username,profile_image_url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;access_token&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;_make_twitter_api_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you have the Twitter / X user data, you create your application user profile with your application access credentials and return that to your user and they’re logged in.&lt;/p&gt;

&lt;h4&gt;
  
  
  Refresh token cron job
&lt;/h4&gt;

&lt;p&gt;Since OAuth2.0 has limited access token validity time, 7200 seconds in case of Twitter / X OAuth2.0. We need to manage automatic renewal of this token in the background.&lt;/p&gt;

&lt;p&gt;I recommend using a task scheduling system or a cron job that automatically checks your user table or token issuance table for expired / about to be expired access tokens.&lt;/p&gt;

&lt;p&gt;In my application I’m using apscheduler&lt;sup id="fnref7"&gt;7&lt;/sup&gt; to schedule tasks on the same application server as the API.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;apscheduler&lt;/strong&gt; is a low profile scheduling library that will “attach” itself to the FastAPI application &lt;code&gt;lifespan&lt;/code&gt; event hook.&lt;/p&gt;

&lt;p&gt;If you want to know more about how to use it, let me know on social media!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is configured as a lifespan event&lt;sup id="fnref8"&gt;8&lt;/sup&gt;, that way it will be running in the background from the moment your FastAPI server is online. This lifespan event uses an async context manager to handle the two events; startup, and shutdown (after the yield) to start and stop the scheduler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@asynccontextmanager&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lifespan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Setup code (runs before the app starts)
&lt;/span&gt;    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;wait_for_db&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;scheduler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_scheduler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;setup_scheduler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Application startup completed successfully&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error during application startup: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt;
    &lt;span class="c1"&gt;# Cleanup code (runs when the app is shutting down)
&lt;/span&gt;    &lt;span class="n"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shutdown&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Application shutdown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;lifespan&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;lifespan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROJECT_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;openapi_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OPENAPI_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the task that is defined, we want to run this regularly to refresh expired Twitter / X access tokens:&lt;/p&gt;

&lt;p&gt;Be aware that you need to revoke your access tokens in case you’re refreshing them within the 2 hour lifespan to avoid token refresh failure errors (see &lt;a href=""&gt;Why am I getting refresh token failure with Twitter / X API&lt;/a&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;refresh_all_expired_tokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;users_with_expired_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_crud&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_users_with_expired_tokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;users_with_expired_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;new_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;refresh_twitter_token_by_user_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;new_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Successfully refreshed token for user &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Failed to refresh token for user &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error refreshing token for user &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Common questions and answers
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Why should you use OAuth 2.0 and not Oauth1.0a for Twitter / X API authorization?
&lt;/h4&gt;

&lt;p&gt;OAuth2.0 offers several benefits over v1.0a:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Granular permission management vs. fine-grained:&lt;/li&gt;
&lt;li&gt;Refresh tokens vs. no expiry access tokens.&lt;/li&gt;
&lt;li&gt;Eventual v1.0.a deprecation, requiring migration effort to OAuth2.0&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Generally, security and access controls have improved in version 2.0. However, that does mean you have to manage access token validity in your application automatically (see Refresh token cron job).&lt;/p&gt;

&lt;h4&gt;
  
  
  What is the validity of the refresh token and access tokens for Twitter / X API?
&lt;/h4&gt;

&lt;p&gt;There’s no clear documentation on the official developer.x.com that clarifies expiration of the &lt;code&gt;refresh_token&lt;/code&gt; provided, but apparently it’s 6 months according to one user in the x community&lt;sup id="fnref9"&gt;9&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Access token has a return value with &lt;code&gt;expires_in&lt;/code&gt; set to 7200 seconds, which is 2 hours.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why am I getting refresh token failure with Twitter / X API
&lt;/h4&gt;

&lt;p&gt;To solve this, you need to ensure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;revoke the current active access_token within its 2 hours validity window,&lt;/li&gt;
&lt;li&gt;after the 2 hour window, you can use the refresh_token to renew the access_token without revoking the access_token.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is how you revoken the access_token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;revoke_twitter_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://api.x.com/2/oauth2/revoke&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;token&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;client_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TWITTER_CLIENT_ID&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;application/x-www-form-urlencoded&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BasicAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TWITTER_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TWITTER_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;_make_twitter_api_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is how you refresh the access_token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;refresh_twitter_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://api.twitter.com/2/oauth2/token&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;refresh_token&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;grant_type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;refresh_token&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;client_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TWITTER_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;application/x-www-form-urlencoded&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;# Prepare Basic Auth
&lt;/span&gt;    &lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BasicAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TWITTER_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TWITTER_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;_make_twitter_api_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Which API's do I have access to in the Free version of X API v2.0?
&lt;/h4&gt;

&lt;p&gt;You can see a full listing of your API access when you register an account, in your development dashboard&lt;sup id="fnref10"&gt;10&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;Or you can go to the About X API page that is publicly accessible&lt;sup id="fnref2"&gt;2&lt;/sup&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Unfortunately with the advent of Twitter / X API version 2, the usability for small products and applications has been severely diminished with a high ticket entrance fee of 100 USD per month for the Basic plan. For the Free version, a very limited allowance of 50 Tweets per day.&lt;/p&gt;

&lt;p&gt;There’s been backlash from day one when this new business model was announced, however we haven’t seen X make any moves to amend or improve their API access for smaller startups and businesses with low revenue.&lt;/p&gt;

&lt;p&gt;Luckily, the implementation of the X API is pretty straightforward as I’ve hopefully demonstrated in this article. There are some caveats when it comes to access token / refresh token issues that have been reported online very frequently. But with a proper implementation of access token revoke, before a refresh this should be resolved.&lt;/p&gt;

&lt;p&gt;Have you encountered any problems implementing the new X API v2.0? If so, lets discuss below!&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;&lt;a href="https://x.com/XDevelopers/status/1649191520250245121" rel="noopener noreferrer"&gt;https://x.com/XDevelopers/status/1649191520250245121&lt;/a&gt; - Twitter API v1.1 deprecation announcement ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;&lt;a href="https://developer.x.com/en/docs/x-api/getting-started/about-x-api" rel="noopener noreferrer"&gt;https://developer.x.com/en/docs/x-api/getting-started/about-x-api&lt;/a&gt; Twitter API Access Levels and versions ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;&lt;a href="https://developer.x.com/en/docs/x-api/rate-limits#v2-limits-free" rel="noopener noreferrer"&gt;https://developer.x.com/en/docs/x-api/rate-limits#v2-limits-free&lt;/a&gt; Twitter API v2.0 Rate limits for Free Access ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;&lt;a href="https://developer.x.com/en/docs/x-api/rate-limits#v2-limits-basic" rel="noopener noreferrer"&gt;https://developer.x.com/en/docs/x-api/rate-limits#v2-limits-basic&lt;/a&gt; Twitter API v2.0 Rate limits for Basic Paid Access (100USD / month) ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn5"&gt;
&lt;p&gt;&lt;a href="https://developer.x.com/en/docs/authentication/oauth-2-0/user-access-token" rel="noopener noreferrer"&gt;https://developer.x.com/en/docs/authentication/oauth-2-0/user-access-token&lt;/a&gt; Twitter OAuth2.0 Authorization Code Flow with PKCE ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn6"&gt;
&lt;p&gt;&lt;a href="https://developer.twitter.com/en/portal/dashboard" rel="noopener noreferrer"&gt;https://developer.twitter.com/en/portal/dashboard&lt;/a&gt; Twitter Developer Dashboard ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn7"&gt;
&lt;p&gt;&lt;a href="https://apscheduler.readthedocs.io/en/3.x/#" rel="noopener noreferrer"&gt;https://apscheduler.readthedocs.io/en/3.x/#&lt;/a&gt; Advanced Python Scheduler ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn8"&gt;
&lt;p&gt;&lt;a href="https://fastapi.tiangolo.com/advanced/events/" rel="noopener noreferrer"&gt;https://fastapi.tiangolo.com/advanced/events/&lt;/a&gt; FastAPI lifespan events ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn9"&gt;
&lt;p&gt;&lt;a href="https://devcommunity.x.com/t/access-token-expires-in/164425/13" rel="noopener noreferrer"&gt;https://devcommunity.x.com/t/access-token-expires-in/164425/13&lt;/a&gt; Access token expires_in and Refresh token expiry ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn10"&gt;
&lt;p&gt;&lt;a href="https://developer.twitter.com/en/portal/products/free" rel="noopener noreferrer"&gt;https://developer.twitter.com/en/portal/products/free&lt;/a&gt; Twitter Developer Dashboard - Your API endpoint access overview ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>api</category>
      <category>python</category>
      <category>socialmedia</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Kubernetes Foundations and Cluster Setup with K3s</title>
      <dc:creator>Rolf Streefkerk</dc:creator>
      <pubDate>Tue, 06 Aug 2024 13:00:00 +0000</pubDate>
      <link>https://forem.com/rolfstreefkerk/kubernetes-foundations-and-cluster-setup-with-k3s-15i6</link>
      <guid>https://forem.com/rolfstreefkerk/kubernetes-foundations-and-cluster-setup-with-k3s-15i6</guid>
      <description>&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;TLDR: to get right into the article, go here&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In my own personal Cloud journey, Kubernetes never really played much of a role up until very recently.&lt;/p&gt;

&lt;p&gt;My interests were with public Cloud providers such as AWS for their accessibility and managed service offerings they provide to significantly increase productivity.&lt;/p&gt;

&lt;p&gt;But, when i actually started to run production workloads on AWS I noticed the significant cost involved in fairly small sized deployments. &lt;/p&gt;

&lt;p&gt;As an example we had an Amazon merchandise app called Trademerch.io that cost us around 130USD per month to run when we started and finished our rework about 5 years ago  (ElasticSearch cluster with 1 node, 2 RDS databases, 2 EC2 servers of which 1 was on a Cron recurring Job). Fast forward to today, with a virtually unchanged architecture we were still paying 130USD per month using the same AWS services, sizing and usage patterns. &lt;/p&gt;

&lt;p&gt;The cost aspect of the public cloud is not something new, but it became painfully obvious that AWS is not there to return the favor, but instead seeks to optimize its own revenue primarily (although they claim otherwise!) This experience has left me jaded when it comes to public Cloud providers in general.&lt;/p&gt;

&lt;p&gt;How can this be done differently? &lt;/p&gt;

&lt;p&gt;Enter Kubernetes and the “deploy where you want” paradigm. Once i finally started looking into Kubernetes about a month ago, I noticed immediately that it’s essentially a Platform as a Service (PAAS) that you fully control. However, most deployment options are NOT cheap (again AWS, but also Azure and Google itself). &lt;/p&gt;

&lt;p&gt;Most public clouds will charge 75 USD to just run the control plane (the “controller” of the Kubernetes cluster) and then you still need to add Nodes to run your workloads (Virtual machines such as EC2). Luckily there are alternatives out there such as Digital Ocean, and Oracle that will allow you to run Kubernetes much more cost effectively.&lt;/p&gt;

&lt;p&gt;Next to this, you have the option to self host anywhere you like, which negates issues of vendor lock with traditional public cloud managed services. Particularly Serverless offerings, since they’re custom built for their own Cloud.&lt;/p&gt;

&lt;p&gt;Now I’ll admit, cost to entry with Serverless can be 0 USD, when your app has no users or very few users it’s possible to run at at zero or near zero cost. But as soon as you get free of the “Free Tier”, costs starts to accumulate and migrating out is a more time consuming affair.&lt;/p&gt;

&lt;p&gt;Kubernetes offers a way to host your applications and infrastructure in a way that is fully in your control, and enables you to scale to virtually millions of containers (parent company Google run this size, regular people of course will not ever come close!).&lt;/p&gt;

&lt;p&gt;My reasoning is, if I can control the infrastructure stack I can learn to scale it to sufficient size and never have to migrate out of the stack. That provides me with transparency and clarity on the future roadmap. Granted, I will still use Serverless where it makes sense.&lt;/p&gt;

&lt;p&gt;Currently for my insightprose.com app I use CloudFlare to host my static website and some of their serverless function capability to run forms. But much of the backend will run on Digital Ocean using 4-6 Node Kubernetes cluster that will run everything needed from Monitoring to Databases and API’s.&lt;/p&gt;

&lt;p&gt;I see Kubernetes as an investment for that reason, and because it has been proven to be extremely successful at what it aims to provide. The down side is the up-front investment in time and money, for each person that cost-benefit analysis may turn out different.&lt;/p&gt;

&lt;p&gt;For me it’s a resounding yes to invest in this technology, and to learn how to best use it for my benefit.&lt;/p&gt;

&lt;p&gt;I hope this series of articles will impart you with enthusiasm to learn and use Kubernetes for your own projects and products, or perhaps even aid you in your current activities!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A humble note; since I’m new with Kubernetes, I expect to make mistakes. &lt;/p&gt;

&lt;p&gt;If you spot any, or have advice to improve the content. I’d be very appreciative if you would contact me via Twitter or via my contact form.&lt;/p&gt;

&lt;p&gt;Thanks for your support!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Thanks and enjoy your learning journey through Kubernetes!&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Kubernetes was first introduced in 2014 by Google and has since become the de facto standard for large scale container orchestration, powering anything from small startups to conglomerates like Google itself.&lt;/p&gt;

&lt;p&gt;Kubernetes has rose to prominence because of its ability to deploy, scale and manage complex application across multiple containers and hosts. Since 2014 applications have grown considerably more complex. With that growth, new service paradigms such as the micro service architecture has gained in popularity. &lt;/p&gt;

&lt;p&gt;In the most recent Cloud Native Annual Survey of 2023, &lt;sup id="fnref1"&gt;1&lt;/sup&gt; , Kubernetes was still seeing year over year growth with a dramatic increase in production use from 58% in 2022 to 66% in 2023 of total respondents.&lt;/p&gt;

&lt;p&gt;Kubernetes offers the following main features:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Automated deployment and scaling&lt;/strong&gt;: Easily scale your application up or down based on demand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-healing capabilities&lt;/strong&gt;: Automatically restarts failed containers and replaces or reschedules containers when nodes die.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service discovery and load balancing&lt;/strong&gt;: Kubernetes can expose a container using the DNS name or its own IP address. If traffic to a container is high, Kubernetes is able to load balance and distribute the network traffic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage orchestration&lt;/strong&gt;: Automatically mount the storage system of your choice, whether from local storage, public cloud providers, or network storage systems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secret and configuration management&lt;/strong&gt;: Deploy and update secrets and application configuration without rebuilding your image and without exposing secrets in your stack configuration.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Real world use cases: Airbnb's Kubernetes Journey
&lt;/h2&gt;

&lt;p&gt;Airbnb, the popular online marketplace for lodging and tourism experiences, provides an excellent example of Kubernetes in action. As their platform grew, they faced challenges with their monolithic architecture. They needed a system that could handle their rapidly increasing scale while allowing for more frequent and reliable deployments.&lt;/p&gt;

&lt;p&gt;Airbnb adopted Kubernetes &lt;sup id="fnref2"&gt;2&lt;/sup&gt; to containerize their applications and move towards a microservices architecture. This transition allowed them to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Increase deployment frequency: exponential growth in deployments was achieved all the way up to 125,000 production deployments per year.&lt;/li&gt;
&lt;li&gt;Increased resource usage efficiency: scheduling improvements reduced computing resource waste. &lt;/li&gt;
&lt;li&gt;Enhanced reliability and fault tolerance.&lt;/li&gt;
&lt;li&gt;Improved configuration management:  version, refactor, and automatically update configuration across hundreds of services has dramatically improved efficiency in this regard.&lt;/li&gt;
&lt;li&gt;Increased developer productivity: via standardization and automations repetitive task reductions were achieved.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While adopting Kubernetes required investment in tooling and processes for Airbnb, it provided key improvements across the board from development experience to standardization and increased deployment efficiency.&lt;/p&gt;

&lt;p&gt;In this article, we'll be using k3s 1.30, a lightweight Kubernetes distribution that's perfect for learning, development, and edge computing. K3s provides a fully compliant Kubernetes distribution with a smaller footprint, making it easier to get started and experiment with Kubernetes concepts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Containers: A Quick Recap
&lt;/h2&gt;

&lt;p&gt;Before we dive deep into Kubernetes, let's revisit the concept of containers, the building blocks of any Kubernetes system.&lt;/p&gt;

&lt;p&gt;Containers are lightweight, standalone, executable packages that include everything needed to run software. This includes the code, runtime, system tools, libraries, and settings. Containers are isolated from one another and the underlying infrastructure, ensuring consistency across environments.&lt;/p&gt;

&lt;p&gt;The key benefits of containers include:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Consistency across environments&lt;/strong&gt;: Containers run the same regardless of where they're deployed, eliminating the "it works on my machine" problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Isolation and resource efficiency&lt;/strong&gt;: Containers share the host OS kernel but are isolated from each other. This allows for efficient resource utilization while maintaining security.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quick startup and scalability&lt;/strong&gt;: Containers can start up in seconds and are easy to replicate, making them ideal for scalable applications.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version control and component reuse&lt;/strong&gt;: Container images can be versioned, shared, and reused, promoting better development practices and code reuse.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While Docker popularized containers, Kubernetes works with various container runtimes. In k3s, containerd is used as the default container runtime.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📌 &lt;strong&gt;Deep Dive for Intermediate Users&lt;/strong&gt;: &lt;br&gt;
If you're familiar with containers, you might be interested in the Open Container Initiative (OCI) standards. These standards ensure interoperability between different container technologies. K3s uses containerd, which is OCI-compliant, allowing it to work with containers created by Docker and other OCI-compliant tools &lt;sup id="fnref3"&gt;3&lt;/sup&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Kubernetes Architecture: The Blueprint of Orchestration
&lt;/h2&gt;

&lt;p&gt;Let’s start with an overview of the Kubernetes architecture to better understand how the major components interact:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3cl6xmcf5hw3jkwi4zsu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3cl6xmcf5hw3jkwi4zsu.png" alt="Kubernetes-overview" width="800" height="542"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The developer or user has access to Kubernetes and everything that’s running on it via the command line tool; &lt;code&gt;kubectl&lt;/code&gt;, this tool has immediate API Server access to the whole cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Control Plane Components
&lt;/h3&gt;

&lt;p&gt;First off we have the major components of the control plane, which is essentially the backbone of the cluster making global decisions around scheduling and events.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;API Server&lt;/strong&gt;:  exposes the API of the control plane and is scaled horizontally (adding more instances) and balancing traffic between them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;etcd&lt;/strong&gt;: highly available key-value cluster storage for all cluster data. On K3s this is a SQLite database.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scheduler&lt;/strong&gt;: schedules newly created Pods with no assigned Node. Takes into account; individual and collective resource requirements, hardware/software/policy constraints, affinity, anti-affinity specifications, data locality etc.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Controller Manager&lt;/strong&gt;: this runs the controller processes of which there are various kinds, a few examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node controller: monitors and responds to nodes going down.&lt;/li&gt;
&lt;li&gt;Job controller: watches for Job objects that are a one-off task, creates Pods to run those tasks to finish the tasks.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cloud Controller Manager:&lt;/strong&gt; (not depicted and optional): these run controllers that are specific to the cloud provider, this includes many different controller types in a single binary just like the “regular” controller manager.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Node Components
&lt;/h3&gt;

&lt;p&gt;For the Nodes, which are the workers of the cluster and most of the time will be virtual servers like for instance EC2 on AWS or Droplets on Digital Ocean.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Kubelet:&lt;/strong&gt; Kubelet is like a “node agent” that runs on each Node and that can register the node using a PodSpec. This is essentially the interface between the control plane and the node. Providing two key services:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Node Administration: The kubelet monitors the node and optimizes it, procures the resources and setting networking to maintain operation.&lt;/li&gt;
&lt;li&gt;Pod execution: The kubelet acts as the coordinator of operations for each node on a pod, activities such as scheduling, startup, and optimizing pod performance within the context of the overall Kubernetes cluster.&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Container Runtime:&lt;/strong&gt;  Docker this is the default runtime using &lt;code&gt;containerd&lt;/code&gt; open source daemon that facilitates the life cycle management via API requests. The container runtime deals with;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;verifying and loading container images&lt;/li&gt;
&lt;li&gt;monitoring system resources&lt;/li&gt;
&lt;li&gt;isolating and allocating resources&lt;/li&gt;
&lt;li&gt;and the container life cycle.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Kube Proxy:&lt;/strong&gt; Kube proxy is a network proxy running on each node in the cluster, this implements part of the Service concept when it comes to the networking side. Specifically, networking rules allowing/disallowing network communication to your pods or outside the cluster. Typically, it will use &lt;code&gt;iptables&lt;/code&gt; for traffic routing.&lt;/p&gt;&lt;/li&gt;

&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;📌 &lt;strong&gt;Deep Dive for Intermediate Users&lt;/strong&gt;: &lt;br&gt;
K3s simplifies the control plane by combining the API Server, Controller Manager, and Scheduler into a single binary. It also replaces etcd with SQLite as the default data store, though etcd is still supported. This design allows k3s to be lighter and faster to set up, making it ideal for edge computing and IoT scenarios &lt;sup id="fnref4"&gt;4&lt;/sup&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Core Kubernetes Concepts: The Building Blocks
&lt;/h2&gt;

&lt;p&gt;We’ve already used the terms Pods and Nodes, lets explore these concepts a bit further and understand how they interact.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frwnaheo09fnwq5hjodxy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frwnaheo09fnwq5hjodxy.png" alt="Kubernetes building blocks" width="725" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pods
&lt;/h3&gt;

&lt;p&gt;The smallest deployable units of computing to create and manage in Kubernetes&lt;/p&gt;

&lt;p&gt;Key characteristics of Pods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Pod can contain one or more containers.&lt;/li&gt;
&lt;li&gt;Containers in a Pod share the same network namespace, meaning they can communicate with each other using &lt;code&gt;localhost&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Containers also share the same shared storage volume in Pod.&lt;/li&gt;
&lt;li&gt;Pods are ephemeral by design. They can be created, destroyed, and recreated as needed.&lt;/li&gt;
&lt;li&gt;Each Pod gets its own IP address in the cluster.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example use cases for multi-container Pods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sidecar container for logging&lt;/li&gt;
&lt;li&gt;Initialization container that sets up the environment before the main container starts&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;📌 &lt;strong&gt;Deep Dive for Intermediate Users&lt;/strong&gt;: &lt;br&gt;
Pods are created usually as workload management resources, not directly as a &lt;code&gt;Pod&lt;/code&gt;. That means, Pods are created as a &lt;code&gt;Deployment&lt;/code&gt;, &lt;code&gt;StatefulSet&lt;/code&gt; or a &lt;code&gt;Job&lt;/code&gt; specification. More on this in the coming articles of this series.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Nodes
&lt;/h3&gt;

&lt;p&gt;Nodes are where the workloads are run for a cluster, placed inside a pod that runs on a node. Nodes can be virtual or a physical machine.&lt;/p&gt;

&lt;p&gt;Key points about Nodes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each Node runs a Kubelet, a container runtime, and a Kube Proxy.&lt;/li&gt;
&lt;li&gt;Nodes can be added or removed from the cluster to scale your infrastructure.&lt;/li&gt;
&lt;li&gt;Kubernetes supports heterogeneous clusters, meaning you can mix and match different types of nodes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Clusters
&lt;/h3&gt;

&lt;p&gt;A Kubernetes cluster is the complete set of Nodes and the Control Plane managing them. It's the entire ecosystem where your applications live.&lt;/p&gt;

&lt;p&gt;Important aspects of Clusters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clusters can span multiple physical or virtual machines.&lt;/li&gt;
&lt;li&gt;They provide redundancy and high availability for your applications.&lt;/li&gt;
&lt;li&gt;You can run multiple clusters for different environments (dev, staging, production) or to separate concerns.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Namespaces
&lt;/h3&gt;

&lt;p&gt;Namespaces provide a way to divide cluster resources between multiple users or projects. They're like virtual clusters within a physical cluster.&lt;/p&gt;

&lt;p&gt;Benefits of using Namespaces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They provide a scope for names, allowing you to use the same resource names in different namespaces.&lt;/li&gt;
&lt;li&gt;They're a great way to divide resources between different teams or projects.&lt;/li&gt;
&lt;li&gt;You can set resource quotas for each namespace, ensuring fair resource allocation.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;📌 &lt;strong&gt;Deep Dive for Intermediate Users&lt;/strong&gt;: &lt;br&gt;
Kubernetes already starts with 4 initial namespaces &lt;sup id="fnref5"&gt;5&lt;/sup&gt;, 2 of the more notable namespaces included are;&lt;br&gt;
 &lt;code&gt;default&lt;/code&gt; so that you can start using your new cluster without first creating a namespace. &lt;br&gt;
&lt;code&gt;kube-system&lt;/code&gt; is the namespace for objects created by the Kubernetes system&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Hands-on: Setting Up k3s
&lt;/h2&gt;

&lt;p&gt;We should have a pretty good understanding of what Kubernetes is and what components it utilizes to create an environment that can run applications and manage them effectively.&lt;/p&gt;

&lt;p&gt;Let’s go ahead and setup our own cluster using k3s 1.30&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing k3s
&lt;/h3&gt;

&lt;p&gt;The installation is very simple.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sfL&lt;/span&gt; https://get.k3s.io | &lt;span class="nv"&gt;INSTALL_K3S_EXEC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"--disable traefik"&lt;/span&gt; sh -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command downloads the latest k3s binary and installs it as a service.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;By default Traefik is included in the installation of k3s, this flag &lt;code&gt;-disable traefik&lt;/code&gt; disables it. We will install Traefik later on in this series, you can ignore it for now.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Copy the kube &lt;code&gt;config&lt;/code&gt; file to our home directory, this will avoid us having to run &lt;code&gt;sudo&lt;/code&gt; for any k3s command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; ~/.kube
&lt;span class="nb"&gt;sudo cp&lt;/span&gt; /etc/rancher/k3s/k3s.yaml ~/.kube/config
&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nv"&gt;$USER&lt;/span&gt;:&lt;span class="nv"&gt;$USER&lt;/span&gt; ~/.kube/config
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 ~/.kube/config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ensure you load the configuration that we just copied.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'export KUBECONFIG=~/.kube/config'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /home/vagrant/.bashrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Starting Your Kubernetes Cluster
&lt;/h3&gt;

&lt;p&gt;One of the advantages of k3s is that it starts automatically after installation. You can verify that it's running with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;k3s kubectl get nodes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see output similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME                STATUS   ROLES                  AGE   VERSION
insight-prose-dev   Ready    control-plane,master   17h   v1.30.3+k3s1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This output shows that you have a single-node cluster up and running, with your machine acting as both the control plane and a worker node.&lt;/p&gt;

&lt;p&gt;To show all the Pods currently in the cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;k3s kubectl get pods &lt;span class="nt"&gt;-A&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;📌 &lt;strong&gt;Deep Dive for Intermediate Users&lt;/strong&gt;: &lt;br&gt;
Kubernetes (and as such K3s) uses containerd as its container runtime, which is lighter than Docker. If you're used to Docker commands, you might need to use crictl for debugging container issues in k3s. The crictl command-line tool is included with k3s &lt;sup id="fnref6"&gt;6&lt;/sup&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Your First Kubernetes Deployment
&lt;/h2&gt;

&lt;p&gt;With our cluster up and running, let's deploy a simple web application. We'll use Nginx as our example. The following diagram illustrates the deployment process:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvo58n9e0o6wkndzqctv7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvo58n9e0o6wkndzqctv7.png" alt="Kubernetes deployment process" width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The above diagram shows which control plane services (yellow) are involved in creating a new Deployment and how the process is sequenced.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a Deployment YAML
&lt;/h3&gt;

&lt;p&gt;Create a file named &lt;code&gt;nginx-deployment.yaml&lt;/code&gt; with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-deployment&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:1.26.1&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’re deploying the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;apiVersion&lt;/code&gt;, &lt;code&gt;kind&lt;/code&gt;, and &lt;code&gt;metadata&lt;/code&gt; are required fields that describe the resource and its API version.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;spec&lt;/code&gt; describes the desired state for the Deployment.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;replicas: 3&lt;/code&gt; tells Kubernetes to run three copies of this application.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;selector&lt;/code&gt; defines how the Deployment finds which Pods to manage. These labels need to be unique within the namespace, otherwise strange things can happen.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;template&lt;/code&gt; describes the Pods that will be created. In this case, each Pod will run one container using the &lt;code&gt;nginx:1.26.1&lt;/code&gt; image.&lt;/li&gt;
&lt;li&gt;we expose this Deployment on port 80. This should look very familiar those coming from a Docker background.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Deploying the Application
&lt;/h3&gt;

&lt;p&gt;Apply the deployment to your cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; nginx-deployment.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command tells Kubernetes to create the resources described in the YAML file.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🚨 &lt;strong&gt;Troubleshooting Tip&lt;/strong&gt;: &lt;br&gt;
If you see an error like "The connection to the server localhost:8080 was refused", make sure you've set up your kubeconfig correctly as described in the previous section. You might need to restart your terminal or run &lt;code&gt;export KUBECONFIG=~/.kube/config&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Exposing the Application
&lt;/h3&gt;

&lt;p&gt;To access the Nginx server from outside the cluster, we need to expose it as a Service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl expose deployment nginx-deployment &lt;span class="nt"&gt;--type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;LoadBalancer &lt;span class="nt"&gt;--port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a Service that exposes our Nginx deployment. The &lt;code&gt;--type=LoadBalancer&lt;/code&gt; flag tells Kubernetes to provide an external IP to access the Service.&lt;/p&gt;

&lt;p&gt;To see the details of the Service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get services nginx-deployment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a cloud environment, this would typically provide an external IP. In k3s running locally, you might see &lt;code&gt;&amp;lt;pending&amp;gt;&lt;/code&gt; for the external IP. You can still access the service using the cluster IP or node port.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📌 &lt;strong&gt;Deep Dive for Intermediate Users&lt;/strong&gt;: &lt;br&gt;
K3s comes with ServiceLB LoadBalancer out of the box, which allows the use of LoadBalancer services without additional configuration. Many Kubernetes installations will require a LoadBalancer to be installed first, as is typical with public cloud vendors to offer their own instead. &lt;sup id="fnref7"&gt;7&lt;/sup&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Practical Exercise: Scaling and Updating
&lt;/h2&gt;

&lt;p&gt;Now that we have our application running, let's try scaling it and updating to a new version.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scaling the Deployment
&lt;/h3&gt;

&lt;p&gt;To scale our deployment to 5 replicas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl scale deployment nginx-deployment &lt;span class="nt"&gt;--replicas&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the scaling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should now see 5 nginx pods running. This demonstrates how easy it is to scale applications in Kubernetes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🚨 &lt;strong&gt;Troubleshooting Tip&lt;/strong&gt;: &lt;br&gt;
If some pods are stuck in "Pending" status, it might be due to resource constraints. Check the events for more information:&lt;br&gt;
&lt;code&gt;kubectl describe deployments&lt;/code&gt;&lt;br&gt;
Look for events related to scheduling or resource allocation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Updating the Application
&lt;/h3&gt;

&lt;p&gt;Let's update Nginx to a newer version. Edit the &lt;code&gt;nginx-deployment.yaml&lt;/code&gt; file and change the image version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:1.27&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply the changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; nginx-deployment.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Watch the rollout status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl rollout status deployment/nginx-deployment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kubernetes will gradually update the pods to the new version, ensuring zero downtime. This process, known as a rolling update (&lt;code&gt;RollingUpdate&lt;/code&gt; is the default), replaces old Pods with new ones incrementally.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📌 &lt;strong&gt;Deep Dive for Intermediate Users&lt;/strong&gt;: &lt;br&gt;
Kubernetes offers fine-grained control over the update process through fields like &lt;code&gt;.spec.strategy.rollingUpdate.maxSurge&lt;/code&gt; and &lt;code&gt;.spec.strategy.rollingUpdate.maxUnavailable&lt;/code&gt; in the deployment spec. These allow you to control how many pods can be created above the desired number, and how many pods can be unavailable during the update process respectively (both default to 25%)  &lt;sup id="fnref8"&gt;8&lt;/sup&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusion: Your Kubernetes Journey Begins
&lt;/h2&gt;

&lt;p&gt;We've covered a lot of ground in this first foundational Kubernetes article; from understanding the basics of Kubernetes architecture to deploying and managing your first application using k3s. &lt;/p&gt;

&lt;p&gt;Here's a quick recap:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Kubernetes architecture consists of a control plane and nodes, working together to manage containerized applications.&lt;/li&gt;
&lt;li&gt;Core concepts like Pods, Nodes, Clusters, and Namespaces form the building blocks of any Kubernetes system.&lt;/li&gt;
&lt;li&gt;K3s provides a lightweight, easy-to-install Kubernetes distribution perfect for learning and development.&lt;/li&gt;
&lt;li&gt;Deployments in Kubernetes allow you to declaratively manage your applications.&lt;/li&gt;
&lt;li&gt;Kubernetes makes it easy to scale and update your applications with simple commands.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;In upcoming articles in this series, we'll dive deeper into:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Kubernetes Networking&lt;/strong&gt;: Understanding Services, Ingress, and Network Policies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistent Storage in Kubernetes&lt;/strong&gt;: Working with PersistentVolumes and StorageClasses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kubernetes Security&lt;/strong&gt;: Role-Based Access Control (RBAC) and Pod Security Policies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Advanced Workloads&lt;/strong&gt;: Exploring StatefulSets, DaemonSets, and Jobs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring and Logging in Kubernetes&lt;/strong&gt;: Setting up robust observability for your cluster&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each of these topics builds on the foundation we've laid in this article, allowing you to create more complex and robust applications on Kubernetes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test Your Knowledge
&lt;/h2&gt;

&lt;p&gt;To reinforce what you've learned, try the following exercises:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deploy a different application (e.g., a simple Node.js app) to your k3s cluster.&lt;/li&gt;
&lt;li&gt;Rollback the Nginx deployment to the previous &lt;code&gt;1.26.1&lt;/code&gt; version.&lt;/li&gt;
&lt;li&gt;Create a service of type ClusterIP for your application and access it from within the cluster.&lt;/li&gt;
&lt;li&gt;Set up a cronjob that prints "Hello, Kubernetes!" every minute.&lt;/li&gt;
&lt;li&gt;Create a ConfigMap and use it to configure your application.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As you progress, you'll find that Kubernetes opens up a world of possibilities for deploying, scaling, and managing containerized applications.  Starting out small and expanding, will open up more questions and with that a search for new answers that will expand your skillset.&lt;/p&gt;

&lt;p&gt;Thanks for your time and let me know in the comments if you’re currently using Kubernetes or you’re planning to and on what project(s).&lt;/p&gt;

&lt;p&gt;See you in the next one!&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Cloud Native 2023: The Undisputed Infrastructure of Global Technology - &lt;a href="https://www.cncf.io/reports/cncf-annual-survey-2023/" rel="noopener noreferrer"&gt;https://www.cncf.io/reports/cncf-annual-survey-2023/&lt;/a&gt;  ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;Developing Kubernetes Services at Airbnb Scale - &lt;a href="https://www.infoq.com/presentations/airbnb-kubernetes-services/" rel="noopener noreferrer"&gt;https://www.infoq.com/presentations/airbnb-kubernetes-services/&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;Open Container Initiative - &lt;a href="https://opencontainers.org/" rel="noopener noreferrer"&gt;https://opencontainers.org/&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;K3s Documentation. "Architecture." - [&lt;a href="https://docs.k3s.io/architecture%5D(https://docs.k3s.io/architecture" rel="noopener noreferrer"&gt;https://docs.k3s.io/architecture](https://docs.k3s.io/architecture&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn5"&gt;
&lt;p&gt;Kubernetes Documentation “Namespaces” - &lt;a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/" rel="noopener noreferrer"&gt;https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn6"&gt;
&lt;p&gt;K3s “Using Docker as the Container Runtime” - &lt;a href="https://docs.k3s.io/advanced#using-docker-as-the-container-runtime" rel="noopener noreferrer"&gt;https://docs.k3s.io/advanced#using-docker-as-the-container-runtime&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn7"&gt;
&lt;p&gt;K3s “Service Load Balancer - &lt;a href="https://docs.k3s.io/networking/networking-services#service-load-balancer" rel="noopener noreferrer"&gt;https://docs.k3s.io/networking/networking-services#service-load-balancer&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn8"&gt;
&lt;p&gt;Kubernetes Documentation “Deployments” - &lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/" rel="noopener noreferrer"&gt;https://kubernetes.io/docs/concepts/workloads/controllers/deployment/&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>tutorial</category>
      <category>cloud</category>
    </item>
    <item>
      <title>How to create a flexible Dev Environment with Vagrant and Docker</title>
      <dc:creator>Rolf Streefkerk</dc:creator>
      <pubDate>Tue, 25 Jun 2024 12:11:51 +0000</pubDate>
      <link>https://forem.com/rolfstreefkerk/how-to-create-a-flexible-dev-environment-with-vagrant-and-docker-4862</link>
      <guid>https://forem.com/rolfstreefkerk/how-to-create-a-flexible-dev-environment-with-vagrant-and-docker-4862</guid>
      <description>&lt;p&gt;Today we're discussing how you can create a fully automated, virtualized development environment. One that's customizable, and ready to use in minutes.&lt;/p&gt;

&lt;p&gt;In this article, I'll show you how to set up such an environment using PHP and Laravel, though the principles can be applied to your preferred tech stack.&lt;/p&gt;

&lt;p&gt;We'll dive into creating a robust setup powered by VirtualBox and Vagrant, featuring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Apache Gateway&lt;/li&gt;
&lt;li&gt;PHP with Apache for front-end&lt;/li&gt;
&lt;li&gt;Laravel API&lt;/li&gt;
&lt;li&gt;Redis Cache with Redis Commander&lt;/li&gt;
&lt;li&gt;MySQL database with PHPMyAdmin&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end of this guide, you'll have a secure, containerized environment accessible via specific routes, with core services in a private Docker network.&lt;/p&gt;

&lt;p&gt;Furthermore, it’s very easy to connect Visual Studio Code to VirtualBox, and start your programming workflow.&lt;/p&gt;

&lt;p&gt;Let's get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  How does it all work?
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;TLDR: Skip to step 4. A - Installation, to get right into setting this up for your system.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are 4 parts to this solution I’ll discuss today. I’ll finish with the Development workflow.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1, Apache as a Gateway (Reverse Proxy)&lt;/li&gt;
&lt;li&gt;2. Docker with Docker compose&lt;/li&gt;
&lt;li&gt;3. Vagrant with VirtualBox&lt;/li&gt;
&lt;li&gt;
4. Development workflow

&lt;ul&gt;
&lt;li&gt;A - Installation&lt;/li&gt;
&lt;li&gt;B - Accessing the web application services&lt;/li&gt;
&lt;li&gt;C - Developing with VSCode&lt;/li&gt;
&lt;li&gt;D - Updating code with GitHub.&lt;/li&gt;
&lt;li&gt;E - (Extra) GitOps with GitHub Actions&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbkrtsrml4gw7pbfxk50w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbkrtsrml4gw7pbfxk50w.png" alt="Vagrant apache deployment diagram" width="800" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you like more in-depth articles on Apache, Docker, or Vagrant?&lt;/p&gt;

&lt;p&gt;Let me know in the comments below, and Connect with me on &lt;a href="https://x.com/rolfstreefkerk" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1, Apache as a Gateway (Reverse Proxy)
&lt;/h2&gt;

&lt;p&gt;Apache 2 is a HTTP server with a modular setup. An HTTP server in essence serves files according to the HTTP protocol of which there are two major versions currently used across the web, http v1.1 and http v2&lt;/p&gt;

&lt;p&gt;Apache is typically used as an HTTP server to serve files, however in this case we’re using it in Proxy “mode” primarily and specifically as a Reverse Proxy (also known as a Gateway).&lt;/p&gt;

&lt;p&gt;The reverse proxy acts like a regular web-server, and will decide where to send the requests and then returns the content as if it was the origin server.&lt;/p&gt;

&lt;p&gt;Typical use-cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Load balancing. &lt;em&gt;Is a topic for another day.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Provide access to servers behind a firewall. &lt;em&gt;This is what we’re doing today.&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ProxyPass "/foo" "http://foo.example.com/bar"
ProxyPassReverse "/foo" "http://foo.example.com/bar"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ProxyPass&lt;/code&gt; is a remote server mapping to the local path. This is considered in Apache as a “Worker”. This is an object that holds its own connections and configuration associated with that connection.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ProxyPassReverse&lt;/code&gt; ensures that the return headers generated from the backend are modified to point to the Gateway path instead of the origin server.&lt;/p&gt;

&lt;p&gt;These are the basic 2 directives to create mappings that appear to orginate from the Reverse Proxy.&lt;/p&gt;

&lt;p&gt;Now you need to map these to specific Locations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Location "/api/"&amp;gt;
    ProxyPass http://api:80/
    ProxyPassReverse http://api:80/
&amp;lt;/Location&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;Location&lt;/code&gt; directive operates on the URL or webpages that are generated only! Not file system paths.&lt;/p&gt;

&lt;p&gt;In this example &lt;code&gt;api&lt;/code&gt; is a named server in the &lt;code&gt;docker-compose&lt;/code&gt; file that is &lt;code&gt;exposed&lt;/code&gt; to port 80.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;within a Docker network you can reference containers by their &lt;code&gt;name&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;code&gt;api&lt;/code&gt; service needs to be accessible on the reverse proxy at the URL path &lt;code&gt;/api/&lt;/code&gt;. So that means, you can access the API in your browser at: &lt;code&gt;http://your-ip/api/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;To make sure that the &lt;code&gt;api&lt;/code&gt; and the other Locations are made available on port 80 we need to encapsulate the &lt;code&gt;Location&lt;/code&gt; and &lt;code&gt;ProxyPass&lt;/code&gt;, &lt;code&gt;ProxyPassReverse&lt;/code&gt; in a &lt;code&gt;VirtualHost&lt;/code&gt; directive like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;VirtualHost *:80&amp;gt;
    ServerName ${SERVER_NAME}
    ProxyPreserveHost On

    # Laravel Frontend app server on root path
    ProxyPass / http://frontend/
    ProxyPassReverse / http://frontend/

    &amp;lt;Location "/api/"&amp;gt;
        ProxyPass http://api:80/
        ProxyPassReverse http://api:80/
    &amp;lt;/Location&amp;gt;

    # ... other directives ....
&amp;lt;/VirtualHost&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;VirtualHost&lt;/code&gt; is a grouping based on an &lt;code&gt;ip-address&lt;/code&gt; or wildcard, match-all &lt;code&gt;*&lt;/code&gt; and a port number. For that grouping you can override global directives such as Location and Directory.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ProxyPreserveHost On&lt;/code&gt;: This directive ensures that the original Host header is passed to the backend server, which can be important for applications that rely on the Host header for their logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Docker with Docker compose
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Fun note about their logo, it’s a whale with shipping containers. Matching perfectly with the core idea of the product.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Docker is software that can package other software in a concept called a Docker Container. This container is portable across many different operating environments such as Linux and Windows.&lt;/p&gt;

&lt;p&gt;Docker provides a way to isolate the container environment (the Guest machine) from the operating system (the Host machine).&lt;/p&gt;

&lt;p&gt;This allows for many different kinds of software and operating environment to run within the containers without interfering with the host machine.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsz50xo3aabdx4zrcmbb3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsz50xo3aabdx4zrcmbb3.png" alt="What is Docker" width="424" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As seen in this diagram, Docker requires a lot of work from the Host Operating System. The benefit is that the containers can be small, since much of the code and the work is happening underneath the containers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker compose
&lt;/h3&gt;

&lt;p&gt;Docker compose is the “compositor” document standard to create a Docker network with one or more Docker containers.&lt;/p&gt;

&lt;p&gt;Below is an example of a docker-compose yaml document:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;for both the &lt;code&gt;gateway&lt;/code&gt; and &lt;code&gt;db&lt;/code&gt; service we configure:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;image&lt;/code&gt;: references an image that must be on DockerHub.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ports&lt;/code&gt;: is an “outside:inside” mapping. Where outside means outside the Docker network (Your host machine or in this case, Virtual Machine)&lt;/li&gt;
&lt;li&gt;In this case we use the same port outside of the Docker network to access this service as inside the network.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;expose&lt;/code&gt; is similar to &lt;code&gt;ports&lt;/code&gt;, except this port is not available outside the Docker network&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;volumes&lt;/code&gt;: are mounts, again it’s an “outside:inside” mapping.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;./apache-config&lt;/code&gt; is the git repo directory where our &lt;code&gt;httpd.conf&lt;/code&gt; lives. That’s mapped straight to the Apache 2 configuration file inside the container.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;depends_on&lt;/code&gt;: this service needs to wait until these other named services are online.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;environment&lt;/code&gt; are environment variables available inside the container. Typically used to set passwords, ports, and username configurations.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;gateway&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;httpd:2.4&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gateway&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;80:80"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./apache-config/httpd.conf:/usr/local/apache2/conf/httpd.conf&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;frontend&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis-ui&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;phpmyadmin&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;SERVER_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${APACHE_SERVER_NAME}&lt;/span&gt;

  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql:5.7&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
    &lt;span class="na"&gt;expose&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3306"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${MYSQL_ROOT_PASSWORD}&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_DATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${MYSQL_DATABASE}&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${MYSQL_USER}&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${MYSQL_PASSWORD}&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db-data:/var/lib/mysql&lt;/span&gt;

  &lt;span class="c1"&gt;# ... other services here&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run this file we can use: &lt;code&gt;docker-compose up --build -d&lt;/code&gt;&lt;br&gt;
This will: - bring the containers online &lt;code&gt;up&lt;/code&gt; - build the images &lt;code&gt;--build&lt;/code&gt; - run it in the backgroud &lt;code&gt;-d&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;To take all of the containers offline, we simply type; &lt;code&gt;docker-compose down&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;To manage containers easily from the CLI, I use &lt;code&gt;dry&lt;/code&gt;, it provides an easier way to see statistics on all containers (memory and cpu usage) as well as a log viewer that can be searched, and much more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary of useful Docker commands:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;docker ps&lt;/code&gt; show all running Docker containers.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker-compose up --build -d&lt;/code&gt;: build the Docker images, bring the containers online and run them in the background.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker-compose down&lt;/code&gt; bring the containers offline.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker-compose stop&lt;/code&gt; stop the containers.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker-compose restart &amp;lt;name&amp;gt;&lt;/code&gt; restart the specified Docker container.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dry&lt;/code&gt; run the Docker manager and monitoring / logs command line utility

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;F2&lt;/code&gt; show all containers (stopped and running)&lt;/li&gt;
&lt;li&gt;select container &lt;code&gt;enter&lt;/code&gt; &amp;gt; &lt;code&gt;feth logs&lt;/code&gt; &amp;gt; &lt;code&gt;enter&lt;/code&gt; &amp;gt; &lt;code&gt;f&lt;/code&gt; to tail the logs.&lt;/li&gt;
&lt;li&gt;select container &lt;code&gt;enter&lt;/code&gt; &amp;gt; &lt;code&gt;fetch logs&lt;/code&gt; &amp;gt; &lt;code&gt;30m&lt;/code&gt; &amp;gt; &lt;code&gt;f&lt;/code&gt; show logs from the last 30 minutes and tail.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. Vagrant with VirtualBox
&lt;/h2&gt;

&lt;p&gt;Vagrant can create complete development environments on Virtual Machine (VM) in the cloud and on your local machine with a simple workflow.&lt;/p&gt;

&lt;p&gt;Vagrant provides consistent environments such that code works regardless of what kind of systems your team members use for their development, or creative, work.&lt;/p&gt;

&lt;p&gt;Vagrant is in my opinion ideal to setup different development environments that require different dependencies, really quickly. Additionally, because it’s all in code, you can easily adapt the environments to match evolving requirements for your use cases.&lt;/p&gt;

&lt;p&gt;For this solution we use a base image &lt;code&gt;generic/ubuntu2204&lt;/code&gt; to build the Virtual Machine with using a &lt;code&gt;Vagrantfile&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A Vagrantfile is, similar to a Dockerfile, because it provides instructions for the Vagrant program how to create the virtual image (using the base image &lt;code&gt;box&lt;/code&gt; and provisioners) and what to use to run it on (a provider, in this case VirtualBox).&lt;/p&gt;

&lt;p&gt;A quick run down of what this all means using an example (shortened version):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Vagrant.configure("2")&lt;/code&gt; denotes the version of Vagrant that we’re using; &lt;code&gt;2&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;config.vm.box = "generic/ubuntu2204"&lt;/code&gt; the box specification running Ubuntu 22.04.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;config.vm.provider "virtualbox"&lt;/code&gt; we use VirtualBox to run the VM.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;config.vm.provision "shell", inline:&lt;/code&gt; we use bash scripts to install our environment.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;config.vm.synced_folder&lt;/code&gt;: enables the VirtualBox built in folder sharing, requires VBox Guest Editions to work.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;config.vm.provider "virtualbox"&lt;/code&gt; has VirtualBox specific configurations such as the &lt;code&gt;cpu&lt;/code&gt; &lt;code&gt;memory&lt;/code&gt; and &lt;code&gt;name&lt;/code&gt; of the virtual machine.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# -*- mode: ruby -*-&lt;/span&gt;
&lt;span class="c1"&gt;# vi: set ft=ruby :&lt;/span&gt;

&lt;span class="no"&gt;Vagrant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;

  &lt;span class="c1"&gt;# Variables&lt;/span&gt;
  &lt;span class="n"&gt;git_user_email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'youremail@mail.com'&lt;/span&gt;

  &lt;span class="c1"&gt;# boxes at https://vagrantcloud.com/search.&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt; &lt;span class="s2"&gt;"docker-apache"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;dockerApache&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"generic/ubuntu2204"&lt;/span&gt;

    &lt;span class="c1"&gt;# via 127.0.0.1 to disable public access&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;network&lt;/span&gt; &lt;span class="s2"&gt;"forwarded_port"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;guest: &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;host: &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;network&lt;/span&gt; &lt;span class="s2"&gt;"public_network"&lt;/span&gt;

    &lt;span class="c1"&gt;# Share an additional folder to the guest VM.&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;synced_folder&lt;/span&gt; &lt;span class="s2"&gt;"./data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"/vagrant_data"&lt;/span&gt;

    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"virtualbox"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;vb&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="c1"&gt;# Display the VirtualBox GUI when booting the machine&lt;/span&gt;
    &lt;span class="c1"&gt;# vb.gui = true&lt;/span&gt;

    &lt;span class="c1"&gt;# Customize the amount of memory on the VM:&lt;/span&gt;
      &lt;span class="n"&gt;vb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"8192"&lt;/span&gt;
      &lt;span class="n"&gt;vb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"docker-apache"&lt;/span&gt;
      &lt;span class="n"&gt;vb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cpus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provision&lt;/span&gt; &lt;span class="s2"&gt;"shell"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;inline: &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SHELL&lt;/span&gt;&lt;span class="sh"&gt;
      sudo apt-get update
      sudo apt-get install -y apt-transport-https ca-certificates curl

      su - vagrant &amp;lt;&amp;lt; EOF
        # clone repo
        mkdir -p /home/vagrant/docker-apache
        cd /home/vagrant/docker-apache
        git clone https://github.com/rpstreef/docker-apache-reverse-proxy .
     EOF
&lt;/span&gt;&lt;span class="no"&gt;    SHELL&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To create a VM out of a &lt;code&gt;Vagrantfile&lt;/code&gt; we can simply run &lt;code&gt;vagrant up&lt;/code&gt;, this will start the process of download the &lt;code&gt;box&lt;/code&gt; and then the &lt;code&gt;provisioning&lt;/code&gt; step with the bash scripts to the &lt;code&gt;provider&lt;/code&gt; VirtualBox.&lt;/p&gt;

&lt;p&gt;When it’s all finished, connect using &lt;code&gt;vagrant ssh&lt;/code&gt; and start using it!.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary of useful Vagrant commands
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;vagrant up&lt;/code&gt; this will create the Virtual Machine.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;vagrant validate&lt;/code&gt; used to verify the &lt;code&gt;Vagrantfile&lt;/code&gt; is semantically correct.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;vagrant halt&lt;/code&gt; to stop the VM, use &lt;code&gt;--force&lt;/code&gt; to shut it down immediately.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;vagrant ssh&lt;/code&gt; connects to the VM via the command-line.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;vagrant destroy&lt;/code&gt; completely remove the Virtual Machine.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4. Development workflow
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A - Installation
&lt;/h3&gt;

&lt;p&gt;Now that it’s clear how the solution parts work, let’s go and install it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Git clone &lt;a href="https://github.com/rpstreef/flexible-dev-environment" rel="noopener noreferrer"&gt;https://github.com/rpstreef/flexible-dev-environment&lt;/a&gt; in your local projects directory.&lt;/li&gt;
&lt;li&gt;Install Vagrant using &lt;a href="https://developer.hashicorp.com/vagrant/docs/installation" rel="noopener noreferrer"&gt;these&lt;/a&gt; instructions&lt;/li&gt;
&lt;li&gt;Install VirtualBox using &lt;a href="https://www.virtualbox.org/wiki/Downloads" rel="noopener noreferrer"&gt;these&lt;/a&gt; instructions.&lt;/li&gt;
&lt;li&gt;Edit the &lt;code&gt;Vagrantfile&lt;/code&gt; :

&lt;ol&gt;
&lt;li&gt;You want to use GitHub on the Virtual Machine?\

&lt;ol&gt;
&lt;li&gt;Change the &lt;code&gt;git_user_email&lt;/code&gt; and &lt;code&gt;git_user_name&lt;/code&gt; values.&lt;/li&gt;
&lt;li&gt;There’s an additional step to complete after the VM is installed. See &lt;a href=""&gt;How to setup your Personal Access Token (PAT) for GitHub:&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;Check machine settings in this block; &lt;code&gt;config.vm.provider "virtualbox"&lt;/code&gt;. Adjust &lt;code&gt;vb.memory = "8192"&lt;/code&gt; and &lt;code&gt;vb.cpus = 6&lt;/code&gt; the number of processors.&lt;/li&gt;

&lt;/ol&gt;

&lt;/li&gt;

&lt;li&gt;From the Git folder, run &lt;code&gt;vagrant up&lt;/code&gt;, this will setup the Virtual Machine with VirtualBox

&lt;ul&gt;
&lt;li&gt;When asked which network, choose the adapter with internet access.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Connect to the Virtual Machine, run &lt;code&gt;vagrant ssh&lt;/code&gt;.

&lt;ul&gt;
&lt;li&gt;Take note of the &lt;code&gt;ip address&lt;/code&gt; and use that to connect to with your browser. Or on the CLI type: &lt;code&gt;ip address&lt;/code&gt; and look for the network adapter name you chose earlier.

&lt;ol&gt;
&lt;li&gt;For the Web Landing-page: &lt;code&gt;http://virtual-machine-ip/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;For the Redis Commander UI &lt;code&gt;http://virtual-machine-ip/cache&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;For PHPMyAdmin: &lt;code&gt;http://virtual-machine-ip/phpmyadmin&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;For Laravel API: &lt;code&gt;http://virtual-machine-ip/api&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;When connected to the VM:

&lt;ul&gt;
&lt;li&gt;Execute &lt;code&gt;dry&lt;/code&gt; on the command line and you should see several containers running.&lt;/li&gt;
&lt;li&gt;Refer to the summary of useful Docker commands chapter for more guidance with Docker commands.

&lt;ul&gt;
&lt;li&gt;and this summary for Vagrant commands.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ol&gt;

&lt;h3&gt;
  
  
  B - Accessing the web application services
&lt;/h3&gt;

&lt;p&gt;The Apache Gateway provides access via port 80 with your browser to only the parts that need to be exposed to the outside:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Front-end → &lt;code&gt;http//ip-address/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Redis Commander → &lt;code&gt;http//ip-address/cache/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Laravel API → &lt;code&gt;http//ip-address/api/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;PHPMyAdmin → &lt;code&gt;http//ip-address/phpmyadmin/&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means that the Redis and MySQL services are not accessible directly from outside the docker network (private network).&lt;/p&gt;

&lt;h3&gt;
  
  
  C - Developing with VSCode
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Configure SSH Access
&lt;/h4&gt;

&lt;p&gt;Configure the ssh access on your machine with vagrant is really easy to do, just run &lt;code&gt;vagrant ssh-config&lt;/code&gt;, copy paste the text into your &lt;code&gt;~/.ssh/config&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;From the command line, anywhere, you can connect to your VM with &lt;code&gt;ssh docker-apache&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;To get the IP address from your VM, run &lt;code&gt;ip address&lt;/code&gt; and check which adapter you used for the network access, and take note.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Connect to VirtualBox with VSCode
&lt;/h4&gt;

&lt;p&gt;When this works, we can connect VSCode:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open up VSCode&lt;/li&gt;
&lt;li&gt;Click on the lower left icon, &lt;code&gt;Connect current window to Host&lt;/code&gt; then enter &lt;code&gt;docker-apache&lt;/code&gt;. This name was retrieved from the configuration we did in step 1.&lt;/li&gt;
&lt;li&gt;This will install the VSCode server files on the virtual machine.&lt;/li&gt;
&lt;li&gt;Open the directory &lt;code&gt;/home/vagrant/docker-apache&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Yes I trust the authors&lt;/code&gt;, when asked.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To get GitHub to work, we just need to add our Personal Access Token in the next step.&lt;/p&gt;

&lt;h3&gt;
  
  
  D - Updating code with GitHub.
&lt;/h3&gt;

&lt;p&gt;To setup the Personal Access Token for GitHub, do the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a PAT here: &lt;a href="https://github.com/settings/tokens" rel="noopener noreferrer"&gt;https://github.com/settings/tokens&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;For most cases, repositories only is sufficient (unless you want to activate GitHub Actions?):

&lt;ul&gt;
&lt;li&gt;Check the &lt;code&gt;repo&lt;/code&gt; checkbox&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Execute the below on the command-line to set your &lt;code&gt;Personal Access Token&lt;/code&gt; for GitHub:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git credential-store store &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
protocol=https
host=github.com # Replace with your Git provider's hostname
username=&amp;lt;your-username&amp;gt; # Replace with your Git username
password=&amp;lt;personal-access-token&amp;gt;
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;these are all stored in &lt;code&gt;cat ~/.git-credentials&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  E - (Extra) GitOps with GitHub Actions
&lt;/h3&gt;

&lt;p&gt;With &lt;a href="https://docs.github.com/en/actions" rel="noopener noreferrer"&gt;GitHub Actions&lt;/a&gt; you can automate, for example;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;docker-compose deployment to your favorite public cloud&lt;/li&gt;
&lt;li&gt;convert docker-compose to Kubernetes and then deploy to your cluster.&lt;/li&gt;
&lt;li&gt;or perform other automation as per the standard workflows offered by GitHub, to get started:

&lt;ol&gt;
&lt;li&gt; Fork my repository: &lt;a href="https://github.com/rpstreef/flexible-dev-environment" rel="noopener noreferrer"&gt;https://github.com/rpstreef/flexible-dev-environment&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt; Then go to &lt;code&gt;Actions&lt;/code&gt;, there’s a list presented of all kinds of ready made automations.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’d like more details on how to automatically deploy using GitHub Actions (GitOps), give me a shout on &lt;a href="https://x.com/rolfstreefkerk" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; or in the comments.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Setting up a virtualized development environment might seem daunting at first, but the benefits are well worth the effort. With this setup, you've gained a powerful, flexible, and secure platform for your development work.&lt;/p&gt;

&lt;p&gt;I'm curious to hear about your experiences down in the comments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How does this compare to your current development workflow?&lt;/li&gt;
&lt;li&gt;Do you see yourself adopting a similar setup, or have you already implemented something like this?&lt;/li&gt;
&lt;li&gt;What other tools or services would you add to enhance this environment?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you found this guide helpful, consider following me on &lt;a href="https://x.com/rolfstreefkerk" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; for more tech tips and discussions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For IT professionals looking to balance career growth with personal well-being, I invite you to join our community, &lt;a href="https://x.com/i/communities/1804838523964678366" rel="noopener noreferrer"&gt;The Health &amp;amp; IT Insider&lt;/a&gt;. We cover a range of topics from DevOps and software development to maintaining a healthy lifestyle in the tech industry.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Thanks for reading, and see you in the next one!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>productivity</category>
      <category>docker</category>
      <category>laravel</category>
    </item>
    <item>
      <title>How to create your own AI chatbot with LangFlow</title>
      <dc:creator>Rolf Streefkerk</dc:creator>
      <pubDate>Tue, 11 Jun 2024 09:26:14 +0000</pubDate>
      <link>https://forem.com/rolfstreefkerk/how-to-create-your-own-rag-ai-with-langflow-1jj9</link>
      <guid>https://forem.com/rolfstreefkerk/how-to-create-your-own-rag-ai-with-langflow-1jj9</guid>
      <description>&lt;p&gt;Today I’d like to start my series of articles on using the LangChain open source tool-set.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/langchain-ai/langchain" rel="noopener noreferrer"&gt;LangChain&lt;/a&gt; is one of the most used frameworks to create AI agents with, if not THE most used. With nearly 90.000 stars and almost 14000 forks on GitHub at the time of this article, we can safely say it is popular.&lt;/p&gt;

&lt;p&gt;Reason enough for me to start covering it. To kick things off with, let’s answer a few essential questions.&lt;/p&gt;

&lt;p&gt;What we're covering today:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Introduction

&lt;ul&gt;
&lt;li&gt;What is a RAG AI Agent?&lt;/li&gt;
&lt;li&gt;What is LangChain and LangFlow?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

How do I get started?

&lt;ul&gt;
&lt;li&gt;How to install LangFlow on your own machine&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

How to create our first RAG AI with LangFlow

&lt;ul&gt;
&lt;li&gt;Part 1: Processing the web-page data&lt;/li&gt;
&lt;li&gt;Part 2: Let’s chat with your RAG&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Experiment and Test&lt;/li&gt;

&lt;li&gt;Conclusion&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is a RAG AI Agent?
&lt;/h3&gt;

&lt;p&gt;Retrieval Augmented Generation (RAG) is a technique to enhance the accuracy and reliability of AI with facts retrieved from other sources.&lt;/p&gt;

&lt;p&gt;Originally the RAG name was coined in &lt;a href="https://arxiv.org/pdf/2005.11401" rel="noopener noreferrer"&gt;this&lt;/a&gt; paper, the authors stated:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;RAG could be employed in a wide variety of scenarios with direct benefit to society, for example by endowing it with a medical index and asking it open-domain questions on that topic, or by helping people be more effective at their jobs&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Essentially you can create your own set of sources that you have vetted for accuracy and fit for purpose. Creating applications with your specific needs in mind, and without potential bias or opinion from the original AI.&lt;/p&gt;

&lt;p&gt;RAG’s are a great way to provide business solutions that can run locally with open source AI models such as &lt;a href="https://llama.meta.com/" rel="noopener noreferrer"&gt;Llama&lt;/a&gt;, or large cloud scale AI’s such as &lt;a href="https://openai.com/" rel="noopener noreferrer"&gt;OpenAI&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is LangChain and LangFlow?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.langchain.com/" rel="noopener noreferrer"&gt;LangChain&lt;/a&gt; is an open source Python library that can integrate with AI models using API’s and functions. With LangChain you can create natural language interactions with an AI using concepts such as Chains, and Prompts to weave together a custom RAG AI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.langflow.org/" rel="noopener noreferrer"&gt;LangFlow&lt;/a&gt; is a GUI that can diagram a LangChain flow to experiment and prototype an AI solution with quickly. It offers drag and drop components and a chat box to immediately start interacting. This simplifies the initial development process of iteration and experimentation.&lt;/p&gt;




&lt;h2&gt;
  
  
  How do I get started?
&lt;/h2&gt;

&lt;p&gt;To get started with LangFlow creating your AI agent there are 3 options you can choose from:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;SAAS Installation using Huggingface spaces:&lt;/strong&gt; 

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Private:&lt;/strong&gt; &lt;a href="https://huggingface.co/" rel="noopener noreferrer"&gt;Huggingface account&lt;/a&gt;, then duplicate  &lt;a href="https://huggingface.co/spaces/Langflow/Langflow?duplicate=true" rel="noopener noreferrer"&gt;LangFlow Spaces&lt;/a&gt; , leave everything on default, click &lt;code&gt;duplicate space&lt;/code&gt;.

&lt;ul&gt;
&lt;li&gt;This will take a bit of time (30+ minutes, not included in the 5 minutes 😜) 

&lt;ul&gt;
&lt;li&gt;In the mean time head over to the Spaces settings &amp;gt;  &lt;code&gt;Variables and secrets&lt;/code&gt; &amp;gt; &lt;code&gt;New secret&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Name: &lt;code&gt;OPENAI_API_KEY&lt;/code&gt;, Value: your OpenAI key we just copied.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Go drink a coffee, walk outside, do other things while the Huggingface space is created.&lt;/li&gt;
&lt;li&gt;Then, you can skip to this chapter.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Public:&lt;/strong&gt; access the &lt;a href="https://huggingface.co/spaces/Langflow/Langflow" rel="noopener noreferrer"&gt;Public LangFlow&lt;/a&gt;  service, ready to go but everyone can read it!

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Do not use your OpenAI API keys here!&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Then, you can skip to this chapter.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Google Cloud installation&lt;/strong&gt;. If preferred, go &lt;a href="https://docs.langflow.org/deployment/gcp-deployment" rel="noopener noreferrer"&gt;here&lt;/a&gt;. 

&lt;ul&gt;
&lt;li&gt;Then you can skip to this chapter.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Or, run it on your own machine using VirtualBox&lt;/strong&gt;. 

&lt;ul&gt;
&lt;li&gt;This is my preferred method of choice, it’s low cost and you have full control. Follow along in the next chapter to find out how to quickly set this up.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  How to install LangFlow on your own machine
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Install Vagrant and VirtualBox:

&lt;ul&gt;
&lt;li&gt;Download and install &lt;a href="https://www.vagrantup.com/downloads.html" rel="noopener noreferrer"&gt;Vagrant&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Download and install &lt;a href="https://www.virtualbox.org/wiki/Downloads" rel="noopener noreferrer"&gt;VirtualBox&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;2) Install via my LangFlow repo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;git clone https://github.com/rpstreef/langflow-setup&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Within the cloned repo; execute; &lt;code&gt;vagrant up&lt;/code&gt; to install the LangFlow VM with docker and docker-compose.

&lt;ul&gt;
&lt;li&gt;Type &lt;code&gt;make&lt;/code&gt; for all the commands available.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;em&gt;Optionally:&lt;/em&gt;

&lt;ul&gt;
&lt;li&gt;Run: &lt;code&gt;make vm&lt;/code&gt; to get access to the Virtual Machine.

&lt;ul&gt;
&lt;li&gt;Note if you do not have VBoxManage on your command-line, this will not work.&lt;/li&gt;
&lt;li&gt;Sometimes you could get a message like this; &lt;code&gt;ssh: connect to host 192.168.1.39 port 22: No route to host&lt;/code&gt;.

&lt;ul&gt;
&lt;li&gt;Retry the connection in that case.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;Run: &lt;code&gt;dry&lt;/code&gt; to see all the containers running&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;open your browser and navigate to; &lt;code&gt;&lt;a href="http://localhost:7860/flows" rel="noopener noreferrer"&gt;http://localhost:7860/flows&lt;/a&gt;&lt;/code&gt;
&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd096ns754dt34if4o7yz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd096ns754dt34if4o7yz.png" alt="LangFlow running on VirtualBox and Docker" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we can start creating a chat bot&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To use the store, you need to register and get an API key &lt;a href="https://www.langflow.store/profile/api-key" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How to create our first RAG AI with LangFlow
&lt;/h2&gt;

&lt;p&gt;This is what we’re going to build today.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftwcii7i8r671280b5hdi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftwcii7i8r671280b5hdi.png" alt="LangFlow Example RAG AI" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To start with, what is the aim of this particular RAG? What do we want it to do?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A chat bot that can answer detailed questions about any web-page we choose.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A perfect use case for this is article analysis via questions and answers. You can formulate the questions such that the bot can provide additional explanation on harder to understand concepts.&lt;/p&gt;

&lt;p&gt;I’m sure there are other uses cases like this, let me know on &lt;a href="https://twitter.com/rolfstreefkerk" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; what you would use it for.&lt;/p&gt;

&lt;p&gt;Basically there are two parts to building this RAG chat-bot:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We need to retrieve the information we want to ask questions about,&lt;/li&gt;
&lt;li&gt;then make that information available to the OpenAI chat bot.&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Part 1: Processing the web-page data
&lt;/h3&gt;

&lt;p&gt;To start of with, we need to store website text, as a vector, in a database such that we can use it later to ask questions about.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I’ve added links to the official LangChain documentation for each of the concepts / parts discussed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://python.langchain.com/v0.2/docs/concepts/#document-loaders" rel="noopener noreferrer"&gt;WebBaseLoader&lt;/a&gt;&lt;/strong&gt;: Enter the Web Page we want to analyze.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://python.langchain.com/v0.2/docs/concepts/#embedding-models" rel="noopener noreferrer"&gt;OpenAIEmbeddings&lt;/a&gt;&lt;/strong&gt;: This will “embed” the information read from the website. We use OpenAI’s &lt;code&gt;text-embedding-3-small&lt;/code&gt; Model to do this. Which should be ample for most use-cases. This creates a vector representation of the &lt;code&gt;documents&lt;/code&gt; we’re about to create. The &lt;a href="https://python.langchain.com/v0.2/docs/how_to/split_by_token/#tiktoken" rel="noopener noreferrer"&gt;TikToken&lt;/a&gt; tokenizer to make it more accurate for OpenAI to split the text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://python.langchain.com/v0.2/docs/concepts/#text-splitters" rel="noopener noreferrer"&gt;CharacterTextSplitter&lt;/a&gt;:&lt;/strong&gt; This part will cut up the text into &lt;code&gt;chunks&lt;/code&gt; with a bit of overlap, and outputs these parts as &lt;code&gt;documents&lt;/code&gt; to &lt;code&gt;FAISS&lt;/code&gt; the vector database we’re using in this scenario.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://python.langchain.com/v0.2/docs/integrations/vectorstores/faiss" rel="noopener noreferrer"&gt;FAISS&lt;/a&gt;&lt;/strong&gt;: Is the Facebook AI Similarity Search (FAISS) vector database that can store and search vectorized &lt;code&gt;documents&lt;/code&gt; in memory.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/akulwhLfk0Q"&gt;
&lt;/iframe&gt;
&lt;/p&gt;




&lt;h3&gt;
  
  
  Part 2: Let’s chat with your RAG
&lt;/h3&gt;

&lt;p&gt;Now that we have the basic information retrieval and vector storage created. We can start adding a Chat AI and chaining the &lt;code&gt;documents&lt;/code&gt; we’ve created with a &lt;a href="https://python.langchain.com/v0.2/docs/concepts/#retrievers" rel="noopener noreferrer"&gt;Retriever&lt;/a&gt; (Search via a question and retrieve &lt;code&gt;documents&lt;/code&gt; using a prompt via the chat box.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://python.langchain.com/v0.2/docs/integrations/chat/openai" rel="noopener noreferrer"&gt;ChatOpenAI&lt;/a&gt;:&lt;/strong&gt; We use the Chat feature offered by OpenAI and set the model to &lt;code&gt;gpt-3.5-turbo-0125&lt;/code&gt; to save on cost. Additionally the &lt;a href="https://gptforwork.com/guides/openai-gpt3-temperature" rel="noopener noreferrer"&gt;temperature&lt;/a&gt; is set to 0.2 to inform the AI we need answers with high probability and not too much creativity (&amp;gt; 0.5 - 1.0)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CombineDocsChain:&lt;/strong&gt; We use chain type &lt;code&gt;stuff&lt;/code&gt;, which basically means the context is the document. For efficiency you could change this to &lt;code&gt;map_reduce&lt;/code&gt;, but it would mean we need to complicate the Prompt parts to combine the documents with.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RetrievalQAWithSourcesChain:&lt;/strong&gt; Chains together and creates the Context from the user question, and the documents retrieved from the Vector database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Let’s chat:&lt;/strong&gt; Now we can ask what our &lt;a href="https://rolfstreefkerk.com/posts/a-holistic-approach-to-health-and-weight-tracking" rel="noopener noreferrer"&gt;webpage&lt;/a&gt; is about:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/rmjhRU9tPcI"&gt;
&lt;/iframe&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  Experiment and Test
&lt;/h2&gt;

&lt;p&gt;This is just a starting point, and a working example of how quickly a RAG can be created for a specific purpose. A few things to consider that can optimize the solution;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Experiment with the token sizes of the &lt;em&gt;OpenAIEmbedding&lt;/em&gt; and &lt;em&gt;ChatOpenAI&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Changing the chain type of the &lt;em&gt;CombineDocsChain&lt;/em&gt;, to &lt;code&gt;map_reduce&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Improving the website text data quality by reducing the token size of the source text (&lt;a href="https://www.datacamp.com/tutorial/stemming-lemmatization-python" rel="noopener noreferrer"&gt;lemmatization and stemming&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Data input quality has a big impact on RAG output quality.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In conclusion, let’s summarize what’s been covered and learned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use LangFlow to quickly create a chat-bot that can answer questions on the content of your choice.&lt;/li&gt;
&lt;li&gt;LangFlow consists of many components of the LangChain framework. Understanding these components, can open up new possibilities to automate with LangFlow.&lt;/li&gt;
&lt;li&gt;LangFlow is a playground to experiment with AI. To prototype an idea quickly, then build it with LangChain.&lt;/li&gt;
&lt;li&gt;(&lt;em&gt;bonus&lt;/em&gt;) Huggingface spaces host a variety of AI generative applications such as image generation with &lt;a href="https://huggingface.co/spaces/stabilityai/stable-diffusion-3-medium" rel="noopener noreferrer"&gt;Stable diffusion&lt;/a&gt; or &lt;a href="https://huggingface.co/spaces/prithivMLmods/Midjourney" rel="noopener noreferrer"&gt;Midjourney&lt;/a&gt;, and &lt;a href="https://huggingface.co/spaces/KingNish/OpenGPT-4o" rel="noopener noreferrer"&gt;GPT4o chat&lt;/a&gt;. Check it out!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the next article I’d like to dive deeper into creating a RAG agent using LangGraph, and deploy that to a Docker container. Stay tuned if you’re interested.&lt;/p&gt;

&lt;p&gt;For now, lets discuss in the comments below: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What have you build?&lt;/li&gt;
&lt;li&gt;What can be improved in this example and why?&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Let's continue the discussion on &lt;a href="https://twitter.com/rolfstreefkerk" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Appreciate your time and till the next one!&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>ai</category>
      <category>rag</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to deploy your own website on AWS</title>
      <dc:creator>Rolf Streefkerk</dc:creator>
      <pubDate>Tue, 28 May 2024 03:36:21 +0000</pubDate>
      <link>https://forem.com/rolfstreefkerk/how-to-deploy-your-own-website-on-aws-1l05</link>
      <guid>https://forem.com/rolfstreefkerk/how-to-deploy-your-own-website-on-aws-1l05</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published on &lt;a href="https://rolfstreefkerk.com/posts/how-to-deploy-your-own-website-on-aws" rel="noopener noreferrer"&gt;rolfstreefkerk.com&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Take full control of your website, and following along with our how-to guide.&lt;/p&gt;

&lt;p&gt;Benefits of building and deploying a website from scratch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Own the code and control it as you see fit&lt;/li&gt;
&lt;li&gt;Learn AWS and how to deploy a website to AWS S3&lt;/li&gt;
&lt;li&gt;Understand DNS and Route53&lt;/li&gt;
&lt;li&gt;How to use DevOps to solve automation issues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Read on to get started.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://x.com/rolfstreefkerk" rel="noopener noreferrer"&gt;Follow me on Twitter&lt;/a&gt; to keep updated on the latest articles on AWS and more.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  You will need the following to get started
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;a static site&lt;/strong&gt;, I recommend one of these frameworks (and I've used):

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://gohugo.io/" rel="noopener noreferrer"&gt;Hugo&lt;/a&gt; 

&lt;ul&gt;
&lt;li&gt;existing &lt;a href="https://themes.gohugo.io/" rel="noopener noreferrer"&gt;themes&lt;/a&gt; will get you a website quick, such that you only have to modify color schemes and layouts.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;or &lt;a href="https://astro.build/" rel="noopener noreferrer"&gt;Astro&lt;/a&gt;; if you’d like to integrate React, VueJS etc. code as well.

&lt;ul&gt;
&lt;li&gt;use their themes page &lt;a href="https://astro.build/themes/" rel="noopener noreferrer"&gt;here&lt;/a&gt; to get a starting point. &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;an &lt;a href="https://aws.amazon.com/" rel="noopener noreferrer"&gt;AWS account&lt;/a&gt;&lt;/strong&gt;, which requires a credit card to setup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;a domain&lt;/strong&gt;, wherever you registered it.

&lt;ul&gt;
&lt;li&gt;In this how-to I use &lt;a href="//porkbun.com"&gt;Porkbun&lt;/a&gt; as my favorite registrar.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;a computer with&lt;/strong&gt;;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.terraform.io/" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt;/&lt;a href="https://opentofu.org/" rel="noopener noreferrer"&gt;OpenTofu&lt;/a&gt; installed. We use Terraform in this article.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/cli/" rel="noopener noreferrer"&gt;AWS CLI&lt;/a&gt; installed with profile configured you want to use for your website deployment.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://git-scm.com/downloads" rel="noopener noreferrer"&gt;Git&lt;/a&gt; command line tooling.&lt;/li&gt;
&lt;li&gt;your code editor of choice, I use &lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;VSCode&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;a &lt;a href="https://github.com/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; account&lt;/strong&gt; so you can fork my example repository.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;(optional) email inbox provider&lt;/em&gt;, I use &lt;a href="//migadu.com"&gt;Migadu&lt;/a&gt;.
## What are we creating today?&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  We are creating the following services and configurations:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;AWS S3 bucket to send your website source files to;&lt;/li&gt;
&lt;li&gt;AWS CloudFront distribution that will cache, optimize website delivery globally to your audience.&lt;/li&gt;
&lt;li&gt;AWS Route53 for your;

&lt;ul&gt;
&lt;li&gt;Email service records with DNSSec configuration,&lt;/li&gt;
&lt;li&gt;You can then hookup a newsletter service like &lt;code&gt;ConvertKit.com&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;Name Server Configuration for your domain; &lt;code&gt;yourwebsite.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;and the CloudFront distribution to optimize your website hosting.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;GitHub Actions for a CI/CD pipeline, deploying your website on command within a minute.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F40hkfi68ukl8m6vw3y02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F40hkfi68ukl8m6vw3y02.png" alt="Deploy your website to AWS" width="743" height="632"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup your Domain on AWS
&lt;/h2&gt;

&lt;p&gt;Login to your AWS Console.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to Route53, after you’ve logged in, and navigate to &lt;code&gt;Hosted zones&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Create your hosted zone and enter your website domain; &lt;code&gt;yourwebsite.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Make a note of the &lt;code&gt;Hosted zone ID&lt;/code&gt;, We’ll use that in the next step for Terraform to automate all the Route53 records to the correct Domain name.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ydaop4rxoper3xj7xt1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ydaop4rxoper3xj7xt1.png" alt="Terraform hosted zone id" width="800" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you choose to automate it using Terraform;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;export the Name Servers from your domain registrar (Porkbun, etc.). &lt;/li&gt;
&lt;li&gt;add the hosted zone resource configuration into &lt;a href="https://github.com/rpstreef/terraform-static-site" rel="noopener noreferrer"&gt;my example Terraform module&lt;/a&gt; and hook it up to all the related resources requiring the Hosted zone id.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  (Optional) Email hosting
&lt;/h3&gt;

&lt;p&gt;If you like to setup an email hosting solution,  I use migadu.com, keep the Route53 website open. &lt;/p&gt;

&lt;p&gt;We’ll import additional configuration text blocks into Route53 to make your domain work with the inbox service.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In Mail inbox service, there is a  &lt;code&gt;DNS Configuration&lt;/code&gt; panel.&lt;/li&gt;
&lt;li&gt;Get the &lt;code&gt;BIND&lt;/code&gt; records output, copy/paste the text of all the DNS records.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;If you require automatic mail server discovery for your Email; &lt;br&gt;
  Check for these strings in the provided DNS records; &lt;code&gt;_autodiscover&lt;/code&gt; or &lt;code&gt;autoconfig&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1mfczsfjwddthnluqs9d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1mfczsfjwddthnluqs9d.png" alt="Migadu BIND records" width="800" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Then in AWS Route53, for your hosted zone; &lt;code&gt;Import zone file&lt;/code&gt;, and copy paste the lines of text in that dialog box.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdj7gfedvyf4egixw2s2c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdj7gfedvyf4egixw2s2c.png" alt="Route53 Hosted zone file" width="800" height="238"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Now you can add your new email inbox in your mail apps. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have &lt;code&gt;_autodiscover&lt;/code&gt; and / or &lt;code&gt;autoconfig&lt;/code&gt; DNS records included, you can;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;go to your email app,&lt;/li&gt;
&lt;li&gt;add a new inbox using; email and password.&lt;/li&gt;
&lt;li&gt;Finished, inbox added without further configuration required.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Otherwise, take a note of your mail inbox service SMTP and IMAP server configurations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automating your AWS account setup with Terraform
&lt;/h2&gt;

&lt;p&gt;Now that we have the Domain in place, and the Mail inbox (optional), we can configure the actual site deployment.&lt;/p&gt;

&lt;p&gt;Create a new project by Forking: &lt;a href="https://github.com/rpstreef/terraform-yourwebsite.com" rel="noopener noreferrer"&gt;https://github.com/rpstreef/terraform-yourwebsite.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a template that will use Terraform modules from another Git repository; &lt;a href="https://github.com/rpstreef/terraform-static-site" rel="noopener noreferrer"&gt;https://github.com/rpstreef/terraform-static-site&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What does this template create?
&lt;/h3&gt;

&lt;p&gt;This template will create the following set of resources;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;S3 bucket for Terraform state&lt;/li&gt;
&lt;li&gt;S3 bucket for &lt;code&gt;yourwebsite.com&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;S3 CORS configuration for ConvertKit.com , this will allow CORS between ConvertKit JavaScript and your domain without warnings.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;ACM Certificate for SSL, &lt;code&gt;*.yourwebsite.com&lt;/code&gt;, and the ACM validation records for Route53 for auto-renewal of SSL.&lt;/li&gt;

&lt;li&gt;Route53 A, and AAAA records (IPv6)&lt;/li&gt;

&lt;li&gt;Route53 DNSSec,

&lt;ul&gt;
&lt;li&gt;only the first step! The second step must be done manually with your Domain Registrar.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Lambda function for redirects to index, ensures you have nice URL’s. 

&lt;ul&gt;
&lt;li&gt;E.g. &lt;a href="https://yourwebsite.com/contact" rel="noopener noreferrer"&gt;https://yourwebsite.com/contact&lt;/a&gt; instead of &lt;a href="https://yourwebsite.com/contact/index.html" rel="noopener noreferrer"&gt;https://yourwebsite.com/contact/index.html&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;CloudFront for caching, and web-page speed optimization, and SSL secured.&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  How to adjust the template?
&lt;/h3&gt;

&lt;p&gt;To make the template fit for your website. &lt;/p&gt;

&lt;p&gt;Do the following&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Change these lines in the &lt;code&gt;terraform.tfvars&lt;/code&gt; file :

&lt;ul&gt;
&lt;li&gt;where you read &lt;code&gt;yourdomain.com&lt;/code&gt;, &lt;/li&gt;
&lt;li&gt;and your &lt;code&gt;hosted_zone_id&lt;/code&gt; for &lt;code&gt;yourdomain.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;check 404 response at the bottom of the file to see if that matches up with your website structure. Additionally HTTP response codes can be added as blocks; &lt;code&gt;{}&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;If you need additionally CORS settings, add an extra rule in the same way as &lt;code&gt;f.convertkit.com&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# General&lt;/span&gt;
&lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"prod"&lt;/span&gt;
&lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
&lt;span class="nx"&gt;project&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"yourdomain.com"&lt;/span&gt;

&lt;span class="c1"&gt;# use tags to track your spend on AWS, seperate by 'product' for instance.&lt;/span&gt;
&lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"production"&lt;/span&gt;
    &lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="nx"&gt;product&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"yourdomain.com"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Which config line used in .aws/config&lt;/span&gt;
&lt;span class="nx"&gt;aws_profile&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"yourdomain-profile"&lt;/span&gt;

&lt;span class="c1"&gt;# Route53&lt;/span&gt;
&lt;span class="nx"&gt;hosted_zone_id&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Z000000000"&lt;/span&gt;

&lt;span class="c1"&gt;# www.yourdomain.com&lt;/span&gt;
&lt;span class="nx"&gt;product_name&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"yourdomain"&lt;/span&gt; &lt;span class="c1"&gt;# avoid to use `.`, this cause an error.&lt;/span&gt;
&lt;span class="nx"&gt;bucket_name&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"yourdomain.com"&lt;/span&gt; &lt;span class="c1"&gt;# your site is deployed here.&lt;/span&gt;

&lt;span class="c1"&gt;# S3 bucket CORS settings:&lt;/span&gt;
&lt;span class="nx"&gt;bucket_cors&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;rule1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;allowed_headers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nx"&gt;allowed_methods&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"PUT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nx"&gt;allowed_origins&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"https://f.convertkit.com"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nx"&gt;expose_headers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ETag"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nx"&gt;max_age_seconds&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;domain_names&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"yourdomain.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"www.yourdomain.com"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nx"&gt;custom_error_responses&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="nx"&gt;error_code&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;
    &lt;span class="nx"&gt;error_caching_min_ttl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
    &lt;span class="nx"&gt;response_code&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="nx"&gt;response_page_path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/404.html"&lt;/span&gt;
&lt;span class="p"&gt;}]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Make sure the configuration in &lt;code&gt;project-state.tf&lt;/code&gt; file is correct;

&lt;ul&gt;
&lt;li&gt;check the bucket name, &lt;/li&gt;
&lt;li&gt;and the AWS &lt;code&gt;profile&lt;/code&gt; name used, e.g. &lt;code&gt;yourwebsite-profile&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;projects_state_bucket_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tfstate-yourwebsite.com"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
    &lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"yourwebsite-profile"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# First we need a local state&lt;/span&gt;
    &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="s2"&gt;"local"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# After terraform apply, switch to remote S3 terraform state&lt;/span&gt;
    &lt;span class="cm"&gt;/*backend "s3" {
        bucket = "tfstate-yourwebsite"
        key = "terraform.tfstate"
        region = "us-east-1"
        profile = "yourwebsite-profile"

        encrypt = true
        acl = "private"
    }*/&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If all the configuration checks out;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;run &lt;code&gt;terraform init&lt;/code&gt;, this will download the dependent modules.&lt;/li&gt;
&lt;li&gt;then; &lt;code&gt;terraform apply&lt;/code&gt; &amp;gt; &lt;code&gt;yes&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;When it’s finished deploying, make note of the variables in the output. We’ll need them later on. To retrieve these later, type; &lt;code&gt;terraform output&lt;/code&gt; in the &lt;code&gt;./environments/production&lt;/code&gt; directory.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Which one came first? The chicken or the egg?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;When finished, we need to adjust the &lt;code&gt;project-state.tf&lt;/code&gt; file:

&lt;ul&gt;
&lt;li&gt;Place the  &lt;code&gt;backend "local"&lt;/code&gt; block in comments.&lt;/li&gt;
&lt;li&gt;Remove the comments from the  &lt;code&gt;backend "s3"&lt;/code&gt; block.&lt;/li&gt;
&lt;li&gt;Migrate the state from &lt;code&gt;local&lt;/code&gt; to &lt;code&gt;S3&lt;/code&gt;: 

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;terraform init -migrate-state&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;type: &lt;code&gt;yes&lt;/code&gt; to copy state from local to s3.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Now it’s fully deployed and we have saved our Terraform state to AWS S3, it’s no longer on your disk. You can remove those &lt;code&gt;tfstate&lt;/code&gt; files if you like.&lt;/p&gt;

&lt;h3&gt;
  
  
  Establishing DNSSec “Chain of Trust”
&lt;/h3&gt;

&lt;p&gt;The benefit of DNSSec is the establishment of the “chain of trust”. &lt;/p&gt;

&lt;p&gt;That means, it is verified that;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You own the domain, &lt;/li&gt;
&lt;li&gt;when you navigate to that domain, the information is coming from your servers and not from someone else’s server (e.g. hackers etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;If you’d like to learn more about DNSSec, &lt;a href="https://www.csoonline.com/article/569685/dnssec-explained-why-you-might-want-to-implement-it-on-your-domain.html" rel="noopener noreferrer"&gt;this&lt;/a&gt; article is a good primer&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now to finalize DNSSec configuration, you will have to manually modify the Domain registrar information.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First, get the required &lt;code&gt;DS&lt;/code&gt; records for DNSSec; &lt;code&gt;View information to create DS record&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzrbuax3y4n65hjr0pm5v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzrbuax3y4n65hjr0pm5v.png" alt="Route53: DNSSec" width="800" height="197"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Then, in the next screen click; &lt;code&gt;Establish a Chain of Trust&lt;/code&gt;. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You will see a table outlining configuration items.&lt;/p&gt;

&lt;p&gt;If you did not register your domain on Route53, click &lt;code&gt;Another Domain registrar&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;On Porkbun, my domain registrar, the screen looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyavg03ukeye7ienuuswp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyavg03ukeye7ienuuswp.png" alt="Porkbun.com DNSSec" width="800" height="607"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enter the following at the &lt;code&gt;dsData&lt;/code&gt; block; on the left is the Porkbun input field name, on the right as the value I will place the name used at &lt;code&gt;Route53&lt;/code&gt;:

&lt;ul&gt;
&lt;li&gt;Key Tag:  &lt;code&gt;Key tag&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;DS Data Algorithm: &lt;code&gt;Signing algorithm type&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Digest Type: &lt;code&gt;Digest algorithm type&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Digest: &lt;code&gt;Digest&lt;/code&gt; &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;If you have a different registrar, you’ll need to review their documentation, it may be slightly different.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  How to check your configuration works?
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Finally, use this online tool; &lt;a href="https://dnssec-debugger.verisignlabs.com/" rel="noopener noreferrer"&gt;https://dnssec-debugger.verisignlabs.com/&lt;/a&gt; to check your domain, if you’re getting all green check-marks. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If they’re all green, It means your chain of trust has been successfully established!&lt;/p&gt;

&lt;p&gt;Now we have a DNSSec secured domain configuration with an S3 static hosted site via CloudFront with SSL.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Performant&lt;/li&gt;
&lt;li&gt;Cheap &lt;/li&gt;
&lt;li&gt;and Secure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Upload your website
&lt;/h2&gt;

&lt;p&gt;We can use a local deployment setup with the AWS CLI, or via GitHub Actions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Local deployment with a script
&lt;/h3&gt;

&lt;p&gt;Depending on your system (Linux, Windows, Mac), you may need to alter this script.&lt;/p&gt;

&lt;p&gt;On Linux, we can automate your website deployment as follows using this bash script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#! /bin/bash&lt;/span&gt;

npm run build

aws s3 &lt;span class="nb"&gt;sync &lt;/span&gt;dist s3://yourwebsite.com &lt;span class="nt"&gt;--profile&lt;/span&gt; yourwebsite-profile

aws cloudfront create-invalidation &lt;span class="nt"&gt;--distribution-id&lt;/span&gt; &amp;lt;CloudFront Distr. Id&amp;gt; &lt;span class="nt"&gt;--paths&lt;/span&gt; &lt;span class="s2"&gt;"/*"&lt;/span&gt; &lt;span class="nt"&gt;--profile&lt;/span&gt; yourwebsite-profile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure to;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;replace &lt;code&gt;npm run build&lt;/code&gt; for the script that generates your static website build.&lt;/li&gt;
&lt;li&gt;replace &lt;code&gt;dist&lt;/code&gt; in the &lt;code&gt;aws s3 sync dist&lt;/code&gt;, if your website build is in another folder.&lt;/li&gt;
&lt;li&gt;replace &lt;code&gt;&amp;lt;CloudFront Distr. Id&amp;gt;&lt;/code&gt; with your CloudFront distribution id.

&lt;ul&gt;
&lt;li&gt;you can find it in the outputs after &lt;code&gt;terraform apply&lt;/code&gt; has finished; &lt;code&gt;cloudfront_distribution_id&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  GitHub Actions
&lt;/h3&gt;

&lt;p&gt;If you like to use automation instead, it’s very easy and cheap to setup.&lt;/p&gt;

&lt;h4&gt;
  
  
  What does this cost anyway?
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;Storage&lt;/th&gt;
&lt;th&gt;Minutes (per month)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Free&lt;/td&gt;
&lt;td&gt;500 MB&lt;/td&gt;
&lt;td&gt;2,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Pro&lt;/td&gt;
&lt;td&gt;1 GB&lt;/td&gt;
&lt;td&gt;3,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You can deploy quite a few times before you hit the &lt;code&gt;Pro&lt;/code&gt; ceiling in terms of &lt;code&gt;Minutes per month&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;storage&lt;/code&gt; size is based on your repository size which, for most, will be very hard to reach.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operating system&lt;/th&gt;
&lt;th&gt;Minute multiplier&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We choose a &lt;code&gt;Linux&lt;/code&gt; build environment, specifically &lt;code&gt;ubuntu-latest&lt;/code&gt;, to get the most out of our free minutes.&lt;/p&gt;

&lt;p&gt;Check out more about GitHub Action pricing &lt;a href="https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  How does it work?
&lt;/h4&gt;

&lt;p&gt;To deploy using GitHub Actions, do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;First, create a new file in your website’s GitHub repository at &lt;code&gt;.github/workflows/deploy-on-comment.yml&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the following code to the file: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;; I’m assuming your website is Node (v20) based. Adapt where needed!&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy on Comment&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;issue_comment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;created&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;edited&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Node.js&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;20'&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm install&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build website&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run build&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure AWS Credentials&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;aws-access-key-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_ACCESS_KEY_ID }}&lt;/span&gt;
          &lt;span class="na"&gt;aws-secret-access-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_SECRET_ACCESS_KEY }}&lt;/span&gt;
          &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;us-east-1&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Sync build output with S3 bucket&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws s3 sync ./dist s3://your-s3-bucket-name&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Invalidate CloudFront cache&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws cloudfront create-invalidation --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} --paths "/*"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There’s several secret variables that need to be created on GitHub, coming from the Terraform output we received earlier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt;:
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt;: &lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;CLOUDFRONT_DISTRIBUTION_ID&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you need to look up what these are again, navigate to your &lt;code&gt;terraform-yourwebsite.com&lt;/code&gt; git repository and then;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cd ./environments/production&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform output&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Input them at the following location in GitHub:&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuddfhnlxicco614wxx72.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuddfhnlxicco614wxx72.png" alt="GitHub Secrets configuration" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You can now &lt;code&gt;create an issue&lt;/code&gt; that details the updates on your website for example. &lt;br&gt;
For each comment that is added, the deployment will start.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can follow the deployment steps taken and the logs in the &lt;code&gt;Actions&lt;/code&gt; tab.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg4srw32r69d97j7za1dj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg4srw32r69d97j7za1dj.png" alt="GitHub issues, comment to deploy" width="800" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;(Optional) In case you’d want to change the GitHub Actions to use a &lt;code&gt;Pull request&lt;/code&gt; instead, you can modify that in the deploy script.
&amp;gt; For more alternative triggers, check out the &lt;a href="https://docs.github.com/en/actions/using-workflows/triggering-a-workflow" rel="noopener noreferrer"&gt;GitHub Actions documentation&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Your website is online!
&lt;/h2&gt;

&lt;p&gt;Now when you go to your web URL; &lt;code&gt;yourwebsite.com&lt;/code&gt;, everything should be up and running.&lt;/p&gt;

&lt;p&gt;What we have build;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;(Optional) Email hosting with Migadu (or choose any that you have);  e.g. &lt;code&gt;hello@yourwebsite.com&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;You can connect this to your ConvertKit.com mailing list for example.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Your own personal domain that is DNSSec secured. 

&lt;ul&gt;
&lt;li&gt;You’ll be certain no hackers can hi-jack your domain.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Your static Website on [[AWS]] using AWS S3.

&lt;ul&gt;
&lt;li&gt;Free web-hosting!&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;CloudFront Content Delivery Network (CDN), enabling:

&lt;ul&gt;
&lt;li&gt;SSL protected website. Form submits are all encrypted by default.&lt;/li&gt;
&lt;li&gt;Increased performance in load speeds, latency across the globe.&lt;/li&gt;
&lt;li&gt;URL rewrites for static websites. No &lt;code&gt;index.html&lt;/code&gt; will be displayed when navigating. &lt;/li&gt;
&lt;li&gt;and redirects for 404 not found pages. Your visitors will see the &lt;code&gt;404.html&lt;/code&gt; page instead of an error text message.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Questions? Let's discuss!
&lt;/h2&gt;

&lt;p&gt;What do you struggle with on AWS? &lt;br&gt;
Did you have issues with deploying on AWS?&lt;br&gt;
How would you do it?&lt;/p&gt;

&lt;p&gt;Let me know down in the comments or on &lt;a href="https://x.com/rolfstreefkerk" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Appreciate your time and till the next one!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Deploy with Eezze</title>
      <dc:creator>Rolf Streefkerk</dc:creator>
      <pubDate>Tue, 16 Apr 2024 12:33:17 +0000</pubDate>
      <link>https://forem.com/eezze/deploy-with-eezze-3li9</link>
      <guid>https://forem.com/eezze/deploy-with-eezze-3li9</guid>
      <description>&lt;p&gt;In this showcase of Eezze, we'll cover how we save time with our deployment automations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1. The Deployment Headache&lt;/li&gt;
&lt;li&gt;2. Seamless Deployment&lt;/li&gt;
&lt;li&gt;3. Tutorial&lt;/li&gt;
&lt;li&gt;4. Frequently Asked Questions&lt;/li&gt;
&lt;li&gt;5. Beyond the Basics&lt;/li&gt;
&lt;li&gt;6. Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  1. The Deployment Headache
&lt;/h3&gt;

&lt;p&gt;In the realm of software development, deploying backend applications often presents a significant challenge. Developers find themselves grappling with the intricate tasks of configuring servers, ensuring dependencies are correctly installed, and maintaining complex deployment scripts. These essential yet time-consuming processes can divert valuable resources away from core development efforts, impeding productivity and slowing down the overall software delivery pipeline.&lt;/p&gt;

&lt;p&gt;At Eezze, we understand these challenges intimately. Our platform leverages industry-standard tooling such as Ansible for configuration management, Terraform for infrastructure provisioning, and Docker containers for consistent application environments. By automating and streamlining the deployment process, Eezze eliminates the hassle, allowing teams to concentrate on building exceptional solutions.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Seamless Deployment
&lt;/h3&gt;

&lt;p&gt;With Eezze, we aim to transform the deployment experience, making it seamless and effortless. In this article, we'll walk you through the process of deploying backend applications using Eezze's platform, demonstrating how our solution automates complex tasks and ensures a smooth, hassle-free workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Tutorial
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Steps:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Set up a Connection in Eezze&lt;/strong&gt;, currently we support &lt;a href="https://releases.ubuntu.com/jammy/" rel="noopener noreferrer"&gt;Ubuntu 22.04&lt;/a&gt; &lt;code&gt;Server&lt;/code&gt; and &lt;a href="https://aws.amazon.com/" rel="noopener noreferrer"&gt;AWS&lt;/a&gt; &lt;code&gt;Cloud&lt;/code&gt; deployments. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feiy7ofharpfoa8abfaka.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feiy7ofharpfoa8abfaka.png" alt="Eezze Server/Cloud Integrations" width="800" height="435"&gt;&lt;/a&gt;&lt;br&gt;
    For a Ubuntu 22.04 Server, we define the server &lt;code&gt;host&lt;/code&gt; configuration in the &lt;code&gt;Connection Info&lt;/code&gt; tab, and then in the  &lt;code&gt;Authentication&lt;/code&gt; tab we configure the SSH connectivity (PEM Key) and User/Password for SuperUser access.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffr82w3pld959n26y2hj2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffr82w3pld959n26y2hj2.png" alt="Eezze Connection Details" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Create your datasources&lt;/strong&gt;, which are basically servers internally (linked to a Connection) or externally hosted servers and services;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8mnpfdnqy7w9b2qyxw2k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8mnpfdnqy7w9b2qyxw2k.png" alt="Eezze Support Datasources" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Configure a REST Server&lt;/strong&gt;, for example; the &lt;code&gt;host&lt;/code&gt; has already been filled in, since this is an internal service (it can be customized) Additionally a health check port is auto-configured to ensure keep-alive in Application Load Balancer scenario's functions properly:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzinewk6kpsnm6yaubrik.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzinewk6kpsnm6yaubrik.png" alt="Eezze Rest Datasource" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Entity definitions&lt;/strong&gt; for your Database Datasource will be automatically applied as migrations at the docker-compose REST server entrypoint, enabling up-to-date synchronization of your data model with your database(s):&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8z45tqyingyeday05a25.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8z45tqyingyeday05a25.png" alt="Eezze ERD Diagram: Edit" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Environment Variables and Vault (protected) variables&lt;/strong&gt; are automatically applied to your containers for run-time use:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqra3xt7zmqzusad253hh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqra3xt7zmqzusad253hh.png" alt="Eezze Vault secured Environment Variables" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add your Services&lt;/strong&gt; in our Service Group &amp;gt; Services editor.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkm7y3hysm19zy6u57wb7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkm7y3hysm19zy6u57wb7.png" alt="Eezze Services overview; Service Groups" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Deploy&lt;/strong&gt; to your empty Ubuntu 22.04 Server instance or a Connection with AWS Account credentials configured in the Authentication section in &lt;code&gt;step (1)&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsoem02r9pavuem9dn3lu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsoem02r9pavuem9dn3lu.png" alt="Eezze Deployment to Ubuntu 22.04 or AWS ECS" width="800" height="59"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We offer these public, and private cloud/server compatible solutions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ubuntu 22.04:&lt;/strong&gt; we use an &lt;a href="https://www.ansible.com/" rel="noopener noreferrer"&gt;Ansible&lt;/a&gt; playbook to install a complete environment that is ready to be used for &lt;a href="https://www.docker.com/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt; container deployment, then build and deploy the containers from a Github repository.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS ECS:&lt;/strong&gt; we've build a custom pipeline that interprets your project configuration, applies that our &lt;a href="https://www.terraform.io/" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt; script that can interpret our project configution exactly, and deploy all Docker Containers via &lt;a href="https://aws.amazon.com/codepipeline/" rel="noopener noreferrer"&gt;AWS CodePipeline&lt;/a&gt;/&lt;a href="https://docs.aws.amazon.com/codebuild/" rel="noopener noreferrer"&gt;CodeBuild&lt;/a&gt; to &lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/AWS_Fargate.html" rel="noopener noreferrer"&gt;ECS Fargate&lt;/a&gt;. These Containers are load balanced with &lt;a href="https://aws.amazon.com/elasticloadbalancing/application-load-balancer/" rel="noopener noreferrer"&gt;AWS ALB&lt;/a&gt; (Application Load Balancer) which terminates the SSL and enables custom domains to be easily linked using &lt;a href="https://aws.amazon.com/route53/" rel="noopener noreferrer"&gt;AWS Route53&lt;/a&gt; such as; &lt;code&gt;https://api.yourapp.com&lt;/code&gt;. &lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  4. Frequently Asked Questions
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Q: How does your company use Eezze for client projects?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A: Eezze is our proprietary in-house tooling that streamlines the development and deployment of backend solutions. While the Eezze platform itself is not directly accessible to clients, we leverage its capabilities to generate the necessary artifacts for your projects, such as Git repositories, Docker files, Ansible playbooks, and source code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: What kind of artifacts and deliverables can we expect from your use of Eezze?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A: By utilizing Eezze, we can provide you with a comprehensive set of artifacts tailored to your project requirements. These may include a fully configured Git repository with your application's source code (Typescript), Docker containers for consistent and isolated environments, Ansible playbooks for automated deployments, and any other necessary configuration files or scripts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Can you handle deploying our application to our existing infrastructure?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A: Absolutely. In addition to providing the generated artifacts, we can also leverage our Eezze automations to deploy your application directly to your existing infrastructure or environment. Our team can work closely with you to understand your infrastructure requirements and ensure a seamless deployment process, whether it's on-premises or in the cloud.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Can we customize or modify the generated artifacts from Eezze?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A: Absolutely. While the Eezze platform itself is proprietary, the generated artifacts, such as source code and configuration files, are fully accessible and customizable by your team. The only proprietary code is the Eezze plugin, which is used to enhance the capability of our code automations. We encourage collaboration and can work closely with you to make any necessary modifications or enhancements to the deliverables. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: How do you handle updates and maintenance for the generated artifacts and deployed applications?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A: As part of our ongoing support and maintenance, we can provide regular updates and enhancements to the generated artifacts and deployed applications. This may include bug fixes, security patches, or new features based on your evolving project requirements. Our team will work closely with you to ensure a seamless update and deployment process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Can we integrate the generated artifacts and deployed applications with our existing infrastructure and tooling?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A: Yes, the artifacts generated by Eezze and the deployed applications are designed to be compatible and easily integrated with a wide range of infrastructure and tooling. Whether you have existing deployment pipelines, monitoring systems, or other tools in place, we can ensure a smooth integration process, minimizing disruptions to your existing workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Beyond the Basics
&lt;/h3&gt;

&lt;p&gt;Eezze's advanced internal tooling brings significant benefits to customers, particularly in the areas of deployment automation and speed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS Deployment Excellence&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Eezze's AWS ECS deployment automation integrates a custom pipeline and Terraform scripting for precise interpretation of project configurations. Key features include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Custom Pipeline &amp;amp; Terraform: Interprets project configurations accurately for Docker Container deployment on ECS Fargate.&lt;/li&gt;
&lt;li&gt;AWS CodePipeline/CodeBuild: Automates deployment workflows for faster time-to-market.&lt;/li&gt;
&lt;li&gt;AWS ALB Load Balancing: Ensures scalability and reliability with SSL termination and custom domain integration using AWS Route53.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This streamlined deployment approach optimizes efficiency, accelerates deployment cycles, and enhances the security and scalability of your applications on AWS ECS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Global Reach with Ubuntu 22.04&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Eezze's automation extends globally to servers running Ubuntu 22.04, delivering rapid deployment benefits to customers. By automating the setup of all required software for a Docker container platform, Eezze accelerates deployment speed and ensures consistency across customer environments, leading to increased efficiency and reduced time-to-deployment for customer projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Conclusion
&lt;/h3&gt;

&lt;p&gt;Eezze's internal tooling directly benefits customers by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accelerating deployment timelines through advanced automation capabilities.&lt;/li&gt;
&lt;li&gt;Ensuring seamless integration with customers' existing infrastructure, minimizing disruptions and maximizing operational efficiency.&lt;/li&gt;
&lt;li&gt;Providing access to cutting-edge deployment solutions, such as AWS integration and global deployment automation, resulting in faster time-to-market and increased competitiveness for customer solutions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ready to experience the speed and efficiency benefits of Eezze's internal tooling for your deployments? &lt;a href="https://eezze.io" rel="noopener noreferrer"&gt;Request a personalized demo&lt;/a&gt; or quote today and discover how Eezze can transform your deployment experiences and deliver superior results for your projects.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>aws</category>
      <category>lowcode</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Build Real-Time Apps with Eezze</title>
      <dc:creator>Rolf Streefkerk</dc:creator>
      <pubDate>Tue, 02 Apr 2024 03:38:17 +0000</pubDate>
      <link>https://forem.com/eezze/build-real-time-apps-with-eezze-4ao1</link>
      <guid>https://forem.com/eezze/build-real-time-apps-with-eezze-4ao1</guid>
      <description>&lt;h2&gt;
  
  
  Build Real-Time Apps with Eezze
&lt;/h2&gt;

&lt;p&gt;Here's a quick overview of what we'll cover in this showcase of Eezze's Websocket functionality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;
Streamlining Websocket Integration with Eezze's UI-Based Approach

&lt;ul&gt;
&lt;li&gt;The Power of Actions&lt;/li&gt;
&lt;li&gt;Harnessing the 6 Directives&lt;/li&gt;
&lt;li&gt;Building Blocks to Backend&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Practical Showcase: Eezze in Action

&lt;ul&gt;
&lt;li&gt;1. Dynamic Template Rendering&lt;/li&gt;
&lt;li&gt;2. Stream Logging Data&lt;/li&gt;
&lt;li&gt;Generated Code (For Advanced Understanding)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Unlocking Real-Time Potential&lt;/li&gt;

&lt;li&gt;Conclusion&lt;/li&gt;

&lt;/ul&gt;



&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;Frustrated by the complexities of implementing real-time features in your applications? Eezze offers a streamlined solution, empowering developers to build responsive, data-driven apps without diving deep into websocket protocols and synchronization code.&lt;/p&gt;

&lt;p&gt;Eezze's low-code approach doesn't sacrifice control. Our platform recognizes that events are the lifeblood of any interactive application.  Our UI lets you precisely configure how your app reacts to user interactions, data updates, and connection changes.&lt;/p&gt;

&lt;p&gt;At its core, Eezze provides a set of directives for event handling.  Imagine setting up a collaborative workspace feature with a simple configuration like "on message update, broadcast to channel subscribers."&lt;/p&gt;

&lt;p&gt;In this article, we'll explore Eezze's event-driven workflow and build a real-time backend.  You'll experience how Eezze's UI-based logic accelerates development of features that traditionally require extensive coding.&lt;/p&gt;



&lt;h3&gt;
  
  
  Streamlining Websocket Integration with Eezze's UI-Based Approach
&lt;/h3&gt;

&lt;p&gt;Configure a websocket connection by specifying the server endpoint within a dedicated interface. You'll then define individual websocket event types, configuring custom "Actions" for each of the 6 supported directives.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Power of Actions
&lt;/h4&gt;

&lt;p&gt;Actions form the backbone of service development in Eezze, extending to websockets, REST APIs, and scheduled tasks. These sequential blocks handle data input, processing, and output. Within an Action block, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Query databases&lt;/li&gt;
&lt;li&gt;Manipulate files&lt;/li&gt;
&lt;li&gt;Manage email communication&lt;/li&gt;
&lt;li&gt;Generate output using the Twig templating language&lt;/li&gt;
&lt;li&gt;Integrate with third-party services via REST or Websocket APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Harnessing the 6 Directives
&lt;/h4&gt;

&lt;p&gt;Eezze puts control in your hands with these directives for building event-driven websocket backends:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;on&lt;/strong&gt; (event specification): Define the precise trigger for an action within your application (e.g., "on new message").&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;broadcast&lt;/strong&gt; (send to everyone): Distribute data to all connected users when the associated "on" event occurs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;channel&lt;/strong&gt; (send to only those that meet channel criteria): Target communication to specific user groups within your application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;notify&lt;/strong&gt; (notification to a specific connection or user): Send targeted notifications to individuals.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;onConnect&lt;/strong&gt;: Execute actions when a user establishes a connection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;onDisconnect&lt;/strong&gt;: Define responses triggered when a user disconnects.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Building Blocks to Backend
&lt;/h4&gt;

&lt;p&gt;Each directive, combined with custom input definitions, Actions, and output configurations, acts as a modular building block.  This allows you to assemble a robust websocket backend tailored to your application's specific needs.&lt;/p&gt;



&lt;h3&gt;
  
  
  Practical Showcase: Eezze in Action
&lt;/h3&gt;

&lt;p&gt;To demonstrate the power of Eezze's websocket integration, let's explore two use cases that we have used websockets for in our upcoming app developed with Eezze:&lt;/p&gt;

&lt;h4&gt;
  
  
  Dynamic Template Rendering
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Update a user profile page in real-time whenever the profile data is modified.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Steps:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create Service Group:&lt;/strong&gt;  Establish a Service Group connected to your Websocket Datasource (Websocket Container Service configuration). 
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2cb2yzdhr35j9b8fxki5.png" alt="Eezze Service Groups" width="800" height="414"&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configure 'On' Service:&lt;/strong&gt;  Within the group, create a new websocket service using the "On" event type triggered by a "render-profile" event.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fco20z4x02g88usqlk03o.png" alt="On Websocket" width="800" height="416"&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Define Actions:&lt;/strong&gt;  Set up the following Actions within the service:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Action 1:&lt;/strong&gt; Retrieve the updated profile data from your database ("Get One" from Profile table, &lt;code&gt;Check On&lt;/code&gt; parameters 'copied' from the Action Chain Input).
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffiraei8f3cdug6q12m8a.png" alt="Eezze Action Get One" width="800" height="398"&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Action 2:&lt;/strong&gt;  Render the profile HTML using a stored Twig template. Provide input mapping from Action 1.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffi8f2l4e7zwdv59ei0px.png" alt="Service Action Edit screen (Twig)" width="800" height="413"&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; The "render-profile" event triggers the service, updating the profile page with the latest data.&lt;/p&gt;

&lt;h4&gt;
  
  
  Stream Logging Data
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt;  Send real-time logging updates to a designated channel for monitoring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Steps:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Configure Channel Service:&lt;/strong&gt; Create a websocket service using the "Channel" directive. Provide an ID to identify the configuration and set up the specific channel name (e.g., &lt;code&gt;pr.${adm.input.projectId}&lt;/code&gt;).
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feugdqgfhhnf9s00z92ep.png" alt="Websocket Channel configuration" width="800" height="412"&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set Up Broadcast Service:&lt;/strong&gt; Create a second service using the "Broadcast" event linked to the &lt;code&gt;log&lt;/code&gt; event. The Action Chain Input defines &lt;code&gt;projectId&lt;/code&gt; and &lt;code&gt;data&lt;/code&gt; for the logging packet. The only Action emits this data.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flhl78x8874ain13tfchd.png" alt="Websocket Broadcast configuration" width="800" height="412"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Applications subscribed to the defined channel receive real-time logging updates. Eezze itself uses this functionality.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu34ezsdnc9lpc45lik1p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu34ezsdnc9lpc45lik1p.png" alt="Channel websocket output to Eezze front-end" width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Generated Code (For Advanced Understanding)
&lt;/h4&gt;

&lt;p&gt;For those curious about the underlying code that powers Eezze's websocket functionality, let's examine the Channel and Broadcast example in more detail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Websocket Controller (controller.ws.ts)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Eezze leverages decorators in TypeScript to streamline websocket service configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;project-logging-channel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;adm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ADM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LogicChain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Action 1 - Logic item '0'&lt;/span&gt;
    &lt;span class="nx"&gt;lc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;custom&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`pr-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;adm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// evaluate the result and then return the result&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;lc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;projectLoggingChannel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Broadcast&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;log&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;adm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ADM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LogicChain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Action 1 - Logic item "0"&lt;/span&gt;
    &lt;span class="nx"&gt;lc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;custom&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`pr-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;adm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// evaluate the result and then return the result&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;lc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;projectLogs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Broadcast Service Code (action.ts)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Broadcast Service itself demonstrates Eezze's focus on readability and efficiency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Send data packet to Channel&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Do&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;adm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ADM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LogicChain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Action 1 - Logic item "0"&lt;/span&gt;
    &lt;span class="nx"&gt;lc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;custom&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;adm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;adm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// evaluate the result and then return the result&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;lc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;_exec&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;h3&gt;
  
  
  Unlocking Real-Time Potential
&lt;/h3&gt;

&lt;p&gt;Eezze's UI-based approach isn't limited to the examples we've explored. Here are a few more ideas that can be quickly realized with Eezze.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Application Type&lt;/th&gt;
&lt;th&gt;Key Features&lt;/th&gt;
&lt;th&gt;How Eezze Simplifies Development&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Collaborative Whiteboard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Live drawing, shapes, annotations, multiple users&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;on&lt;/code&gt; events map directly to user actions, &lt;code&gt;broadcast&lt;/code&gt; updates the view&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data Visualization Dashboard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Real-time updating charts, graphs based on data streams&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;on&lt;/code&gt; data arrival triggers processing, updates pushed to clients&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Workflow Automation Monitor&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Visualize progress of complex workflows, track status changes, send notifications&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;on&lt;/code&gt; workflow events trigger updates and notifications, &lt;code&gt;channel&lt;/code&gt; could be used for team-specific views&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Key Point:&lt;/strong&gt;  Eezze enables developers to focus on the core interactions of their application rather than the complexities of managing real-time data flow.&lt;/p&gt;



&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Building real-time features has traditionally been a time consuming task, requiring specialized coding expertise that can slow down even the most agile development teams. Eezze transforms this process, enabling you to harness the power of websockets through a simple, event-driven UI.&lt;/p&gt;

&lt;p&gt;With Eezze, you focus on defining how your application should respond to user actions, data changes, and external events. The complexities of websocket management and synchronization are handled for you.&lt;/p&gt;

&lt;p&gt;Ready to see Eezze in action and how it can revolutionize your development workflow? Contact us on &lt;a href="https://eezze.io" rel="noopener noreferrer"&gt;Eezze.io&lt;/a&gt; or find us on &lt;a href="https://www.linkedin.com/company/eezze/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

</description>
      <category>websockets</category>
      <category>lowcode</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to setup a Serverless application with AWS SAM and Terraform</title>
      <dc:creator>Rolf Streefkerk</dc:creator>
      <pubDate>Thu, 07 May 2020 09:05:58 +0000</pubDate>
      <link>https://forem.com/rolfstreefkerk/how-to-setup-a-serverless-application-with-aws-sam-and-terraform-33m9</link>
      <guid>https://forem.com/rolfstreefkerk/how-to-setup-a-serverless-application-with-aws-sam-and-terraform-33m9</guid>
      <description>&lt;h1&gt;
  
  
  TLDR;
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;AWS Serverless Application Model (SAM) is used to quickly create Serverless applications with support for;

&lt;ul&gt;
&lt;li&gt;local development that emulates AWS Lambda, and API Gateway via Docker&lt;/li&gt;
&lt;li&gt;takes care of blue/green deployments via AWS CodeDeploy&lt;/li&gt;
&lt;li&gt;infrastructure as code, repeatedly deploy the same infrastructure on multiple environments (dev, test, production).&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Terraform is a Cloud agnostic Infrastructure as Code language and tooling.

&lt;ul&gt;
&lt;li&gt;takes care of non-serverless, permissions, the "difficult stuff".&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The result is a great local development experience via AWS SAM combined with the power of Terraform as the glue and as a general purpose solution for other AWS service deployments. &lt;/p&gt;

&lt;h1&gt;
  
  
  Chapters
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Overview&lt;/li&gt;
&lt;li&gt;
Terraform

&lt;ul&gt;
&lt;li&gt;AWS CodePipeline&lt;/li&gt;
&lt;li&gt;AWS CloudWatch&lt;/li&gt;
&lt;li&gt;AWS IAM&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

AWS SAM

&lt;ul&gt;
&lt;li&gt;AWS SAM Template&lt;/li&gt;
&lt;li&gt;Local testing&lt;/li&gt;
&lt;li&gt;AWS CodeDeploy&lt;/li&gt;
&lt;li&gt;Known issues&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Conclusion&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;The creation of this combination came out of necessity working together with developers that are not cloud native developers. Our initial solution was to emulate the online environment by using command line tooling that would execute a proxy which then executes the Lambda code. This resulted in some big problems down the line (hint, hard to debug!).&lt;/p&gt;

&lt;p&gt;The solution here is to avoid self created offline development environments, AWS SAM is directly supported and developed by the good people of Amazon themselves. Hence, we've started using AWS SAM because of its use of Docker containers to emulate AWS services and it provides the means to easily create the relevant Service events (e.g. events for SNS, API Gateway, etc.) for AWS Lambda local testing.&lt;/p&gt;

&lt;p&gt;In this article I'll discuss the background and the solution I created to successfully integrate Terraform deployments with AWS SAM. &lt;/p&gt;

&lt;p&gt;There are two repositories on Github that demonstrate my solution, please feel free to review these here:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/rpstreef" rel="noopener noreferrer"&gt;
        rpstreef
      &lt;/a&gt; / &lt;a href="https://github.com/rpstreef/terraform-aws-sam-integration-example" rel="noopener noreferrer"&gt;
        terraform-aws-sam-integration-example
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      An example how you can use both Terraform and AWS SAM
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/rpstreef" rel="noopener noreferrer"&gt;
        rpstreef
      &lt;/a&gt; / &lt;a href="https://github.com/rpstreef/aws-sam-node-example" rel="noopener noreferrer"&gt;
        aws-sam-node-example
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      AWS SAM NodeJS project example
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h1&gt;
  
  
  Overview
&lt;/h1&gt;

&lt;p&gt;First off, let's start with an overview of the solution and where the responsibilities have been defined for both Terraform and AWS SAM.&lt;/p&gt;

&lt;p&gt;The idea behind this setup is to take advantage of a couple of key features offered by AWS SAM;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local development via Docker images.&lt;/li&gt;
&lt;li&gt;Various Blue/Green Deployment scenarios supported via AWS CodeDeploy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then have the rest of the application be defined by Terraform that can do this reliably and in a DRY (Don't Repeat Yourself) structure.&lt;/p&gt;

&lt;p&gt;With that said, AWS SAM &lt;strong&gt;only&lt;/strong&gt; defines the API definition (API Gateway), the AWS Lambda functions, and Lambda CloudWatch Alarms for CodeDeploy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fkm8am42m4ym308iw6upn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fkm8am42m4ym308iw6upn.png" alt="overview-terraform-aws-sam" width="800" height="722"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Terraform
&lt;/h1&gt;

&lt;p&gt;Terraform is an all purpose infrastructure as code tool suite that can create infrastructure on a number of Cloud vendors. With its big support base, and the very useful modules system, it's relatively straightforward to deploy large infrastructures to AWS. &lt;/p&gt;

&lt;p&gt;With that said, what does Terraform deploy in this particular configuration with AWS SAM?&lt;/p&gt;

&lt;p&gt;Besides the complete AWS CodePipeline, as shown in the diagram above, it manages all "fringe" AWS services in &lt;a href="https://github.com/rpstreef/terraform-aws-sam-integration-example" rel="noopener noreferrer"&gt;this&lt;/a&gt; code example. From left to right:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWS Cognito&lt;/strong&gt; for identity management and authentication. Provides API Gateway security through Header authentication.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS CloudWatch&lt;/strong&gt; for monitoring with Alarms. Sets Alarms for API Gateway endpoints (latency, P95, P99 and 400/500 errors) and AWS Lambda (errors, timeouts etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS IAM&lt;/strong&gt; roles and permissions management. Allows execution of AWS Lambda by an API Gateway endpoint and sets general Role permission policies for AWS Lambda execution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For your own projects this would mean; services like SQS queues, Databases, S3 buckets etc. would all be created by Terraform and referenced in the AWS SAM template.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS CodePipeline
&lt;/h2&gt;

&lt;p&gt;To deploy AWS SAM, we use AWS CodePipeline to setup our automated CI/CD pipeline. It consists of the following stages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Source:&lt;/strong&gt; Retrieves the repository data from GitHub.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build:&lt;/strong&gt; Builds the solution based on a build script, &lt;code&gt;buildspec.yaml&lt;/code&gt;, and the repository data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy:&lt;/strong&gt; Deploys the build stage output.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Source
&lt;/h3&gt;

&lt;p&gt;Each stage can result in artifacts that need to cary over to the next stage in the pipeline, that's where the &lt;code&gt;artifact_store&lt;/code&gt; configuration comes in.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"artifact_store"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-codepipeline-artifacts-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;random_string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postfix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;acl&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"private"&lt;/span&gt;
  &lt;span class="nx"&gt;force_destroy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nx"&gt;lifecycle_rule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

    &lt;span class="nx"&gt;expiration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;days&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To get the source, the GitHub credentials (&lt;code&gt;OAuthToken&lt;/code&gt;), and connection information (&lt;code&gt;Owner&lt;/code&gt;, &lt;code&gt;Repo&lt;/code&gt;, and &lt;code&gt;Branch&lt;/code&gt;) needs to be provided.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;PollForSourceChanges&lt;/code&gt; if set to &lt;code&gt;true&lt;/code&gt;, will start the pipeline on every push to the configured &lt;code&gt;Branch&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_codepipeline"&lt;/span&gt; &lt;span class="s2"&gt;"_"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;# Misc configuration here&lt;/span&gt;

  &lt;span class="nx"&gt;artifact_store&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;artifact_store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;
    &lt;span class="nx"&gt;type&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"S3"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;stage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Source"&lt;/span&gt;

    &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Source"&lt;/span&gt;
      &lt;span class="nx"&gt;category&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Source"&lt;/span&gt;
      &lt;span class="nx"&gt;owner&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ThirdParty"&lt;/span&gt;
      &lt;span class="k"&gt;provider&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"GitHub"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt;
      &lt;span class="nx"&gt;output_artifacts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

      &lt;span class="nx"&gt;configuration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;OAuthToken&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;github_token&lt;/span&gt;
        &lt;span class="nx"&gt;Owner&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;github_owner&lt;/span&gt;
        &lt;span class="nx"&gt;Repo&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;github_repo&lt;/span&gt;
        &lt;span class="nx"&gt;Branch&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;github_branch&lt;/span&gt;
        &lt;span class="nx"&gt;PollForSourceChanges&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;poll_source_changes&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Build stage here&lt;/span&gt;

  &lt;span class="c1"&gt;# Deploy stage here&lt;/span&gt;

  &lt;span class="nx"&gt;lifecycle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ignore_changes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;lifecycle&lt;/code&gt; &lt;code&gt;ignore_changes&lt;/code&gt; configuration is applied because of a bug with the &lt;code&gt;OAuthToken&lt;/code&gt; parameter. See &lt;a href="https://github.com/terraform-providers/terraform-provider-aws/issues/2854" rel="noopener noreferrer"&gt;this&lt;/a&gt; issue for more information.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build
&lt;/h3&gt;

&lt;p&gt;The build stage is pretty self explanatory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;stage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Build"&lt;/span&gt;

  &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Build"&lt;/span&gt;
    &lt;span class="nx"&gt;category&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Build"&lt;/span&gt;
    &lt;span class="nx"&gt;owner&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWS"&lt;/span&gt;
    &lt;span class="k"&gt;provider&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CodeBuild"&lt;/span&gt;
    &lt;span class="nx"&gt;version&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt;
    &lt;span class="nx"&gt;input_artifacts&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;output_artifacts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="nx"&gt;configuration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;ProjectName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_codebuild_project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's more interesting is the buildspec file which defines the steps to produce our &lt;code&gt;artifacts&lt;/code&gt;. The &lt;code&gt;configuration.json&lt;/code&gt; file contains all the properties that are defined in the AWS SAM template. That way we can configure this run for each environment (dev, test, prod) individually with different settings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.2&lt;/span&gt;
&lt;span class="na"&gt;phases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runtime-versions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;nodejs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12&lt;/span&gt;
    &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pip3 install --upgrade aws-sam-cli&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cd dependencies/nodejs&lt;/span&gt;
      &lt;span class="c1"&gt;# Do not install dev dependencies&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm install --only=production&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cd ../../&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sam build&lt;/span&gt;
  &lt;span class="na"&gt;post_build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sam package --s3-bucket $ARTIFACT_BUCKET --output-template-file packaged.yaml&lt;/span&gt; 
&lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;packaged.yaml&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;configuration.json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Deploy
&lt;/h3&gt;

&lt;p&gt;Here we get to the meat and potatoes of the pipeline, the deployment stage.&lt;/p&gt;

&lt;p&gt;There are two steps it needs to undergo:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;CloudFormation change set:&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here we create the CloudFormation Change set on an existing stack or a new stack, which means it will "calculate" the difference and applies the changes necessary to the related services in your AWS account.&lt;/p&gt;

&lt;p&gt;For this we need an IAM Role with the appropriate permissions (&lt;code&gt;role_arn&lt;/code&gt; or &lt;code&gt;RoleArn&lt;/code&gt;, someone at Terraform will eventually find out which one ;) )&lt;/p&gt;

&lt;p&gt;The template is a result from our build process, hence &lt;code&gt;build::packaged.yaml&lt;/code&gt;. Then the &lt;code&gt;build::configuration.json&lt;/code&gt; file is in the Github repo that contains the relevant parameters for deployment of our stack.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Please note there's an inconsistency in how Terraform deploys action &lt;code&gt;CreateChangeSet&lt;/code&gt;. By just setting the &lt;code&gt;role_arn&lt;/code&gt; in the &lt;code&gt;action&lt;/code&gt; block, the &lt;code&gt;RoleARN&lt;/code&gt; in the &lt;code&gt;configuration&lt;/code&gt; block gets set to &lt;code&gt;null&lt;/code&gt;. You need to set the &lt;code&gt;RoleARN&lt;/code&gt; parameter to avoid this. I've reported this &lt;a href="https://github.com/terraform-providers/terraform-provider-aws/issues/13121" rel="noopener noreferrer"&gt;issue&lt;/a&gt; to the official GitHub repo.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;stage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Deploy"&lt;/span&gt;

  &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CreateChangeSet"&lt;/span&gt;
    &lt;span class="nx"&gt;category&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Deploy"&lt;/span&gt;
    &lt;span class="nx"&gt;owner&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWS"&lt;/span&gt;
    &lt;span class="k"&gt;provider&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CloudFormation"&lt;/span&gt;
    &lt;span class="nx"&gt;input_artifacts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;role_arn&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iam_cloudformation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role_arn&lt;/span&gt;
    &lt;span class="nx"&gt;version&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="nx"&gt;run_order&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="nx"&gt;configuration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;ActionMode&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CHANGE_SET_REPLACE"&lt;/span&gt;
      &lt;span class="nx"&gt;Capabilities&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CAPABILITY_IAM,CAPABILITY_AUTO_EXPAND"&lt;/span&gt;
      &lt;span class="nx"&gt;OutputFileName&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ChangeSetOutput.json"&lt;/span&gt;
      &lt;span class="nx"&gt;RoleArn&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iam_cloudformation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role_arn&lt;/span&gt;
      &lt;span class="nx"&gt;StackName&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack_name&lt;/span&gt;
      &lt;span class="nx"&gt;TemplatePath&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"build::packaged.yaml"&lt;/span&gt;
      &lt;span class="nx"&gt;ChangeSetName&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-deploy"&lt;/span&gt;
      &lt;span class="nx"&gt;TemplateConfiguration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"build::configuration.json"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;CloudFormation change set execute:&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the second &lt;code&gt;action&lt;/code&gt; step we actually execute the change set made previously&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;  &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Deploy"&lt;/span&gt;
    &lt;span class="nx"&gt;category&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Deploy"&lt;/span&gt;
    &lt;span class="nx"&gt;owner&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWS"&lt;/span&gt;
    &lt;span class="k"&gt;provider&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CloudFormation"&lt;/span&gt;
    &lt;span class="nx"&gt;input_artifacts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;version&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="nx"&gt;run_order&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;

    &lt;span class="nx"&gt;configuration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;ActionMode&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CHANGE_SET_EXECUTE"&lt;/span&gt;
      &lt;span class="nx"&gt;Capabilities&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CAPABILITY_IAM,CAPABILITY_AUTO_EXPAND"&lt;/span&gt;
      &lt;span class="nx"&gt;OutputFileName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ChangeSetExecuteOutput.json"&lt;/span&gt;
      &lt;span class="nx"&gt;StackName&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack_name&lt;/span&gt;
      &lt;span class="nx"&gt;ChangeSetName&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-deploy"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to find out which &lt;code&gt;configuration&lt;/code&gt; block parameters are required in defining CodePipeline stages and actions, look at &lt;a href="https://docs.aws.amazon.com/codepipeline/latest/userguide/action-reference-CloudFormation.html#action-reference-CloudFormation-config" rel="noopener noreferrer"&gt;this&lt;/a&gt; official documentation page for CodePipeline, and in this case the CloudFormation action reference in particular.&lt;/p&gt;

&lt;h3&gt;
  
  
  AWS CodePipeline Terraform module
&lt;/h3&gt;

&lt;p&gt;To run your own AWS CodePipeline for your AWS SAM integration, I've made this Terraform module available here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://registry.terraform.io/modules/rpstreef/codepipeline-sam/aws/1.0.0" rel="noopener noreferrer"&gt;https://registry.terraform.io/modules/rpstreef/codepipeline-sam/aws/1.0.0&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS CloudWatch
&lt;/h2&gt;

&lt;p&gt;With CloudWatch the main purpose is monitoring, so I've created monitoring for;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API Gateway endpoints:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt; &lt;strong&gt;Latency P95:&lt;/strong&gt; 95th percentile latency, which represents typical customer experienced latency figures.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Latency P99:&lt;/strong&gt; 99th percentile latency, represents the worst case latency that customers experience.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;400 Errors:&lt;/strong&gt; HTTP 400 errors reported by the endpoint.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;500 Errors:&lt;/strong&gt; HTTP 500 internal server errors reported by the endpoint.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Lambda functions:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Error rate:&lt;/strong&gt; Alarms on errors with a default of threshold of 1 percent during a 5 minute measurement period&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Throttle count:&lt;/strong&gt; Alarm on throttle count of 1 within 1 minute measurement period&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Iterator age:&lt;/strong&gt; Alarm for Stream based invocations such as Kinesis, alerts you when the time to execute is over 1 minute within a 5 minute measurement period. Check for more details &lt;a href="https://aws.amazon.com/premiumsupport/knowledge-center/lambda-iterator-age/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deadletter queue:&lt;/strong&gt; Alarm for DLQueue messages (for async Lambda invocations or SQS queues for example), 1 message within 1 minute triggers the alarm.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  AWS CloudWatch Terraform module
&lt;/h3&gt;

&lt;p&gt;If you want to create CloudWatch Alarms for API Gateway endpoints and/or AWS Lambda, you can use my Terraform module to quickly set them up:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://registry.terraform.io/modules/rpstreef/cloudwatch-alarms/aws/1.0.0" rel="noopener noreferrer"&gt;https://registry.terraform.io/modules/rpstreef/cloudwatch-alarms/aws/1.0.0&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS IAM
&lt;/h2&gt;

&lt;p&gt;To setup the required policies and permissions, the following two things are done.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Create Roles with Permissions policies that allow a service to use other services.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This creates a role based off of policy documents like shown below. Then this ARN gets applied in the AWS SAM template and that function can subsequently publish a message to an SNS topic for instance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"logs:CreateLogGroup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"logs:CreateLogStream"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"logs:PutLogEvents"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:logs:*:*:*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"cognito-idp:SignUp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"cognito-idp:AdminInitiateAuth"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"cognito-idp:ListUsers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"cognito-idp:AdminConfirmSignUp"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${cognito_user_pool_arn}"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"lambda:GetLayerVersion"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"sns:Publish"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${sns_topic_arn}"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"xray:PutTraceSegments"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"xray:PutTelemetryRecords"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"xray:GetSamplingRules"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"xray:GetSamplingTargets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"xray:GetSamplingStatisticSummaries"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We store this json and then call it with my module like show here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"iam"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github.com/rpstreef/tf-iam?ref=v1.1"&lt;/span&gt;

  &lt;span class="nx"&gt;namespace&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;namespace&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;
  &lt;span class="nx"&gt;resource_tag_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_tag_name&lt;/span&gt;

  &lt;span class="nx"&gt;assume_role_policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;module}&lt;/span&gt;&lt;span class="s2"&gt;/policies/lambda-assume-role.json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;template&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;module}&lt;/span&gt;&lt;span class="s2"&gt;/policies/lambda.json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;role_name&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_function_user_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-role"&lt;/span&gt;
  &lt;span class="nx"&gt;policy_name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_function_user_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-policy"&lt;/span&gt;

  &lt;span class="nx"&gt;role_vars&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cognito_user_pool_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cognito_user_pool_arn&lt;/span&gt;
    &lt;span class="nx"&gt;sns_topic_arn&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sns_topic_arn_lambda&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;role_vars&lt;/code&gt; get filled in the json template automatically upon inclusion via the &lt;code&gt;data "template_file"&lt;/code&gt; Terraform function called within that &lt;a href="https://github.com/rpstreef/tf-iam" rel="noopener noreferrer"&gt;IAM module&lt;/a&gt;. See the official documentation on this functionality &lt;a href="https://www.terraform.io/docs/providers/template/d/file.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Create a permission for one service to allow execution of another service.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To allow your API Gateway to execute an integrated AWS Lambda function, you have to give it permission like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lambda_permission"&lt;/span&gt; &lt;span class="s2"&gt;"_"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;principal&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"apigateway.amazonaws.com"&lt;/span&gt;
  &lt;span class="nx"&gt;action&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lambda:InvokeFunction"&lt;/span&gt;
  &lt;span class="nx"&gt;function_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_function_identity_arn&lt;/span&gt;

  &lt;span class="nx"&gt;source_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:execute-api:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;
    &lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;
    &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_caller_identity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;account_id&lt;/span&gt;
    &lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api_gateway_rest_api_id&lt;/span&gt;
  &lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/*/*"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This &lt;code&gt;lambda:InvokeFunction&lt;/code&gt; action tells the principle &lt;code&gt;apigateway.amazonaws.com&lt;/code&gt; that the &lt;code&gt;source_arn&lt;/code&gt; is allowed to execute the &lt;code&gt;function_name&lt;/code&gt;.  You can apply this similarly for the SNS service (&lt;code&gt;sns.amazonaws.com&lt;/code&gt;) or any other service that can integrate with AWS Lambda. &lt;/p&gt;

&lt;p&gt;The actual integration of the AWS Lambda with the endpoint is defined in the OpenAPI document that is included in the AWS SAM repository. With these two together, you have a functioning integration.&lt;/p&gt;

&lt;h1&gt;
  
  
  AWS SAM
&lt;/h1&gt;

&lt;p&gt;Alright so we know what the Terraform parts do, how about AWS SAM?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Check my example repository &lt;a href="https://github.com/rpstreef/aws-sam-node-example" rel="noopener noreferrer"&gt;here&lt;/a&gt; if you'd like to go in-depth.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's see that diagram again.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fkm8am42m4ym308iw6upn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fkm8am42m4ym308iw6upn.png" alt="overview-terraform-aws-sam" width="800" height="722"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the AWS CodePipeline is finished it will deploy, from left to right:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWS API Gateway:&lt;/strong&gt; Using the OpenAPI document (&lt;code&gt;api.yaml&lt;/code&gt; in my example repo) to specify the integration, Cognito Security, and our endpoints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Lambda:&lt;/strong&gt; Creates just the functions with their environment variables.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS CloudWatch deploy Alarms:&lt;/strong&gt; To make sure we track the right version of the deployed Lambda function, we create and update the Alarms everytime in this template.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then there are a few artifacts in this repo that control the integration with Terraform and define the AWS SAM stack.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;template.yaml&lt;/code&gt;: The most important file, the AWS SAM template that determines the AWS services that get deployed and how they're configured. More details on the anatomy of the AWS SAM template &lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-template-anatomy.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;configuration.json&lt;/code&gt;: This file is a CloudFormation template that specifies the parameters you can see in the &lt;code&gt;template.yaml&lt;/code&gt; file at the top. That way each environment can have a different configurations with the same deployment of AWS services. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;api.yaml&lt;/code&gt;: The OpenAPI document describing our API Gateway endpoints and the JSON Schema for the input and output models used for each of those endpoints. Details on the workings of this I have discussed in a previous article &lt;a href="https://dev.to/rolfstreefkerk/openapi-with-terraform-on-aws-api-gateway-17je"&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  AWS SAM Template
&lt;/h2&gt;

&lt;p&gt;As you can tell from the official documents on the AWS SAM template anatomy, it resembles CloudFormation for the most part and it introduces a few special &lt;code&gt;Resources&lt;/code&gt; to more quickly create serverless applications. &lt;/p&gt;

&lt;p&gt;Rather than going over every bit of detail, I'll focus on two features that make creating templates much less painful. After which I'll discuss how to get local development up and running and show a bit of the CodeDeploy goodness that comes along with AWS SAM applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Globals
&lt;/h3&gt;

&lt;p&gt;With &lt;code&gt;Globals&lt;/code&gt; you have the ability to apply configuration across all individually defined resources at once. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Globals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Function&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nodejs12.x&lt;/span&gt;
    &lt;span class="na"&gt;Tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Environment&lt;/span&gt;
      &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AppName&lt;/span&gt;

&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;IdentityFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::Function&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;FunctionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Fn::Sub&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${Environment}-${AppName}-identity&lt;/span&gt;

  &lt;span class="na"&gt;UserFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::Function&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;FunctionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Fn::Sub&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${Environment}-${AppName}-user&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means that each function declared under &lt;code&gt;Resources&lt;/code&gt; will use the &lt;code&gt;Runtime&lt;/code&gt; NodeJS version 12 and they will get the same &lt;code&gt;Tags&lt;/code&gt; applied. This saves a lot of space configuring the same properties for each individual function.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. OpenAPI
&lt;/h3&gt;

&lt;p&gt;With the OpenAPI document, certain definitions in AWS SAM template are unnecessary, therefore they can be omitted. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;IdentityFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::Function&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;src/&lt;/span&gt;
      &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;identity/app.lambdaHandler&lt;/span&gt;
      &lt;span class="na"&gt;Role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;IdentityRoleARN&lt;/span&gt;
      &lt;span class="na"&gt;Events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;identityAuthenticate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Api&lt;/span&gt;
          &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/identity/authenticate&lt;/span&gt;
            &lt;span class="na"&gt;Method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;post&lt;/span&gt;
            &lt;span class="na"&gt;RestApiId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Api&lt;/span&gt;
        &lt;span class="na"&gt;identityRegister&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Api&lt;/span&gt;
          &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/identity/register&lt;/span&gt;
            &lt;span class="na"&gt;Method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;post&lt;/span&gt;
            &lt;span class="na"&gt;RestApiId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Api&lt;/span&gt;
        &lt;span class="na"&gt;identityReset&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Api&lt;/span&gt;
          &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/identity/reset&lt;/span&gt;
            &lt;span class="na"&gt;Method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;post&lt;/span&gt;
            &lt;span class="na"&gt;RestApiId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Api&lt;/span&gt;
        &lt;span class="na"&gt;identityVerify&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Api&lt;/span&gt;
          &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/identity/verify&lt;/span&gt;
            &lt;span class="na"&gt;Method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;post&lt;/span&gt;
            &lt;span class="na"&gt;RestApiId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Api&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Events&lt;/code&gt; property does two things; &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It allows API Gateway to execute this function,&lt;/li&gt;
&lt;li&gt;and it configures the integration for these endpoints with this Lambda function.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first, we solve by setting the permissions in our Terraform configuration as demonstrated in this chapter.&lt;br&gt;
The second we solve by setting the integration configuration using this AWS OpenAPI extension,  &lt;code&gt;x-amazon-apigateway-integration&lt;/code&gt;, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;/identity/authenticate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;identityAuthenticate&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Authenticate user (either login, or continue session)&lt;/span&gt;
      &lt;span class="na"&gt;x-amazon-apigateway-integration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Fn::Sub&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${IdentityFunction.Arn}/invocations&lt;/span&gt;
        &lt;span class="na"&gt;passthroughBehavior&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;when_no_match"&lt;/span&gt;
        &lt;span class="na"&gt;httpMethod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST"&lt;/span&gt;
        &lt;span class="na"&gt;timeoutInMillis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;APITimeout&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aws_proxy"&lt;/span&gt;
  &lt;span class="c1"&gt;# omitted propertes for brevity&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make this interpolation work, you'll have to include this document in the &lt;code&gt;Api&lt;/code&gt; resource like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::Api&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;DefinitionBody&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Fn::Transform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Include&lt;/span&gt;
          &lt;span class="na"&gt;Parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api.yaml&lt;/span&gt;
      &lt;span class="c1"&gt;# omitted propertes for brevity&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This has an added benefit that the OpenAPI document is kept separate from your AWS SAM template, so you can use the OpenAPI document for instance to generate client and/or server side code without modification.&lt;/p&gt;

&lt;p&gt;The idea is to make the AWS SAM use as minimal as possible, and have everything else done by either the OpenAPI specification or my Terraform modules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local testing
&lt;/h2&gt;

&lt;p&gt;To test run locally, a few things need to be in place:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Docker installation:&lt;/strong&gt; Make sure you have Docker installed or Docker Desktop when you're on Windows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Installed dependencies:&lt;/strong&gt;, the NodeJS dependencies need to be installed, &lt;code&gt;npm install&lt;/code&gt;, before the related code can be executed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code editor with Debugging capabilities:&lt;/strong&gt;, to link the debugging port with your code editor view. I use Visual Code which has support for this integrated, like most editors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment variables:&lt;/strong&gt; The last part of emulating the online environment, duplicate your variables in the &lt;code&gt;env.json&lt;/code&gt; file categorized by function name. Also note that when running in  a local environment, the &lt;code&gt;AWS_SAM_LOCAL&lt;/code&gt; environment variable is set to &lt;code&gt;true&lt;/code&gt;. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now start the local development api with &lt;code&gt;sam local start-api -d 5858 -n env.json&lt;/code&gt; or via the npm run command in my repo &lt;code&gt;npm run start-api&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The following will appear indicating the api server is ready:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Mounting UserFunction at http://127.0.0.1:3000/user &lt;span class="o"&gt;[&lt;/span&gt;GET, POST]
Mounting IdentityFunction at http://127.0.0.1:3000/identity/reset &lt;span class="o"&gt;[&lt;/span&gt;POST]
Mounting IdentityFunction at http://127.0.0.1:3000/identity/register &lt;span class="o"&gt;[&lt;/span&gt;POST]
Mounting IdentityFunction at http://127.0.0.1:3000/identity/authenticate &lt;span class="o"&gt;[&lt;/span&gt;POST]
Mounting IdentityFunction at http://127.0.0.1:3000/identity/verify &lt;span class="o"&gt;[&lt;/span&gt;POST]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Debugging is enabled on port 5858, you'll see this notice in the command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Debugger listening on ws://0.0.0.0:5858/275235c0-635f-4e27-b412-d4137d4e1c64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I can set breakpoints in my code editor and attach the debugging tooling to the debugging port 5858. This should stop execution at that breakpoint, allowing you to live view variables and the call stack.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fe8gabxoev5g5c6vnmifu.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fe8gabxoev5g5c6vnmifu.gif" alt="magic" width="300" height="168"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating events
&lt;/h3&gt;

&lt;p&gt;Now that we can actually start a local api, the next thing is to actually replicate the events you can receive from other other AWS services such as DynamoDB, S3, SNS etc.&lt;/p&gt;

&lt;p&gt;To view the supported list of events, execute &lt;code&gt;sam local generate-event&lt;/code&gt;.  Then we can choose to emulate SNS like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam &lt;span class="nb"&gt;local &lt;/span&gt;generate-event sns notification
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To customize it, add the appropriate flags that wish to have in the event. To list those flags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam &lt;span class="nb"&gt;local &lt;/span&gt;generate-event sns notification &lt;span class="nt"&gt;--help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or just manually edit the json files.&lt;/p&gt;

&lt;p&gt;Once we have these events, we can do a local test run by invoking the lambda directly with the event we want to test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam &lt;span class="nb"&gt;local &lt;/span&gt;invoke IdentityFunction &lt;span class="nt"&gt;-e&lt;/span&gt; ./events/sns-notification.json &lt;span class="nt"&gt;-d&lt;/span&gt; 5858 &lt;span class="nt"&gt;-n&lt;/span&gt; env.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  AWS CodeDeploy
&lt;/h2&gt;

&lt;p&gt;The last piece of functionality that makes AWS SAM such great tooling to use, is the CodeDeploy functionality. Next to support for ECS, and EC2, it also supports deployment to Lambda functions. With this service you basically get blue/green deployment out of the box for &lt;a href="https://aws.amazon.com/codedeploy/pricing/" rel="noopener noreferrer"&gt;free&lt;/a&gt; without having to set anything up at all.&lt;/p&gt;

&lt;p&gt;How this works is, once the CloudFormation stack (changes) are getting applied, it will use CodeDeploy for the Lambda function parts which looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3qnz0xojpo90w457ci53.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3qnz0xojpo90w457ci53.png" alt="codedeploy-example" width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Depending on the deployment strategy chosen in your AWS SAM template (under property &lt;code&gt;DeploymentPreference&lt;/code&gt;), for instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;IdentityFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::Function&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;FunctionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Fn::Sub&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${Environment}-${AppName}-identity&lt;/span&gt;
      &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Provides all Cognito functions through 4 API Rest calls&lt;/span&gt;
      &lt;span class="na"&gt;DeploymentPreference&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LambdaCanary10Percent5Minutes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will automatically do traffic shifting from your old version to the new Lambda version code. In this example, it will shift 10 percent to the new version and 90% to your old version. After 5 minutes, 100% of your new code is deployed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;See &lt;a href="https://docs.aws.amazon.com/codedeploy/latest/userguide/deployment-configurations.html" rel="noopener noreferrer"&gt;this&lt;/a&gt; official article for all the available configurations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To manage potential errors and rollbacks, this is where the CloudWatch Alarms come in that were discussed in the introduction of this chapter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;IdentityCanaryErrorsAlarm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::CloudWatch::Alarm&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;AlarmName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
        &lt;span class="na"&gt;Fn::Sub&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${Environment}-${AppName}-identity-canary-alarm&lt;/span&gt;
      &lt;span class="na"&gt;AlarmDescription&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Identity Lambda function canary errors&lt;/span&gt;
      &lt;span class="na"&gt;ComparisonOperator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GreaterThanThreshold&lt;/span&gt;
      &lt;span class="na"&gt;EvaluationPeriods&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
      &lt;span class="na"&gt;MetricName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Errors&lt;/span&gt;
      &lt;span class="na"&gt;Namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS/Lambda&lt;/span&gt;
      &lt;span class="na"&gt;Period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;
      &lt;span class="na"&gt;Statistic&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Sum&lt;/span&gt;
      &lt;span class="na"&gt;Threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
      &lt;span class="na"&gt;Dimensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Resource&lt;/span&gt;
          &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Fn::Sub&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${IdentityFunction}:live&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;FunctionName&lt;/span&gt;
          &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
            &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;IdentityFunction&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ExecutedVersion&lt;/span&gt;
          &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Fn::GetAtt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;IdentityFunction&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Version&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whenever a deployment is done with CodeDeploy, it will update the CloudWatch alarm to monitor the latest version for any errors within the &lt;code&gt;EvaluationPeriods&lt;/code&gt; specified. If the &lt;code&gt;Threshold&lt;/code&gt; is breached, it will do a rollback to the old version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Known issues
&lt;/h2&gt;

&lt;p&gt;Not all is perfect yet with AWS SAM, I've logged some of the errors and issues I came across for your convenience.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. I'm not seeing my code changes when running my api with &lt;code&gt;sam local start-api&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Unlike what the CLI tool suggests, &lt;code&gt;You only need to restart SAM CLI if you update your AWS SAM template&lt;/code&gt;, this is actually not true.&lt;/p&gt;

&lt;p&gt;You'll need to run &lt;code&gt;sam build&lt;/code&gt; after every code change. Hopefully this will get fixed in later versions of the tool. Links to related issues on Github;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/awslabs/aws-sam-cli/issues/1921" rel="noopener noreferrer"&gt;sam local start-api doesn't update on changes&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/awslabs/aws-sam-cli/issues/921" rel="noopener noreferrer"&gt;watch Option for SAM Build command&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The work around suggested is to use &lt;code&gt;nodemon&lt;/code&gt; and then run &lt;code&gt;nodemon --exec sam build&lt;/code&gt; next to the command &lt;code&gt;sam local start-api -d 5858 -n env.json&lt;/code&gt;. This way any code changes trigger the &lt;code&gt;sam build&lt;/code&gt; and you can keep the local api running in parallel.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. 'CreateFile', 'The system cannot find the file specified.'
&lt;/h4&gt;

&lt;p&gt;Makes sure your Docker environment is running before you run any local invocations.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Runtime.ImportModuleError
&lt;/h4&gt;

&lt;p&gt;To run AWS SAM locally, make sure you run &lt;code&gt;npm install&lt;/code&gt; in your dependencies folder. For my example, the dependencies are located in &lt;code&gt;./dependencies/nodejs&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  4. No deployment configuration found for name: LambdaAllAtOnce
&lt;/h4&gt;

&lt;p&gt;Somehow it does not find this configuration even though it is listed. To solve it, use the &lt;code&gt;AllAtOnce&lt;/code&gt; as &lt;code&gt;DeploymentPreference&lt;/code&gt; which also seems to work for Lambda next to EC2.&lt;/p&gt;

&lt;h4&gt;
  
  
  5. AWS SAM creates a stage called "Stage" by default.
&lt;/h4&gt;

&lt;p&gt;The solution here as suggest by &lt;a href="https://github.com/awslabs/serverless-application-model/issues/191" rel="noopener noreferrer"&gt;this&lt;/a&gt; issue, is to add the OpenAPI version to the globals section of your AWS SAM template like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Globals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;OpenApiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3.0.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;The inclusion of AWS SAM in our local development workflow has made things significantly more efficient and the ability to use Terraform for almost everything else keeps most of what we already did the same. The best of both worlds!&lt;/p&gt;

&lt;p&gt;There are some definitive improvements to be made in this setup I'm sure. My first thoughts are using &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html" rel="noopener noreferrer"&gt;AWS Systems Manager Parameter store&lt;/a&gt; for synching parameters between Terraform and AWS SAM. For now however, the manual synching works and only needs to be done at setup time.&lt;/p&gt;

&lt;p&gt;If you have any other idea's or improvements, let me know in the comments below.&lt;/p&gt;

&lt;p&gt;Thanks for reading and leave a heart when you enjoyed or appreciated the article.&lt;/p&gt;

&lt;p&gt;Till next time!&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>terraform</category>
      <category>devops</category>
      <category>aws</category>
    </item>
    <item>
      <title>How to protect Serverless (Open)API's?</title>
      <dc:creator>Rolf Streefkerk</dc:creator>
      <pubDate>Tue, 24 Mar 2020 11:26:26 +0000</pubDate>
      <link>https://forem.com/rolfstreefkerk/how-to-protect-serverless-open-api-s-5eem</link>
      <guid>https://forem.com/rolfstreefkerk/how-to-protect-serverless-open-api-s-5eem</guid>
      <description>&lt;p&gt;Last week I was unable to deliver a proper article due to the sudden Corona measures (I hope everyone stays safe out there!), so I decided to postpone.&lt;/p&gt;

&lt;p&gt;With that said, I'd like to discuss how to secure your (Open)API's using the tools that already exist in the AWS services we're using, and how &lt;a href="https://aws.amazon.com/waf/" rel="noopener noreferrer"&gt;AWS WAF&lt;/a&gt; (Web Application Firewall) can potentially assist (for a price).&lt;/p&gt;

&lt;p&gt;We'll cover the following topics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;
OWASP Top 10

&lt;ul&gt;
&lt;li&gt;(1) Injection&lt;/li&gt;
&lt;li&gt;(2) Broken Authentication&lt;/li&gt;
&lt;li&gt;(3) Sensitive data exposure&lt;/li&gt;
&lt;li&gt;(4) XML External Entities (XXE)&lt;/li&gt;
&lt;li&gt;(5) Broken Access Control&lt;/li&gt;
&lt;li&gt;(6) Security Misconfiguration&lt;/li&gt;
&lt;li&gt;(7) Cross-Site Scripting XSS&lt;/li&gt;
&lt;li&gt;(8) Insecure Deserialization&lt;/li&gt;
&lt;li&gt;(9) Using Components with Known Vulnerabilities&lt;/li&gt;
&lt;li&gt;(10) Insufficient Logging &amp;amp; Monitoring&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;In closing&lt;/li&gt;

&lt;li&gt;Further reading&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;To give this discussion better context, I'll be using the &lt;a href="https://owasp.org/www-project-top-ten/" rel="noopener noreferrer"&gt;OWASP Top 10&lt;/a&gt; Web Application Vulnerabilities as a guiding list.&lt;/p&gt;

&lt;p&gt;The architecture to be considered for this article is my reference application architecture used for this series of OpenAPI articles.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/rpstreef" rel="noopener noreferrer"&gt;
        rpstreef
      &lt;/a&gt; / &lt;a href="https://github.com/rpstreef/openapi-tf-example" rel="noopener noreferrer"&gt;
        openapi-tf-example
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Example of how you can use OpenAPI with AWS API Gateway, Also includes integrations with AWSLambda, AWS Cognito, AWS SNS and CloudWatch logs
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;It features;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API Gateway&lt;/strong&gt;, exposes all the available services &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lambda&lt;/strong&gt;, contains the code that ultimately exposes/processes data to/from the API's.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cognito for Identity Management&lt;/strong&gt;, identity registration, login, and session management.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudWatch logs&lt;/strong&gt;, error, info, debug logging and Alarms service.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SNS&lt;/strong&gt;, reliable and durable pub/sub message based integration service.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS CodePipeline and CodeBuild&lt;/strong&gt;, CI/CD for Lambda code deployments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Normally you would also use a data source, here we can assume we're either using NoSQL (DynamoDB) or a SQL variant (MySQL, Postgress).&lt;/p&gt;

&lt;h1&gt;
  
  
  OWASP Top 10
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;The OWASP list represents a broad consensus about the most critical security risks to web applications.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For each of the 10 items I'll go through the general &lt;strong&gt;risks&lt;/strong&gt; associated with that item, and then the possible &lt;strong&gt;solutions&lt;/strong&gt; to apply for each of the relevant AWS services. In the &lt;strong&gt;conclusion&lt;/strong&gt; for each chapter I'll briefly state my recommendation.&lt;/p&gt;

&lt;h2&gt;
  
  
  (1) Injection
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Risks
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://owasp.org/www-project-top-ten/OWASP_Top_Ten_2017/Top_10-2017_A1-Injection" rel="noopener noreferrer"&gt;Injection risks&lt;/a&gt; are most prevalent in environment variables, (No)SQL queries, JSON/XML parsers, and API query/body parameters. &lt;/p&gt;

&lt;h3&gt;
  
  
  Solutions
&lt;/h3&gt;

&lt;h4&gt;
  
  
  AWS API Gateway &amp;amp; OpenAPI
&lt;/h4&gt;

&lt;p&gt;To mitigate any type of injection attempt we can force correct input of our API's. To do that we can use the concept of "Models" in API Gateway. These models are essentially JSON Schema's that define what type of data is accepted as input or what is given as output after execution.&lt;/p&gt;

&lt;p&gt;For example, this register model has a regex pattern that defines what kind of information is allowed to be input into API's using this model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Register"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"firstName"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lastName"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"username"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"pattern"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^[_A-Za-z0-9-&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;+]+(&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.[A-Za-z0-9]+)*(&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.[A-Za-z]{2,})$"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"password"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"username"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"firstName"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lastName"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Registration details"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;When you enable &lt;code&gt;validation&lt;/code&gt; on your API resource in your OpenAPI specification, it will use this JSON Schema to automatically validate your input. This will support regex validation as well and not just the required property(!). See &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-request-validation-set-up.html" rel="noopener noreferrer"&gt;this&lt;/a&gt; AWS documentation for more information.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;/identity/register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Identity"&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Register&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;new&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Business&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;user"&lt;/span&gt;
      &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;identityRegister"&lt;/span&gt;
      &lt;span class="na"&gt;requestBody&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Registration&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;details"&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/Register"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;x-amazon-apigateway-request-validator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;full&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The request validator selects which configuration to use for that particular endpoint.&lt;/p&gt;

&lt;p&gt;Somewhere else in the root of the document we should configure the variants, like so:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;x-amazon-apigateway-request-validators&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;full&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;validateRequestBody&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="nx"&gt;validateRequestParameters&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="nx"&gt;body-only&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;validateRequestBody&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="nx"&gt;validateRequestParameters&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Of course, you will have to do more data validation within your application code, &lt;code&gt;string&lt;/code&gt; will accept anything. Here you can make a trade off between API Gateway validation and code based validation. The advantage of API Gateway validation is that you will not be charged for any code execution at all.&lt;/p&gt;

&lt;p&gt;When you execute against an API with this configuration and you supply incorrect input, this is the response:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Invalid request body"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  AWS Lambda
&lt;/h4&gt;

&lt;p&gt;By default all environment variables are encrypted at rest, see &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html" rel="noopener noreferrer"&gt;here&lt;/a&gt; if you want to use your own encryption keys (CMK).&lt;/p&gt;

&lt;p&gt;If you want to encrypt the environment variables in transit, you can use a key managed by &lt;a href="https://aws.amazon.com/kms/" rel="noopener noreferrer"&gt;AWS KMS&lt;/a&gt; to do that. See &lt;a href="https://www.thedevcoach.co.uk/kms-aws-lambda/" rel="noopener noreferrer"&gt;this&lt;/a&gt; article explaining how to create the key, and how to manage encryption and decryption in AWS Lambda.&lt;/p&gt;
&lt;h4&gt;
  
  
  AWS WAF
&lt;/h4&gt;

&lt;p&gt;The Web application firewall support OSI Layer Level 7 filtering, meaning on the application level it can do input filtering. This is similar to the filtering shown previously but this is automated and it won't reach your API at all if the filter blocks the API request.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/traveloka/terraform-aws-waf-owasp-top-10-rules" rel="noopener noreferrer"&gt;This&lt;/a&gt; Github project implements all the OWASP Top 10 rules into AWS WAF for you if you wish. There's a downside with respect to the pricing model you need to be aware of. So here a trade off needs to be made between code level protection or an AWS service protection.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_wafregional_sql_injection_match_set"&lt;/span&gt; &lt;span class="s2"&gt;"owasp_01_sql_injection_set"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target_scope&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"regional"&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-owasp-01-detect-sql-injection-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;random_id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hex&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="nx"&gt;sql_injection_match_tuple&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;text_transformation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"URL_DECODE"&lt;/span&gt;

    &lt;span class="nx"&gt;field_to_match&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"URI"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;sql_injection_match_tuple&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;text_transformation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTML_ENTITY_DECODE"&lt;/span&gt;

    &lt;span class="nx"&gt;field_to_match&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"URI"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;sql_injection_match_tuple&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;text_transformation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"URL_DECODE"&lt;/span&gt;

    &lt;span class="nx"&gt;field_to_match&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"QUERY_STRING"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;sql_injection_match_tuple&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;text_transformation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTML_ENTITY_DECODE"&lt;/span&gt;

    &lt;span class="nx"&gt;field_to_match&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"QUERY_STRING"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;sql_injection_match_tuple&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;text_transformation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"URL_DECODE"&lt;/span&gt;

    &lt;span class="nx"&gt;field_to_match&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"BODY"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;sql_injection_match_tuple&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;text_transformation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTML_ENTITY_DECODE"&lt;/span&gt;

    &lt;span class="nx"&gt;field_to_match&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"BODY"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;sql_injection_match_tuple&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;text_transformation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"URL_DECODE"&lt;/span&gt;

    &lt;span class="nx"&gt;field_to_match&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HEADER"&lt;/span&gt;
      &lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Authorization"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;sql_injection_match_tuple&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;text_transformation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTML_ENTITY_DECODE"&lt;/span&gt;

    &lt;span class="nx"&gt;field_to_match&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HEADER"&lt;/span&gt;
      &lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Authorization"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_wafregional_rule"&lt;/span&gt; &lt;span class="s2"&gt;"owasp_01_sql_injection_rule"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"aws_wafregional_sql_injection_match_set.owasp_01_sql_injection_set"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target_scope&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"regional"&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="nx"&gt;name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-owasp-01-mitigate-sql-injection-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;random_id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hex&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;metric_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;OWASP01MitigateSQLInjection&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;random_id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hex&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="nx"&gt;predicate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;data_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;aws_wafregional_sql_injection_match_set&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owasp_01_sql_injection_set&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nx"&gt;negated&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"false"&lt;/span&gt;
    &lt;span class="nx"&gt;type&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SqlInjectionMatch"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This particular example consists of 8 rules within one Web ACL. The total &lt;a href="https://aws.amazon.com/waf/pricing/" rel="noopener noreferrer"&gt;cost&lt;/a&gt; of activating this rule is:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource type&lt;/th&gt;
&lt;th&gt;Number&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Web ACL&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;$5.00 (pro-rated hourly)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rule&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;$8.00 (pro-rated hourly)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request&lt;/td&gt;
&lt;td&gt;per 1 Million&lt;/td&gt;
&lt;td&gt;$0.60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$13.60 / month&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Based on 1 Million requests to your API, you'll pay $13.60 for protection against all types of SQL injection attacks on top of the regular API Gateway request costs.&lt;/p&gt;

&lt;p&gt;Here there's a definitive trade off between convenience, cost, traffic, and trusting development level security over DevOps rule based systems such as AWS WAF.&lt;/p&gt;
&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;For most applications I would say AWS WAF for SQL injection is not worth enabling over doing API Gateway input validation and application level input filtering.&lt;/p&gt;

&lt;p&gt;If you have real-world experience using AWS WAF in large scale applications, let me know in the comments.&lt;/p&gt;
&lt;h2&gt;
  
  
  (2) Broken Authentication
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Risks
&lt;/h3&gt;

&lt;p&gt;Here we want to avoid dictionary attacks on your authentication process, and/or broken or badly designed authentication mechanisms.&lt;/p&gt;
&lt;h3&gt;
  
  
  Solutions
&lt;/h3&gt;
&lt;h4&gt;
  
  
  AWS API Gateway &amp;amp; OpenAPI
&lt;/h4&gt;

&lt;p&gt;For our API's we want to make sure we enable authentication on our API's as much as possible, to enable this on our OpenAPI we add the &lt;code&gt;security&lt;/code&gt; parameter as follows:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;/user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;getUser&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;get User details by ID&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#/components/parameters/userID'&lt;/span&gt;
      &lt;span class="na"&gt;security&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;example-CognitoUserPoolAuthorizer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That name is a reference to the Cognito user pool authorizer configuration:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;securitySchemes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;example-CognitoUserPoolAuthorizer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apiKey"&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization"&lt;/span&gt;
      &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;header"&lt;/span&gt;
      &lt;span class="na"&gt;x-amazon-apigateway-authtype&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cognito_user_pools"&lt;/span&gt;
      &lt;span class="na"&gt;x-amazon-apigateway-authorizer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;providerARNs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${cognito_user_pool_arn}"&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cognito_user_pools"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Once enabled for an API, they need to have a registered user in the Cognito User Pool before they can be authenticated and receive a &lt;code&gt;JWT Token&lt;/code&gt; as proof they're authorized to execute the API.&lt;/p&gt;
&lt;h4&gt;
  
  
  AWS Cognito
&lt;/h4&gt;

&lt;p&gt;In Cognito we need to do several things to make sure we defend against the following potential risks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dictionary attacks / brute force attacks&lt;/li&gt;
&lt;li&gt;Password length and strength requirements.&lt;/li&gt;
&lt;li&gt;Multi-factor authentication&lt;/li&gt;
&lt;li&gt;Rotation of authorization session IDs.&lt;/li&gt;
&lt;li&gt;Invalidation of session IDs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First of all, we want to make sure password are at least 8 characters long as recommended by &lt;a href="https://pages.nist.gov/800-63-3/sp800-63b.html#memsecret" rel="noopener noreferrer"&gt;NIST&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Within the &lt;code&gt;aws_cognito_user_pool&lt;/code&gt; resource, we set the password policy as follows:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_cognito_user_pool"&lt;/span&gt; &lt;span class="s2"&gt;"_"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# ... other configuration ...&lt;/span&gt;

  &lt;span class="nx"&gt;password_policy&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;minimum_length&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
    &lt;span class="nx"&gt;require_uppercase&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="nx"&gt;require_lowercase&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="nx"&gt;require_numbers&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="nx"&gt;require_symbols&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="c1"&gt;# ... other configuration ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then to enable multi-factor authentication:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_cognito_user_pool"&lt;/span&gt; &lt;span class="s2"&gt;"_"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# ... other configuration ...&lt;/span&gt;

  &lt;span class="nx"&gt;mfa_configuration&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ON"&lt;/span&gt;
  &lt;span class="nx"&gt;sms_authentication_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Your code is {####}"&lt;/span&gt;

  &lt;span class="nx"&gt;sms_configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;external_id&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"example"&lt;/span&gt;
    &lt;span class="nx"&gt;sns_caller_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sns_caller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;software_token_mfa_configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="c1"&gt;# ... other configuration ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;When &lt;code&gt;mfa_configuration&lt;/code&gt; is set to &lt;code&gt;ON&lt;/code&gt; it is required for everyone to either have an SMS or Software MFA enabled.&lt;/p&gt;

&lt;p&gt;If you want, Cognito supports more &lt;a href="https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-settings-advanced-security.html" rel="noopener noreferrer"&gt;advanced security features&lt;/a&gt; for additional &lt;a href="https://aws.amazon.com/cognito/pricing/" rel="noopener noreferrer"&gt;cost&lt;/a&gt;. The example given for 100.000 users without advanced security is $250, with is $4525(!). Again here, is this worth your investment.&lt;/p&gt;

&lt;p&gt;The features provided are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-settings-compromised-credentials.html" rel="noopener noreferrer"&gt;Checks for compromised credentials&lt;/a&gt;, this will do username/password scans to see if it has been leaked anywhere.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-settings-adaptive-authentication.html" rel="noopener noreferrer"&gt;Adaptive authentication&lt;/a&gt;, determines risk level per authentication and can block the sign-in if determined to be suspicious.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-viewing-advanced-security-metrics.html" rel="noopener noreferrer"&gt;Publishing of security metrics&lt;/a&gt;, this will report all the sign in metrics to your CloudWatch logs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This will obviously require you integrate the &lt;a href="https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-viewing-advanced-security-app.html" rel="noopener noreferrer"&gt;Cognito SDK&lt;/a&gt; into your app or web application.&lt;/p&gt;
&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Properly implementing API Gateway security and Cognito Authentication with sensible password requirements with 2FA can already mitigate most issues indicated by OWASP. &lt;/p&gt;

&lt;p&gt;If you have stricter security requirements for your app, the additional advanced security features offered by Cognito can be worthwhile investigating.&lt;/p&gt;
&lt;h2&gt;
  
  
  (3) Sensitive data exposure
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Risks
&lt;/h3&gt;

&lt;p&gt;Here obvious risks are not encrypting data in transit or at rest, weak cryptography that can be reversed engineered, and man in the middle attacks.&lt;/p&gt;
&lt;h3&gt;
  
  
  Solutions
&lt;/h3&gt;

&lt;p&gt;All data on the AWS Services side is encrypted in transit using TLS/SSL. We're now mostly looking at how you can protect data at rest.&lt;/p&gt;
&lt;h4&gt;
  
  
  AWS DynamoDB
&lt;/h4&gt;

&lt;p&gt;For DynamoDB we want to make sure we're encrypting information as follows.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_dynamodb_table"&lt;/span&gt; &lt;span class="s2"&gt;"_"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

   &lt;span class="c1"&gt;# ... other configuration ...&lt;/span&gt;

  &lt;span class="nx"&gt;server_side_encryption&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="c1"&gt;# ... other configuration ...&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For more information on CMK or updating table encryption, see &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/encryption.tutorial.html" rel="noopener noreferrer"&gt;this&lt;/a&gt; official documentation. &lt;/p&gt;

&lt;p&gt;For further best practices regarding security on DynamoDB, see &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/best-practices-security-preventative.html" rel="noopener noreferrer"&gt;this&lt;/a&gt; official documentation.&lt;/p&gt;

&lt;p&gt;Since DynamoDB uses IAM role access it's not strictly necessary to use a VPC to restrict public access. If you want to be following best practices, using a VPC solution is recommended here and can ensure all traffic is done within the AWS network for enhanced security.&lt;/p&gt;
&lt;h4&gt;
  
  
  AWS RDS
&lt;/h4&gt;

&lt;p&gt;When creating a SQL based data store, we can use the following to enable encryption:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_db_instance"&lt;/span&gt; &lt;span class="s2"&gt;"_"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;# ... other configuration ...&lt;/span&gt;

  &lt;span class="nx"&gt;storage_encrypted&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;publicly_accessible&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

  &lt;span class="c1"&gt;# ... other configuration ...&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You would also be well advised not to allow public access to the database. A VPC should be configured to allow only private subnets access to your database to enhance security.&lt;/p&gt;

&lt;p&gt;To connect to RDS you can use it's public SSL certificate to ensure data transmissions are encrypted.&lt;/p&gt;

&lt;p&gt;See &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.html" rel="noopener noreferrer"&gt;this&lt;/a&gt; article for general security guidelines for AWS RDS.&lt;/p&gt;
&lt;h4&gt;
  
  
  AWS Lambda
&lt;/h4&gt;

&lt;p&gt;Before storing sensitive data such as credit card details, it's advised to use the proper encryption algorithms. These algorithms have a work factor delay that makes brute forcing very difficult. &lt;a href="https://www.cryptolux.org/index.php/Argon2" rel="noopener noreferrer"&gt;Argon2&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Scrypt" rel="noopener noreferrer"&gt;scrypt&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Bcrypt" rel="noopener noreferrer"&gt;bcrypt&lt;/a&gt;, or &lt;a href="https://en.wikipedia.org/wiki/PBKDF2" rel="noopener noreferrer"&gt;PBKDF2&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Depending on which Database you're using, the options are available to ensure in transit and at rest encryption. The next thing is to prevent public access as much as possible using a VPC setup.&lt;/p&gt;
&lt;h2&gt;
  
  
  (4) XML External Entities (XXE)
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Risks
&lt;/h3&gt;

&lt;p&gt;A lot of systems still use XML in one form or another, you can think about SOAP API services, and integration layer messaging systems. This risk is about automated XML processing with mostly outdated library dependencies.&lt;/p&gt;
&lt;h3&gt;
  
  
  Solutions
&lt;/h3&gt;
&lt;h4&gt;
  
  
  AWS Lambda
&lt;/h4&gt;

&lt;p&gt;Ensure your XML processing libraries are up to date and check the &lt;code&gt;Example Attack Scenarios&lt;/code&gt; &lt;a href="https://owasp.org/www-project-top-ten/OWASP_Top_Ten_2017/Top_10-2017_A4-XML_External_Entities_(XXE)" rel="noopener noreferrer"&gt;here&lt;/a&gt; to get an idea about the attack vectors.&lt;/p&gt;

&lt;p&gt;Use &lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html" rel="noopener noreferrer"&gt;this&lt;/a&gt; cheat sheet to find out exactly how to mitigate the risk for these programming languages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#cc" rel="noopener noreferrer"&gt;C/C++&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#java" rel="noopener noreferrer"&gt;Java&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#java-8-and-up" rel="noopener noreferrer"&gt;Java 8 and up&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#net" rel="noopener noreferrer"&gt;.NET&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#ios" rel="noopener noreferrer"&gt;iOS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#php" rel="noopener noreferrer"&gt;PHP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#python" rel="noopener noreferrer"&gt;Python&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;To be honest, I don't use XML at all. We have just one project where we need to ingest XML coming from a USA government agency and that particular format does not even conform XML standards. We had to build a custom parser to deal with it.&lt;/p&gt;
&lt;h2&gt;
  
  
  (5) Broken Access Control
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Risks
&lt;/h3&gt;

&lt;p&gt;Broken access control means attackers can get access to information that does not belong to the user session. For instance, by means of altering URL query parameters like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://example.com/app/accountInfo?acct=notmyacct
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Another potential risk is liberal CORS settings that allow API execution from different web domains.&lt;/p&gt;
&lt;h3&gt;
  
  
  Solutions
&lt;/h3&gt;
&lt;h4&gt;
  
  
  AWS API Gateway &amp;amp; OpenAPI
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;CORS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Managing the correct CORS settings for your API is the first step. For this we need to modify the &lt;code&gt;options&lt;/code&gt; operation on each of the API paths that has CORS enabled:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;200&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#/components/responses/cors'&lt;/span&gt;
    &lt;span class="na"&gt;400&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#/components/responses/cors'&lt;/span&gt;
    &lt;span class="na"&gt;500&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#/components/responses/cors'&lt;/span&gt;
  &lt;span class="na"&gt;x-amazon-apigateway-integration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;200"&lt;/span&gt;
        &lt;span class="na"&gt;responseParameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;method.response.header.Access-Control-Max-Age&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;'7200'"&lt;/span&gt;
          &lt;span class="na"&gt;method.response.header.Access-Control-Allow-Methods&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;'OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE'"&lt;/span&gt;
          &lt;span class="na"&gt;method.response.header.Access-Control-Allow-Headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"&lt;/span&gt;
          &lt;span class="na"&gt;method.response.header.Access-Control-Allow-Origin&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;'*'"&lt;/span&gt;
    &lt;span class="na"&gt;passthroughBehavior&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;when_no_match"&lt;/span&gt;
    &lt;span class="na"&gt;timeoutInMillis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;29000&lt;/span&gt;
    &lt;span class="na"&gt;requestTemplates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;200&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}"&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mock"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Crucial is the &lt;code&gt;method.response.header.Access-Control-Allow-Origin&lt;/code&gt; response parameter that is now set to &lt;code&gt;'*'&lt;/code&gt; to accept all origin requests. For development purposes this may be fine when you're dealing with various testing servers and local execution environments. For production we need to lock this down to the domain name executing this API only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API Limits&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next thing we want to make sure that we limit the number of parallel executions of the API.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_api_gateway_method_settings"&lt;/span&gt; &lt;span class="s2"&gt;"_"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;rest_api_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_api_gateway_rest_api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;stage_name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_api_gateway_stage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stage_name&lt;/span&gt;
  &lt;span class="nx"&gt;method_path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"*/*"&lt;/span&gt;

  &lt;span class="nx"&gt;settings&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;throttling_burst_limit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api_throttling_burst_limit&lt;/span&gt;
    &lt;span class="nx"&gt;throttling_rate_limit&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api_throttling_rate_limit&lt;/span&gt;
    &lt;span class="nx"&gt;metrics_enabled&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api_metrics_enabled&lt;/span&gt;
    &lt;span class="nx"&gt;logging_level&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api_logging_level&lt;/span&gt;
    &lt;span class="nx"&gt;data_trace_enabled&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api_data_trace_enabled&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;To control limits on your API stage, set the &lt;code&gt;throttling_burst_limit&lt;/code&gt; and &lt;code&gt;throttling_rate_limit&lt;/code&gt; parameters. They control the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;throttling_burst_limit&lt;/code&gt;: The number of requests per second the API Stage will sustain for a few seconds after which it will error with a 429 HTTP response.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;throttling_rate_limit&lt;/code&gt;: The number of requests per second for the API Stage, it will continue until it reaches the burst rate limit after which it will error with a 429 HTTP response.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In case of abuse, we need to ensure these have sensible limits such that DoS attacks are limited as much as possible.&lt;/p&gt;
&lt;h4&gt;
  
  
  AWS DynamoDB, Cognito &amp;amp; Lambda
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Fine grained access control&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We can implement fine grained access control mechanisms on DynamoDB that will allow only the authenticated user access to its own records. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/specifying-conditions.html" rel="noopener noreferrer"&gt;This&lt;/a&gt; article goes into detail how to do this, the following example is given:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"dynamodb:GetItem"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"dynamodb:BatchGetItem"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"dynamodb:Query"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:dynamodb:us-west-2:123456789012:dynamodb:table/GameScores"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ForAllValues:StringEquals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"dynamodb:LeadingKeys"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"${www.amazon.com:user_id}"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"dynamodb:Attributes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"UserId"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"GameTitle"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"Wins"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"StringEqualsIfExists"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"dynamodb:Select"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SPECIFIC_ATTRIBUTES"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The above policy will give read only access to items in the &lt;code&gt;GameScores&lt;/code&gt; table that belong to &lt;code&gt;www.amazon.com:user_id&lt;/code&gt; and it will only be able to retrieve these columns for each row;  &lt;code&gt;UserId&lt;/code&gt;, &lt;code&gt;GameTitle&lt;/code&gt;, and &lt;code&gt;Wins&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To note is that &lt;code&gt;www.amazon.com:user_id&lt;/code&gt; refers to identities from an Amazon Federated login and not to the regular Cognito User Pool. To use the User Pool registered identities, use &lt;code&gt;cognito-identity.amazonaws.com:sub&lt;/code&gt; instead.&lt;/p&gt;

&lt;p&gt;To be able to use this policy in your AWS Lambda code you have to retrieve the temporary credentials with the following function, see the &lt;a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/STS.html#assumeRoleWithWebIdentity-property" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; for more details:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;RoleArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;STRING_VALUE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* required */&lt;/span&gt;
  &lt;span class="na"&gt;RoleSessionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;STRING_VALUE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* required */&lt;/span&gt;
  &lt;span class="na"&gt;WebIdentityToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;STRING_VALUE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* required */&lt;/span&gt;
  &lt;span class="na"&gt;DurationSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NUMBER_VALUE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;Policy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;STRING_VALUE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;PolicyArns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;arn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;STRING_VALUE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="cm"&gt;/* more items */&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;ProviderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;STRING_VALUE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nx"&gt;sts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assumeRoleWithWebIdentity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// an error occurred&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;     &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;           &lt;span class="c1"&gt;// successful response&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;When this executes successfully you have temporary credentials that allow you to read from DynamoDB as indicated by the policy earlier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Roles &amp;amp; Role access&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To solve the roles and role access problems for your API, you can use the following setup in Cognito:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"cognito"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"../modules/cognito"&lt;/span&gt;

  &lt;span class="nx"&gt;namespace&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;namespace&lt;/span&gt;
  &lt;span class="nx"&gt;resource_tag_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_tag_name&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;

  &lt;span class="nx"&gt;cognito_identity_pool_name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cognito_identity_pool_name&lt;/span&gt;
  &lt;span class="nx"&gt;cognito_identity_pool_provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cognito_identity_pool_provider&lt;/span&gt;

  &lt;span class="nx"&gt;schema_map&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"email"&lt;/span&gt;
      &lt;span class="nx"&gt;attribute_data_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"String"&lt;/span&gt;
      &lt;span class="nx"&gt;mutable&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="nx"&gt;required&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"phone_number"&lt;/span&gt;
      &lt;span class="nx"&gt;attribute_data_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"String"&lt;/span&gt;
      &lt;span class="nx"&gt;mutable&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="nx"&gt;required&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"businessID"&lt;/span&gt;
      &lt;span class="nx"&gt;attribute_data_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"String"&lt;/span&gt;
      &lt;span class="nx"&gt;mutable&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="nx"&gt;required&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"role"&lt;/span&gt;
      &lt;span class="nx"&gt;attribute_data_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"String"&lt;/span&gt;
      &lt;span class="nx"&gt;mutable&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="nx"&gt;required&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"roleAccess"&lt;/span&gt;
      &lt;span class="nx"&gt;attribute_data_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"String"&lt;/span&gt;
      &lt;span class="nx"&gt;mutable&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="nx"&gt;required&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;schema_map&lt;/code&gt; contains all the additional parameters that need to be recorded (either manual or automated after registration) for a registration and they're retrievable during an &lt;code&gt;authenticated&lt;/code&gt; session in AWS Lambda.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;event&lt;/code&gt; object in an AWS Lambda session contains all the information from the currently authenticated user. To retrieve, access the &lt;code&gt;claims&lt;/code&gt; object: &lt;code&gt;event.requestContext.authorizer.claims&lt;/code&gt; which will contain the custom parameters set in AWS Cognito like so:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;'custom:role':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'USER'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;'custom:roleAccess':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"business"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rwu"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"r"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rwud"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;'custom:businessID':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;When a user authenticates against Cognito, now AWS Lambda knows which role the user has, and what kind of role access that entails. Each of those properties, e.g. &lt;code&gt;business&lt;/code&gt;, will enable you to allow access to &lt;code&gt;GET&lt;/code&gt; (r), &lt;code&gt;POST&lt;/code&gt; (w), &lt;code&gt;PUT&lt;/code&gt; (u), &lt;code&gt;DELETE&lt;/code&gt; (d) operations on that specific business API. You can make this as fine grained as you need it to.&lt;/p&gt;

&lt;p&gt;For key identifiers such as a &lt;code&gt;businessID&lt;/code&gt; it's recommended to make those part of the DynamoDB partition key such that they're mandatory when querying for data and never supplied via API query parameters or body JSON objects.&lt;/p&gt;
&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Broken access controls can create a lot of problems both for data security as well as (D)DoS attack opportunities via loose CORS and Rate limit settings on your API.&lt;/p&gt;
&lt;h2&gt;
  
  
  (6) Security Misconfiguration
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Risks
&lt;/h3&gt;

&lt;p&gt;This is a very broad set of threats that go across the application stack. From unpatched software, to default accounts.&lt;br&gt;
A typical example here is an S3 bucket that has public permissions, this used to be prevalent as &lt;a href="https://www.infoworld.com/article/2613888/amazon-s3-storage-buckets-set-to--public--are-ripe-for-data-plundering.html" rel="noopener noreferrer"&gt;this&lt;/a&gt; article shows.&lt;/p&gt;
&lt;h3&gt;
  
  
  Solutions
&lt;/h3&gt;
&lt;h4&gt;
  
  
  AWS Code Build
&lt;/h4&gt;

&lt;p&gt;Since this stack is running fully Serverless and thus is a managed service by Amazon, the area's that are most exposed to potential security leaks is the code we use on AWS Lambda and Lambda Layer.&lt;/p&gt;

&lt;p&gt;In case of Node, we need to ensure we have upgraded to the latest version supported on AWS and that is version 12. Within your build system we have many opportunities to test for vulnerabilities in dependencies and code such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NPM Audit:&lt;/strong&gt; To audit your dependencies, see &lt;a href="https://docs.npmjs.com/auditing-package-dependencies-for-security-vulnerabilities" rel="noopener noreferrer"&gt;this&lt;/a&gt; article for details, we can implement the &lt;code&gt;npm audit&lt;/code&gt; step in CodeBuild and halt the pipeline once vulnerabilities have been found.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OWASP Dependency checker:&lt;/strong&gt; OWASP has a dependency checker that can be integrated on the command line as well, see &lt;a href="https://owasp.org/www-project-dependency-check/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. This particular analyzer has support for several languages including NodeJS and NPM Package manager, see &lt;a href="https://jeremylong.github.io/DependencyCheck/analyzers/nodejs.html" rel="noopener noreferrer"&gt;here&lt;/a&gt; for the details.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NodeJsScan:&lt;/strong&gt; NodeJsScan is a docker ready general purpose security scanner specifically for NodeJS, &lt;a href="https://github.com/ajinabraham/NodeJsScan" rel="noopener noreferrer"&gt;here's&lt;/a&gt; the Github for more details. This scans your entire codebase for vulnerabilities such as XSS, Remote Code Injection, and SQL Injection among others.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RetireJs:&lt;/strong&gt; is a well known scanner for node dependencies and javascript code vulnerabilities. See their &lt;a href="https://retirejs.github.io/retire.js/" rel="noopener noreferrer"&gt;Github page&lt;/a&gt; for details.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  AWS Lambda
&lt;/h4&gt;

&lt;p&gt;For AWS Lambda we have a great middleware to help cover correct security configurations regarding HTTP REST API's, it's called &lt;a href="https://middy.js.org/" rel="noopener noreferrer"&gt;Middy&lt;/a&gt;. These are some of the recommended plugins:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/middyjs/middy/tree/1.0.0-beta/packages/http-cors" rel="noopener noreferrer"&gt;HTTP-CORS&lt;/a&gt; helps to set the correct HTTP CORS headers.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/middyjs/middy/tree/1.0.0-beta/packages/http-error-handler" rel="noopener noreferrer"&gt;HTTP-Error-handler&lt;/a&gt; returns correct HTTP responses, this works in conjunction with &lt;a href="https://www.npmjs.com/package/http-errors" rel="noopener noreferrer"&gt;http-error&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/middyjs/middy/tree/1.0.0-beta/packages/http-security-headers" rel="noopener noreferrer"&gt;HTTP-Security-headers&lt;/a&gt; this applies best practice security headers to HTTP responses.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Since AWS takes care of the details around every service we use, we still have a responsibility in the area's we introduce possible security leaks. Of course we can misconfigure an S3 bucket for public access, and there are many other examples that I could cover. However, I focussed on the key area of source code instead.&lt;/p&gt;

&lt;p&gt;If you're interested in a much more broad and in-depth look, please review the guidelines on CIS for additional solutions &lt;a href="https://www.cisecurity.org/cis-benchmarks/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  (7) Cross-Site Scripting XSS
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Risks
&lt;/h3&gt;

&lt;p&gt;XSS flaws occur when web applications include user-provided data in webpages that is sent to the browser without proper sanitization. If the data isn’t properly validated or escaped, an attacker can embed scripts, inline frames, or other objects into the rendered page.&lt;/p&gt;

&lt;p&gt;XSS is a very well known security risk, there are several variants &lt;a href="https://owasp.org/www-project-top-ten/OWASP_Top_Ten_2017/Top_10-2017_A7-Cross-Site_Scripting_(XSS)" rel="noopener noreferrer"&gt;OWASP&lt;/a&gt; recognizes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reflected XSS:&lt;/strong&gt; this is typically about URL interaction/scripts that have malicious intent coming from user data that is not sanitized. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stored XSS:&lt;/strong&gt; storing of unsanitized user input that is viewed by someone else and in the worst case by someone with admin privileges.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DOM XSS:&lt;/strong&gt; most common with javascript frameworks that manipulate the DOM such as ReactJS and VueJS. An example here is a DOM node replacement with a malicious login screen.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Solutions
&lt;/h3&gt;
&lt;h4&gt;
  
  
  AWS API Gateway &amp;amp; Lambda
&lt;/h4&gt;

&lt;p&gt;This risk is about input sanitation, we have discussed this in previous chapters, please see the chapter about Injection for my recommendations on how to deal with this.&lt;/p&gt;
&lt;h4&gt;
  
  
  AWS WAF
&lt;/h4&gt;

&lt;p&gt;Additionally, if you'd like specific Web Application Firewall rules to deal with XSS risks, the following Terraform code can be used (taken from &lt;a href="https://github.com/traveloka/terraform-aws-waf-owasp-top-10-rules" rel="noopener noreferrer"&gt;this&lt;/a&gt; Github repo):&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_wafregional_xss_match_set"&lt;/span&gt; &lt;span class="s2"&gt;"owasp_03_xss_set"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target_scope&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"regional"&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-owasp-03-detect-xss-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;random_id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hex&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="nx"&gt;xss_match_tuple&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;text_transformation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"URL_DECODE"&lt;/span&gt;

    &lt;span class="nx"&gt;field_to_match&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"URI"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;xss_match_tuple&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;text_transformation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTML_ENTITY_DECODE"&lt;/span&gt;

    &lt;span class="nx"&gt;field_to_match&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"URI"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;xss_match_tuple&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;text_transformation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"URL_DECODE"&lt;/span&gt;

    &lt;span class="nx"&gt;field_to_match&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"QUERY_STRING"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;xss_match_tuple&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;text_transformation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTML_ENTITY_DECODE"&lt;/span&gt;

    &lt;span class="nx"&gt;field_to_match&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"QUERY_STRING"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;xss_match_tuple&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;text_transformation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"URL_DECODE"&lt;/span&gt;

    &lt;span class="nx"&gt;field_to_match&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"BODY"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;xss_match_tuple&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;text_transformation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTML_ENTITY_DECODE"&lt;/span&gt;

    &lt;span class="nx"&gt;field_to_match&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"BODY"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;xss_match_tuple&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;text_transformation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"URL_DECODE"&lt;/span&gt;

    &lt;span class="nx"&gt;field_to_match&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HEADER"&lt;/span&gt;
      &lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"cookie"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;xss_match_tuple&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;text_transformation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTML_ENTITY_DECODE"&lt;/span&gt;

    &lt;span class="nx"&gt;field_to_match&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HEADER"&lt;/span&gt;
      &lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"cookie"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_wafregional_rule"&lt;/span&gt; &lt;span class="s2"&gt;"owasp_03_xss_rule"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"aws_wafregional_xss_match_set.owasp_03_xss_set"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target_scope&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"regional"&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="nx"&gt;name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-owasp-03-mitigate-xss-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;random_id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hex&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;metric_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;OWASP03MitigateXSS&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;random_id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hex&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="nx"&gt;predicate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;data_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;aws_wafregional_xss_match_set&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owasp_03_xss_set&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nx"&gt;negated&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"false"&lt;/span&gt;
    &lt;span class="nx"&gt;type&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"XssMatch"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Again here, the cost to have AWS take care of this for you is as follows. These XSS protections contain 8 rules within one Web ACL. The total &lt;a href="https://aws.amazon.com/waf/pricing/" rel="noopener noreferrer"&gt;cost&lt;/a&gt; of activating this rule is:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource type&lt;/th&gt;
&lt;th&gt;Number&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Web ACL&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;$5.00 (pro-rated hourly)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rule&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;$8.00 (pro-rated hourly)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request&lt;/td&gt;
&lt;td&gt;per 1 Million&lt;/td&gt;
&lt;td&gt;$0.60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$13.60 / month&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Based on 1 Million requests to your API, you'll pay $13.60 for protection against all types of XSS attacks on top of the regular API Gateway request costs. You can of course activate this together with the SQL injection rules within the same Web ACL to save $5.00 per month for a total cost of $22.20 / month.&lt;/p&gt;
&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;When you apply good input sanitation that should sufficiently protect you from any risk of XSS. &lt;br&gt;
Please check your current implementation againt &lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html" rel="noopener noreferrer"&gt;this&lt;/a&gt; cheat sheet from OWASP to ensure you're fully protected.&lt;/p&gt;
&lt;h2&gt;
  
  
  (8) Insecure Deserialization
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Risks
&lt;/h3&gt;

&lt;p&gt;Flaws in how deserialization is done can result in remote code execution. For example, a JSON object send by a web application via an AWS API Gateway is deserialized (JSON to Javascript Object conversion) and actions are performed on this data. &lt;/p&gt;

&lt;p&gt;Examples of potential risks are; session cookie tampering, and session state manipulation.&lt;/p&gt;
&lt;h3&gt;
  
  
  Solutions
&lt;/h3&gt;
&lt;h4&gt;
  
  
  AWS Lambda
&lt;/h4&gt;

&lt;p&gt;This requires manual code review and the use of code vulnerability scanners as introduced in the Security Misconfiguration chapter. The solution as recommended by &lt;a href="https://owasp.org/www-project-top-ten/OWASP_Top_Ten_2017/Top_10-2017_A8-Insecure_Deserialization" rel="noopener noreferrer"&gt;OWASP&lt;/a&gt; is to validate state and cookies using integrity checks with digital signatures such as a hash or an authentication signature. If the data does not contain such signatures, it will be ignored.&lt;br&gt;
Furthermore, logging of any deserialization errors is very useful to detect any possible tampering.&lt;/p&gt;
&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Please view &lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html" rel="noopener noreferrer"&gt;this&lt;/a&gt; OWASP cheat sheet for details per programming language what to look out for.&lt;/p&gt;
&lt;h2&gt;
  
  
  (9) Using Components with Known Vulnerabilities
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Risks
&lt;/h3&gt;

&lt;p&gt;Any code or component that is used as a dependency can have known vulnerabilities. Because there's such a massive reliance on open source dependencies in most projects it's very hard to be aware of security risks.&lt;/p&gt;
&lt;h3&gt;
  
  
  Solutions
&lt;/h3&gt;

&lt;p&gt;Here we need to rely on Build phase dependency scanning and manual review to avoid security risks as much as possible. Please review the Solutions in the chapter Security Misconfiguration to mitigate this security risk.&lt;/p&gt;
&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;The fact that several OWASP Top 10 risks share similarities means that many software services and products rely on open source software. With that reliance come security vulnerabilities that are hard to detect without some form of automated detection. Developers and DevOps need to put a lot more attention on threat detection in the build phase before code is put into production.&lt;/p&gt;
&lt;h2&gt;
  
  
  (10) Insufficient Logging &amp;amp; Monitoring
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Risks
&lt;/h3&gt;

&lt;p&gt;This risks is about a lack of contextual logging (where, what, and how) which decreases awareness, and in time monitoring of threats. The latter is about how quickly an IT team can respond to security incidents.&lt;/p&gt;
&lt;h3&gt;
  
  
  Solutions
&lt;/h3&gt;
&lt;h4&gt;
  
  
  AWS CloudWatch
&lt;/h4&gt;

&lt;p&gt;CloudWatch can provide both Logging, and Monitoring with Alerts. As I've demonstrated in an earlier article in this &lt;em&gt;OpenAPI&lt;/em&gt; series of articles. Review that article for logging and Monitoring suggestions that will increase visibility and make debugging and error (threat) detection easier.&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/rolfstreefkerk" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F311542%2F1d2e6fa7-cab5-4277-90fb-7e299f32e6f9.jpeg" alt="rolfstreefkerk"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/rolfstreefkerk/how-to-do-logging-on-aws-serverless-3pi3" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;How to do logging on AWS Serverless&lt;/h2&gt;
      &lt;h3&gt;Rolf Streefkerk ・ Feb 9 '20&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#aws&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#serverless&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#devops&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#terraform&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;



&lt;h4&gt;
  
  
  AWS X-Ray
&lt;/h4&gt;

&lt;p&gt;X-Ray is as the name suggest a service that provides much more transparency into Serverless execution by both visualizing the execution path, and making logs easily accessible. I've covered the use and implementation of it in an earlier article, please review that article below.&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/rolfstreefkerk" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F311542%2F1d2e6fa7-cab5-4277-90fb-7e299f32e6f9.jpeg" alt="rolfstreefkerk"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/rolfstreefkerk/serverless-tracing-with-aws-x-ray-3119" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Serverless tracing with AWS X-Ray&lt;/h2&gt;
      &lt;h3&gt;Rolf Streefkerk ・ Feb 16 '20&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#aws&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#devops&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#serverless&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#terraform&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;With these two tools in hand most anomalies can be relatively quickly detected either though monitoring alerts from CloudWatch via SNS, or through bug/error analysis with AWS X-Ray.&lt;/p&gt;

&lt;h1&gt;
  
  
  In closing
&lt;/h1&gt;

&lt;p&gt;There was a lot to cover here and I'm sure I missed equally as much as well. Researching this has actually uncovered some things I haven't implemented myself properly yet, that's what makes creating these articles so valuable for me. I hope this has been useful reading, and I very much appreciate your time and attention.&lt;/p&gt;

&lt;p&gt;If I've made any mistakes or you have suggestions, additions please do let me know in the comments. I'm very much interested reading your thoughts and expertise on this difficult subject.&lt;/p&gt;

&lt;p&gt;For my next article I'd like to discuss DynamoDB in some detail, and why you should be using it.&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

&lt;h1&gt;
  
  
  Further reading
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://d0.awsstatic.com/whitepapers/Security/aws-waf-owasp.pdf" rel="noopener noreferrer"&gt;AWS WAF OWASP Security Top 10 whitepaper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://d0.awsstatic.com/whitepapers/Security/DDoS_White_Paper.pdf" rel="noopener noreferrer"&gt;AWS Best practices DDOS resiliency&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://d0.awsstatic.com/whitepapers/Security/AWS_Security_Whitepaper.pdf" rel="noopener noreferrer"&gt;Amazon Web Services: Overview of Security Processes Whitepaper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/mobile/understanding-amazon-cognito-authentication-part-3-roles-and-policies/" rel="noopener noreferrer"&gt;Understanding AWS Cognito Roles and Policies&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>security</category>
      <category>devops</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to setup a basic VPC with EC2 and RDS using Terraform</title>
      <dc:creator>Rolf Streefkerk</dc:creator>
      <pubDate>Sun, 08 Mar 2020 10:34:50 +0000</pubDate>
      <link>https://forem.com/rolfstreefkerk/how-to-setup-a-basic-vpc-with-ec2-and-rds-using-terraform-3jij</link>
      <guid>https://forem.com/rolfstreefkerk/how-to-setup-a-basic-vpc-with-ec2-and-rds-using-terraform-3jij</guid>
      <description>&lt;p&gt;This guide will help you set up a basic AWS VPC with a virtual machine (EC2) and database (RDS) using Terraform (Infrastructure as Code).&lt;/p&gt;

&lt;p&gt;I'll be breaking this topic down as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The outline&lt;/li&gt;
&lt;li&gt;VPC&lt;/li&gt;
&lt;li&gt;
EC2

&lt;ul&gt;
&lt;li&gt;EC2 Security group&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

RDS

&lt;ul&gt;
&lt;li&gt;RDS Security group&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Conclusion&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  The outline
&lt;/h1&gt;

&lt;p&gt;We're going to create the following on AWS:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgc4m221kunlarpjlzoen.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgc4m221kunlarpjlzoen.png" alt="vpc-diagram" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;VPC&lt;/code&gt; with 1 &lt;code&gt;Route table&lt;/code&gt; that connects the &lt;code&gt;Internet Gateway&lt;/code&gt; to the &lt;code&gt;public subnet&lt;/code&gt; that hosts the &lt;code&gt;EC2 instance&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two &lt;code&gt;private subnets&lt;/code&gt; configured as 1 subnet group that hosts 1 &lt;code&gt;RDS instance&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Access control is arranged using &lt;code&gt;security groups&lt;/code&gt;, one for the EC2 public subnet and 1 for the RDS private subnets.&lt;/p&gt;

&lt;p&gt;The reason we have 2 subnets for RDS is because that is a deployment requirement, you cannot launch an RDS instance without configuring it with 2 subnets.&lt;/p&gt;

&lt;p&gt;Ideally, you would want to do &lt;code&gt;load balancing&lt;/code&gt; for both EC2 and RDS instances. On the EC2 side you would have to add another subnet for the other EC2 instance and connect them with a load balancer. In case one of the subnets goes down for whatever reason, your site is still up and running.&lt;/p&gt;

&lt;p&gt;For this article however, we're going to focus on the minimum setup for development and testing purposes.&lt;/p&gt;

&lt;h1&gt;
  
  
  VPC
&lt;/h1&gt;

&lt;p&gt;To create a VPC we configure our module as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc"&lt;/span&gt; &lt;span class="s2"&gt;"_"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_cidr&lt;/span&gt;

  &lt;span class="nx"&gt;enable_dns_support&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enable_dns_support&lt;/span&gt;
  &lt;span class="nx"&gt;enable_dns_hostnames&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enable_dns_hostnames&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_internet_gateway"&lt;/span&gt; &lt;span class="s2"&gt;"_"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route_table"&lt;/span&gt; &lt;span class="s2"&gt;"_"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;dynamic&lt;/span&gt; &lt;span class="s2"&gt;"route"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;

    &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;cidr_block&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cidr_block&lt;/span&gt;
      &lt;span class="nx"&gt;gateway_id&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gateway_id&lt;/span&gt;
      &lt;span class="nx"&gt;instance_id&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance_id&lt;/span&gt;
      &lt;span class="nx"&gt;nat_gateway_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nat_gateway_id&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route_table_association"&lt;/span&gt; &lt;span class="s2"&gt;"_"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subnet_ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;subnet_id&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subnet_ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;route_table_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_route_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Please note&lt;/strong&gt;; I removed the tag blocks for brevity, but you should tag every resource possible to enable easy cost tracking of your deployments and to be able to find everything should anything go wrong with the &lt;code&gt;tfstate&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The route table is configured to be &lt;em&gt;associated&lt;/em&gt; with an internet gateway in the &lt;code&gt;aws_route_table_association&lt;/code&gt; resource. Any subnet we supply in &lt;code&gt;var.subnet_ids&lt;/code&gt; will have access to the route table configuration and the internet gateway.&lt;/p&gt;

&lt;p&gt;I call the VPC module like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"vpc"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"../../modules/vpc"&lt;/span&gt;

  &lt;span class="nx"&gt;resource_tag_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_tag_name&lt;/span&gt;
  &lt;span class="nx"&gt;namespace&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;namespace&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;

  &lt;span class="nx"&gt;vpc_cidr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.0.0/16"&lt;/span&gt;

  &lt;span class="nx"&gt;route&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;cidr_block&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;
      &lt;span class="nx"&gt;gateway_id&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gateway_id&lt;/span&gt;
      &lt;span class="nx"&gt;instance_id&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
      &lt;span class="nx"&gt;nat_gateway_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;subnet_ids&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subnet_ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ids&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;vpc_cidr = "10.0.0.0/16"&lt;/code&gt; means we're creating a VPC with 65,536 possible IP addresses. See &lt;a href="https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing" rel="noopener noreferrer"&gt;here&lt;/a&gt; for an explanation on the CIDR notation.&lt;/p&gt;

&lt;p&gt;The route table is connected to the EC2 subnet via; &lt;code&gt;subnet_ids = module.subnet_ec2.ids&lt;/code&gt;. This subnet has full access to the internet via the &lt;code&gt;cidr_block&lt;/code&gt; configuration; &lt;code&gt;"0.0.0.0/0"&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  EC2
&lt;/h1&gt;

&lt;p&gt;To create the EC2 instance, we just need to configure what machine we want and place it in the subnet where our Route Table is present.&lt;/p&gt;

&lt;p&gt;In our &lt;code&gt;EC2 module&lt;/code&gt; we configure the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;resource_name_prefix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;namespace&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_tag_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"_"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ami&lt;/span&gt;                         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ami&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance_type&lt;/span&gt;
  &lt;span class="nx"&gt;user_data&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user_data&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_id&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subnet_id&lt;/span&gt;
  &lt;span class="nx"&gt;associate_public_ip_address&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;associate_public_ip_address&lt;/span&gt;
  &lt;span class="nx"&gt;key_name&lt;/span&gt;                    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_key_pair&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key_name&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_security_group_ids&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_security_group_ids&lt;/span&gt;

  &lt;span class="nx"&gt;iam_instance_profile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iam_instance_profile&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_eip"&lt;/span&gt; &lt;span class="s2"&gt;"_"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"tls_private_key"&lt;/span&gt; &lt;span class="s2"&gt;"_"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;algorithm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"RSA"&lt;/span&gt;
  &lt;span class="nx"&gt;rsa_bits&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_key_pair"&lt;/span&gt; &lt;span class="s2"&gt;"_"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;key_name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key_name&lt;/span&gt;
  &lt;span class="nx"&gt;public_key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tls_private_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_key_openssh&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates:&lt;/p&gt;

&lt;p&gt;1) AWS EC2 instance&lt;br&gt;
2) With an elastic IP associated with that instance&lt;br&gt;
3) A public/private key (PEM key) to access the instance via SSH.&lt;/p&gt;

&lt;p&gt;Then we can call it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"ec2"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"../../modules/ec2"&lt;/span&gt;

  &lt;span class="nx"&gt;resource_tag_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_tag_name&lt;/span&gt;
  &lt;span class="nx"&gt;namespace&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;namespace&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;

  &lt;span class="nx"&gt;ami&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ami-07ebfd5b3428b6f4d"&lt;/span&gt; &lt;span class="c1"&gt;# Ubuntu Server 18.04 LTS&lt;/span&gt;
  &lt;span class="nx"&gt;key_name&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_name_prefix&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-ec2-key"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance_type&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_id&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subnet_ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;vpc_security_group_ids&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four main things we need to supply the EC2 module (among other things):&lt;/p&gt;

&lt;p&gt;1) Attach the EC2 instance to the subnet; &lt;code&gt;subnet_id  = module.subnet_ec2.ids[0]&lt;/code&gt;,&lt;br&gt;
2) attaches the security group; &lt;code&gt;vpc_security_group_ids = [aws_security_group.ec2.id]&lt;/code&gt;, a security group acts like a firewall.&lt;br&gt;
3) Supply it with the VPC that it needs to be deployed in; &lt;code&gt;vpc_id = module.vpc.id&lt;/code&gt;&lt;br&gt;
4) AMI identifier, &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/finding-an-ami.html" rel="noopener noreferrer"&gt;here's&lt;/a&gt; more on how to find Amazon Machine Image (AMI) identifiers.&lt;/p&gt;
&lt;h2&gt;
  
  
  EC2 Security group
&lt;/h2&gt;

&lt;p&gt;So far we have a VPC setup, an EC2 instance and its subnet, and we've configured a reference to the security group the EC2 subnet is using.&lt;/p&gt;

&lt;p&gt;A security group acts like a firewall for your subnet, what is allowed to go in &lt;code&gt;ingress&lt;/code&gt; and what is allowed to go out &lt;code&gt;egress&lt;/code&gt; of your subnet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_security_group"&lt;/span&gt; &lt;span class="s2"&gt;"ec2"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_name_prefix&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-ec2-sg"&lt;/span&gt;

  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"EC2 security group (terraform-managed)"&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;ingress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rds_port&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rds_port&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"MySQL"&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rds_cidr_blocks&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;ingress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Telnet"&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;ingress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTTP"&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;ingress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTTPS"&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Allow all outbound traffic.&lt;/span&gt;
  &lt;span class="nx"&gt;egress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"-1"&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We allow traffic to come in from ports; &lt;strong&gt;22&lt;/strong&gt; (SSH), &lt;strong&gt;80&lt;/strong&gt; (HTTP), &lt;strong&gt;443&lt;/strong&gt; (HTTPS), and we allow ALL traffic on all ports to go out. If you want to further tighten this down, profile which ports your application uses for outbound traffic to increase security.&lt;/p&gt;

&lt;p&gt;For ingress you can further tighten this down by supplying a specific IP address that is allowed to connect on port 22.&lt;/p&gt;

&lt;h1&gt;
  
  
  RDS
&lt;/h1&gt;

&lt;p&gt;We're almost done with the setup, only our database subnet and instance with security group needs to be configured.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;resource_name_prefix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;namespace&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_tag_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_db_subnet_group"&lt;/span&gt; &lt;span class="s2"&gt;"_"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_name_prefix&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;identifier&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-subnet-group"&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_ids&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subnet_ids&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_db_instance"&lt;/span&gt; &lt;span class="s2"&gt;"_"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;identifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_name_prefix&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;identifier&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="nx"&gt;allocated_storage&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allocated_storage&lt;/span&gt;
  &lt;span class="nx"&gt;backup_retention_period&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;backup_retention_period&lt;/span&gt;
  &lt;span class="nx"&gt;backup_window&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;backup_window&lt;/span&gt;
  &lt;span class="nx"&gt;maintenance_window&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maintenance_window&lt;/span&gt;
  &lt;span class="nx"&gt;db_subnet_group_name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_db_subnet_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;engine&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;engine&lt;/span&gt;
  &lt;span class="nx"&gt;engine_version&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;engine_version&lt;/span&gt;
  &lt;span class="nx"&gt;instance_class&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance_class&lt;/span&gt;
  &lt;span class="nx"&gt;multi_az&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;multi_az&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;username&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;
  &lt;span class="nx"&gt;password&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;
  &lt;span class="nx"&gt;port&lt;/span&gt;                    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;
  &lt;span class="nx"&gt;publicly_accessible&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publicly_accessible&lt;/span&gt;
  &lt;span class="nx"&gt;storage_encrypted&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage_encrypted&lt;/span&gt;
  &lt;span class="nx"&gt;storage_type&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage_type&lt;/span&gt;

  &lt;span class="nx"&gt;vpc_security_group_ids&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;allow_major_version_upgrade&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allow_major_version_upgrade&lt;/span&gt;
  &lt;span class="nx"&gt;auto_minor_version_upgrade&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auto_minor_version_upgrade&lt;/span&gt;

  &lt;span class="nx"&gt;final_snapshot_identifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;final_snapshot_identifier&lt;/span&gt;
  &lt;span class="nx"&gt;snapshot_identifier&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;snapshot_identifier&lt;/span&gt;
  &lt;span class="nx"&gt;skip_final_snapshot&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;skip_final_snapshot&lt;/span&gt;

  &lt;span class="nx"&gt;performance_insights_enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;performance_insights_enabled&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a lot of options here, lets grab the &lt;code&gt;tfvars&lt;/code&gt; file to see what most of these variables contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# RDS&lt;/span&gt;
&lt;span class="nx"&gt;rds_identifier&lt;/span&gt;        &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"mysql"&lt;/span&gt;
&lt;span class="nx"&gt;rds_engine&lt;/span&gt;            &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"mysql"&lt;/span&gt;
&lt;span class="nx"&gt;rds_engine_version&lt;/span&gt;    &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"8.0.15"&lt;/span&gt;
&lt;span class="nx"&gt;rds_instance_class&lt;/span&gt;    &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"db.t2.micro"&lt;/span&gt;
&lt;span class="nx"&gt;rds_allocated_storage&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="nx"&gt;rds_storage_encrypted&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;     &lt;span class="c1"&gt;# not supported for db.t2.micro instance&lt;/span&gt;
&lt;span class="nx"&gt;rds_name&lt;/span&gt;              &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;        &lt;span class="c1"&gt;# use empty string to start without a database created&lt;/span&gt;
&lt;span class="nx"&gt;rds_username&lt;/span&gt;          &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"admin"&lt;/span&gt;   &lt;span class="c1"&gt;# rds_password is generated&lt;/span&gt;

&lt;span class="nx"&gt;rds_port&lt;/span&gt;                    &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3306&lt;/span&gt;
&lt;span class="nx"&gt;rds_maintenance_window&lt;/span&gt;      &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Mon:00:00-Mon:03:00"&lt;/span&gt;
&lt;span class="nx"&gt;rds_backup_window&lt;/span&gt;           &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10:46-11:16"&lt;/span&gt;
&lt;span class="nx"&gt;rds_backup_retention_period&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="nx"&gt;rds_publicly_accessible&lt;/span&gt;     &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

&lt;span class="nx"&gt;rds_final_snapshot_identifier&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"prod-trademerch-website-db-snapshot"&lt;/span&gt; &lt;span class="c1"&gt;# name of the final snapshot after deletion&lt;/span&gt;
&lt;span class="nx"&gt;rds_snapshot_identifier&lt;/span&gt;       &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="c1"&gt;# used to recover from a snapshot&lt;/span&gt;

&lt;span class="nx"&gt;rds_performance_insights_enabled&lt;/span&gt;  &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few notes on the configuration I used here;&lt;/p&gt;

&lt;p&gt;1) &lt;strong&gt;Instance sizing and encryption:&lt;/strong&gt; for production make sure you use an instance size that is larger than a &lt;code&gt;db.t2.micro&lt;/code&gt; such that you can use encryption on the storage layer.&lt;br&gt;
2) &lt;strong&gt;Maintenance window:&lt;/strong&gt; This day and time setting is used for patching of your instance.&lt;br&gt;
3) &lt;strong&gt;Public access:&lt;/strong&gt; Make sure to set public access off for obvious reasons, but this should already be the case anyway if your instance is hosted in a private subnet. &lt;br&gt;
4) &lt;strong&gt;Backups:&lt;/strong&gt; Two things regarding backups:&lt;br&gt;
   4.1) Providing the final snapshot identifier is useful when destroying the environment, it will automatically create a snapshot with the given name. If you do no supply this variable, you wont be able to remove the RDS instance with the &lt;code&gt;terraform destroy&lt;/code&gt; command and you'll have to do this manually(!).&lt;br&gt;
   4.2) RDS supports automated backups, make sure to set the retention period (in days) correctly. &lt;br&gt;
5) &lt;strong&gt;Query tracing:&lt;/strong&gt; To enable in depth tracing of your queries and performance statistics, set &lt;code&gt;performance_insights_enabled&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;. This is very useful in analyzing slow queries and, generally, query performance.&lt;br&gt;
6) &lt;strong&gt;Database password:&lt;/strong&gt; The password for the database is generated, this can be done with this resource:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"random_string"&lt;/span&gt; &lt;span class="s2"&gt;"password"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;length&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;
  &lt;span class="nx"&gt;special&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  RDS Security group
&lt;/h2&gt;

&lt;p&gt;Finally, we need to supply the security group configuration for RDS such that EC2 can communicate with our Database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_security_group"&lt;/span&gt; &lt;span class="s2"&gt;"_"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_name_prefix&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-rds-sg"&lt;/span&gt;

  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"RDS (terraform-managed)"&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rds_vpc_id&lt;/span&gt;

  &lt;span class="c1"&gt;# Only MySQL in&lt;/span&gt;
  &lt;span class="nx"&gt;ingress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sg_ingress_cidr_block&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Allow all outbound traffic.&lt;/span&gt;
  &lt;span class="nx"&gt;egress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"-1"&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sg_egress_cidr_block&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above allows &lt;code&gt;ingress&lt;/code&gt; from port 3306 and &lt;code&gt;egress&lt;/code&gt; everything.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;I hope this guide has been useful, please leave a comment below to let me know what you liked, did not like, suggestions and so on. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Next week I'll cover (Open)API security with configuration recommendations, and an AWS API firewall solution, &lt;a href="https://aws.amazon.com/waf/" rel="noopener noreferrer"&gt;AWS WAF&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>terraform</category>
      <category>devops</category>
      <category>database</category>
    </item>
    <item>
      <title>Serverless CI/CD for AWS Lambda</title>
      <dc:creator>Rolf Streefkerk</dc:creator>
      <pubDate>Sun, 01 Mar 2020 16:45:44 +0000</pubDate>
      <link>https://forem.com/rolfstreefkerk/serverless-ci-cd-for-aws-lambda-3hpg</link>
      <guid>https://forem.com/rolfstreefkerk/serverless-ci-cd-for-aws-lambda-3hpg</guid>
      <description>&lt;p&gt;Today I'll explain how to setup CI/CD (Continuous Integration / Continuous Development) with Terraform and AWS CodePipeline. To do that we use the same repository as the other articles in this series:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/rpstreef" rel="noopener noreferrer"&gt;
        rpstreef
      &lt;/a&gt; / &lt;a href="https://github.com/rpstreef/openapi-tf-example" rel="noopener noreferrer"&gt;
        openapi-tf-example
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Example of how you can use OpenAPI with AWS API Gateway, Also includes integrations with AWSLambda, AWS Cognito, AWS SNS and CloudWatch logs
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;and a new one has been added that contains the NodeJS code&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/rpstreef" rel="noopener noreferrer"&gt;
        rpstreef
      &lt;/a&gt; / &lt;a href="https://github.com/rpstreef/openapi-node-example" rel="noopener noreferrer"&gt;
        openapi-node-example
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      OpenAPI Node code example
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;What are we going to cover today?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
1. Setup Terraform (openapi-tf-example repo)

&lt;ul&gt;
&lt;li&gt;1.1 Source code&lt;/li&gt;
&lt;li&gt;1.2 AWS CodeBuild&lt;/li&gt;
&lt;li&gt;1.2.1 Buildspec file&lt;/li&gt;
&lt;li&gt;1.3 Permissions&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

2. Setup NodeJS source code (openapi-node-example repo)

&lt;ul&gt;
&lt;li&gt;2.1 Gulp file&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;3. Conclusion&lt;/li&gt;

&lt;li&gt;4. What's next&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  1. Setup Terraform (openapi-tf-example repo)
&lt;/h1&gt;

&lt;p&gt;To setup our CI/CD we are going to use a service that will enable us to setup an automated pipeline for us. On AWS that is called &lt;em&gt;CodePipeline&lt;/em&gt;, with this service we can setup any number of &lt;em&gt;stages&lt;/em&gt; that specify how to automate our deployments.&lt;/p&gt;

&lt;p&gt;Once we're done with this whole setup, we can have our code deployed automatically at every commit to the Master branch of our NodeJS repository.&lt;/p&gt;

&lt;p&gt;But first, let's look at the CodePipeline setup we'll be using:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F79ei5iz34c8gv71d4ueg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F79ei5iz34c8gv71d4ueg.png" alt="diagram" width="800" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Please note the two distinct halves of the solution;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Source&lt;/strong&gt;: This is the stage where the source code is retrieved from the designated Github repository&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build&lt;/strong&gt;: The stage where AWS CodeBuild retrieves the source code, and can perform actions on it within a Linux or Windows environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1.1 Source code
&lt;/h2&gt;

&lt;p&gt;The Source code stage of the pipeline looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_codepipeline"&lt;/span&gt; &lt;span class="s2"&gt;"_"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-codepipeline"&lt;/span&gt;
  &lt;span class="nx"&gt;role_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iam_codepipeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role_arn&lt;/span&gt;

  &lt;span class="nx"&gt;artifact_store&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;artifact_store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;
    &lt;span class="nx"&gt;type&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"S3"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;stage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Source"&lt;/span&gt;

    &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Source"&lt;/span&gt;
      &lt;span class="nx"&gt;category&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Source"&lt;/span&gt;
      &lt;span class="nx"&gt;owner&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ThirdParty"&lt;/span&gt;
      &lt;span class="k"&gt;provider&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"GitHub"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt;
      &lt;span class="nx"&gt;output_artifacts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

      &lt;span class="nx"&gt;configuration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;OAuthToken&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;github_token&lt;/span&gt;
        &lt;span class="nx"&gt;Owner&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;github_owner&lt;/span&gt;
        &lt;span class="nx"&gt;Repo&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;github_repo&lt;/span&gt;
        &lt;span class="nx"&gt;Branch&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;github_branch&lt;/span&gt;
        &lt;span class="nx"&gt;PollForSourceChanges&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;poll_source_changes&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The Github repo is configured using an OAuthToken connection and the arguments &lt;em&gt;PollForSourceChanges&lt;/em&gt; makes sure every push to the configured Branch triggers this stage of the pipeline.&lt;/p&gt;

&lt;p&gt;The output artifacts, &lt;em&gt;source&lt;/em&gt;, will be stored in this S3 bucket:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"artifact_store"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-codepipeline-artifacts-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;random_string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postfix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;acl&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"private"&lt;/span&gt;
  &lt;span class="nx"&gt;force_destroy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nx"&gt;lifecycle_rule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

    &lt;span class="nx"&gt;expiration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;days&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We set an expiration on the bucket to clear it out every 5 days.&lt;/p&gt;
&lt;h2&gt;
  
  
  1.2 AWS CodeBuild
&lt;/h2&gt;

&lt;p&gt;Now the second stage of the Pipeline is the &lt;a href="https://aws.amazon.com/codebuild/" rel="noopener noreferrer"&gt;AWS CodeBuild&lt;/a&gt; phase. This stage of the &lt;em&gt;aws_codepipeline&lt;/em&gt; resource looks like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;stage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Build"&lt;/span&gt;

    &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Build"&lt;/span&gt;
      &lt;span class="nx"&gt;category&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Build"&lt;/span&gt;
      &lt;span class="nx"&gt;owner&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWS"&lt;/span&gt;
      &lt;span class="k"&gt;provider&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CodeBuild"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt;
      &lt;span class="nx"&gt;input_artifacts&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

      &lt;span class="nx"&gt;configuration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;ProjectName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_codebuild_project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is pretty self explanatory, we get the source code from our S3 bucket defined previously and we point to the CodeBuild configuration using the ProjectName.&lt;/p&gt;

&lt;p&gt;In the &lt;em&gt;aws_codebuild_project&lt;/em&gt; resource we set which artifacts we have and where they come from, the environment we're running the build in and any environment variables we need access to during the build run.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_codebuild_project"&lt;/span&gt; &lt;span class="s2"&gt;"_"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-codebuild"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_codebuild_project"&lt;/span&gt;
  &lt;span class="nx"&gt;build_timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;build_timeout&lt;/span&gt;
  &lt;span class="nx"&gt;badge_enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;badge_enabled&lt;/span&gt;
  &lt;span class="nx"&gt;service_role&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iam_codebuild&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role_arn&lt;/span&gt;

  &lt;span class="nx"&gt;artifacts&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;type&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CODEPIPELINE"&lt;/span&gt;
    &lt;span class="nx"&gt;namespace_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"BUILD_ID"&lt;/span&gt;
    &lt;span class="nx"&gt;packaging&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ZIP"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;compute_type&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;build_compute_type&lt;/span&gt;
    &lt;span class="nx"&gt;image&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;build_image&lt;/span&gt;
    &lt;span class="nx"&gt;type&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"LINUX_CONTAINER"&lt;/span&gt;
    &lt;span class="nx"&gt;privileged_mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;privileged_mode&lt;/span&gt;

    &lt;span class="nx"&gt;environment_variable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"LAMBDA_FUNCTION_NAMES"&lt;/span&gt;
      &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_function_names&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;environment_variable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"LAMBDA_LAYER_NAME"&lt;/span&gt;
      &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_layer_name&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;type&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CODEPIPELINE"&lt;/span&gt;
    &lt;span class="nx"&gt;buildspec&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;template_file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buildspec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rendered&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The details of the build run are in the source &lt;em&gt;buildspec&lt;/em&gt; file.&lt;/p&gt;
&lt;h3&gt;
  
  
  1.2.1 Buildspec file
&lt;/h3&gt;

&lt;p&gt;In this file we get to define all the build phases and the commands, among other things, it needs to execute for us. For a full overview of the whole specification, look at the official documents &lt;a href="https://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What we need is pretty simple:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.2&lt;/span&gt;
&lt;span class="na"&gt;run-as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root&lt;/span&gt;
&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;LAMBDA_FUNCTION_NAMES&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$LAMBDA_FUNCTION_NAMES&lt;/span&gt;
    &lt;span class="na"&gt;LAMBDA_LAYER_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$LAMBDA_LAYER_NAME&lt;/span&gt;

&lt;span class="na"&gt;phases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;run-as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root&lt;/span&gt;
        &lt;span class="na"&gt;runtime-versions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;nodejs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
        &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pip3 install awscli --upgrade --user&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm install gulp-cli -g&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm install&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;run-as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root&lt;/span&gt;
        &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gulp update --functions "$LAMBDA_FUNCTION_NAMES" --lambdaLayer "$LAMBDA_LAYER_NAME"&lt;/span&gt;
    &lt;span class="na"&gt;post_build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
           &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo " Completed Lambda &amp;amp; Lambda Layer updates ... "&lt;/span&gt;
           &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo Update completed on `date`&lt;/span&gt;
&lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;update-$(date +%Y-%m-%d)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We need an up to date AWS CLI tool to update our Lambda and Lambda layer code, and then it needs to run our custom &lt;a href="https://gulpjs.com" rel="noopener noreferrer"&gt;Gulp&lt;/a&gt; script:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;gulp&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;functions&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$LAMBDA_FUNCTION_NAMES&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;lambdaLayer&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$LAMBDA_LAYER_NAME&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This script updates the code and configuration in all the functions specified in the &lt;em&gt;LAMBDA_FUNCTION_NAMES&lt;/em&gt; environment variable, as well as the Lambda-layer in &lt;em&gt;LAMBDA_LAYER_NAME&lt;/em&gt; environment variable.&lt;/p&gt;
&lt;h2&gt;
  
  
  1.3 Permissions
&lt;/h2&gt;

&lt;p&gt;Both AWS CodePipeline and CodeBuild need permissions to run operations on AWS services.&lt;/p&gt;

&lt;p&gt;For CodePipeline we need the following permissions set (see file ./policies/codepipeline-policy.json):&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObjectVersion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetBucketVersioning"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"s3:List*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"s3:PutObject"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"${s3_bucket_arn}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"${s3_bucket_arn}/*"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"codebuild:BatchGetBuilds"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"codebuild:StartBuild"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${codebuild_project_arn}"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;CodePipeline needs access to S3, to deploy the &lt;em&gt;source&lt;/em&gt; artifacts and it needs to start the CodeBuild process.&lt;/p&gt;

&lt;p&gt;In order for this CodeBuild phase to run properly, it needs permissions to access AWS S3, and to modify AWS Lambda and Lambda layer configurations. To that end we add the following policy from the file &lt;em&gt;./policies/codebuild-policy.json&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"logs:CreateLogGroup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"logs:CreateLogStream"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"logs:PutLogEvents"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"lambda:GetLayerVersion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"lambda:PublishLayerVersion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"lambda:PublishVersion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"lambda:UpdateAlias"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"lambda:UpdateFunctionCode"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"lambda:UpdateFunctionConfiguration"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"lambda:ListFunctions"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObjectVersion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetBucketVersioning"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"s3:List*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"s3:PutObject"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"${s3_bucket_arn}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"${s3_bucket_arn}/*"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;An improvement here would be to specify exactly which AWS Lambda resources the Lambda/Log policies apply to.&lt;/p&gt;
&lt;h1&gt;
  
  
  2. Setup NodeJS source code (openapi-node-example repo)
&lt;/h1&gt;

&lt;p&gt;The Terraform side of things has been set up, now for the CodeBuild phase to work, it needs the Gulp script to execute Lambda and Lambda-layer update statements.&lt;/p&gt;
&lt;h2&gt;
  
  
  2.1 Gulp file
&lt;/h2&gt;

&lt;p&gt;Below is the core of the Gulp script.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;minimist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lambdaLayer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambdaLayer&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;arrFunctions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;At the top part we need to get the CLI parameters using &lt;a href="https://github.com/substack/minimist" rel="noopener noreferrer"&gt;minimist&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;lambdaLayerJSON&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nx"&gt;gulp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;taskLambdaLayer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;shellJS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws lambda publish-layer-version --layer-name &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;lambdaLayer&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; --zip-file fileb://&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;projectName&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;zipLayerFilename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;lambdaLayerJSON&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then we need to publish the new Lambda layer code first before we update the Lambda's. This function captures the output of this shell execution and JSON parses this into a file scoped variable. We need the new Lambda layer version from the output.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;arrFunctions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;gulp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;update_&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;gulp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;series&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;updateCode&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;shellJS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws lambda update-function-code --function-name &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; --zip-file fileb://&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;projectName&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;zipFileName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;updateConfig&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;shellJS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws lambda update-function-configuration --function-name &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; --layers &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;lambdaLayerJSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LayerVersionArn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;gulp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lambdas&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;gulp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parallel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;arrFunctions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;update_&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Finally, we update the function Code with&lt;br&gt;
&lt;br&gt;
 &lt;code&gt;function updateCode&lt;/code&gt;&lt;br&gt;
&lt;br&gt;
 and then we update the functions configuration to update the Lambda layer version in&lt;br&gt;
&lt;br&gt;
 &lt;code&gt;function updateConfig&lt;/code&gt;&lt;br&gt;
&lt;br&gt;
. Here we use the file scoped &lt;em&gt;lambdaLayerJSON&lt;/em&gt; that stores the latest Lambda layer version ARN&lt;/p&gt;
&lt;h1&gt;
  
  
  3. Conclusion
&lt;/h1&gt;

&lt;p&gt;All the parts of the solution are there, the only thing left is to integrate this into our openapi-tf-example repo and we're done!&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"cicd"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github.com/rpstreef/tf-cicd-lambda?ref=v1.0"&lt;/span&gt;

  &lt;span class="nx"&gt;resource_tag_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_tag_name&lt;/span&gt;
  &lt;span class="nx"&gt;namespace&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;namespace&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;

  &lt;span class="nx"&gt;github_token&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;github_token&lt;/span&gt;
  &lt;span class="nx"&gt;github_owner&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;github_owner&lt;/span&gt;
  &lt;span class="nx"&gt;github_repo&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;github_repo&lt;/span&gt;
  &lt;span class="nx"&gt;poll_source_changes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;poll_source_changes&lt;/span&gt;

  &lt;span class="nx"&gt;lambda_layer_name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_lambda_layer_version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;layer_name&lt;/span&gt;
  &lt;span class="nx"&gt;lambda_function_names&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="k"&gt;${module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_names&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The last two variables contain all our Lambda and Lambda layer function names that need to be updated once the code in the &lt;em&gt;openapi-node-example&lt;/em&gt; repo changes.&lt;/p&gt;
&lt;h1&gt;
  
  
  4. What's next
&lt;/h1&gt;

&lt;p&gt;With that you can quickly setup a CI/CD that works for your Serverless Lambda/Lambda-layer environment. Checkout my dedicated CI/CD repo for Serverless here:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/rpstreef" rel="noopener noreferrer"&gt;
        rpstreef
      &lt;/a&gt; / &lt;a href="https://github.com/rpstreef/tf-cicd-lambda" rel="noopener noreferrer"&gt;
        tf-cicd-lambda
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Terraform AWS CI/CD module for AWS Lambda compute
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;If you have any questions about the setup, comments, or improvement suggestions? Do comment and share!&lt;/p&gt;

&lt;p&gt;Thanks for reading and till next week!&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>devops</category>
      <category>aws</category>
    </item>
    <item>
      <title>Automated Static Site With AWS CodePipeline and CloudFormation</title>
      <dc:creator>Rolf Streefkerk</dc:creator>
      <pubDate>Sun, 23 Feb 2020 09:15:20 +0000</pubDate>
      <link>https://forem.com/rolfstreefkerk/automated-static-site-with-aws-codepipeline-and-cloudformation-5cla</link>
      <guid>https://forem.com/rolfstreefkerk/automated-static-site-with-aws-codepipeline-and-cloudformation-5cla</guid>
      <description>&lt;p&gt;This is a guide to setting up and deploying your static site with &lt;a href="https://gohugo.io/" rel="noopener noreferrer"&gt;Hugo&lt;/a&gt; on AWS infrastructure. I'll explain how to set up an automated deployment with AWS CodePipeline using a CloudFormation template, and an AWS Lambda function to generate our site and deploy it to S3.&lt;/p&gt;

&lt;p&gt;Briefly I'll cover how to manually setup your domain on Route53 and a CodeCommit git repository. For these last two steps you could also use your own domain name solution and a GitHub repository.&lt;/p&gt;

&lt;p&gt;I've dedicated a section about costs as well to show what kind of potential savings you could realize using a static site on AWS. See 7. What does it cost? to get an idea. The short story, you can run this static site with a decent amount of traffic for about &lt;strong&gt;$1.00 / month&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We'll cover the following topics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1. Let's get started&lt;/li&gt;
&lt;li&gt;2. Setup AWS S3 buckets&lt;/li&gt;
&lt;li&gt;3. Setup AWS Route53 DNS&lt;/li&gt;
&lt;li&gt;4. Setup AWS CodeCommit&lt;/li&gt;
&lt;li&gt;5. AWS Lambda: Static site Generator&lt;/li&gt;
&lt;li&gt;6. Custom AWS CodePipeline setup with AWS CloudFormation&lt;/li&gt;
&lt;li&gt;7. What does it cost?&lt;/li&gt;
&lt;li&gt;8. Comments&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  1. Let's get started
&lt;/h1&gt;

&lt;p&gt;First we need an &lt;strong&gt;Amazon Web Services account&lt;/strong&gt;, go to &lt;a href="http://aws.amazon.com/" rel="noopener noreferrer"&gt;Amazon Web Services&lt;/a&gt; and sign up for the 1 year free tier account if you haven't already.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Also check out my GitHub repository alongside this guide at &lt;a href="https://github.com/rpstreef/static-site-generator" rel="noopener noreferrer"&gt;https://github.com/rpstreef/static-site-generator&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Next up, let's look at a brief overview of what we'll be building and which AWS services are used and how they are connected.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fkxqqu7rqictmetnirqaw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fkxqqu7rqictmetnirqaw.png" alt="AWS Static Site setup" width="800" height="680"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS CodeCommit, git repository for your source code.&lt;/li&gt;
&lt;li&gt;AWS Lambda, runs code on amazon managed servers.&lt;/li&gt;
&lt;li&gt;AWS S3, hosting on durable file storage.

&lt;ul&gt;
&lt;li&gt;Source code bucket: once the automation process runs, the master branch from your CodeCommit repo will be copied to this bucket.&lt;/li&gt;
&lt;li&gt;Site bucket: contains the generated website deployed by the Lambda function.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;AWS CodePipeline, our workflow defined in a CloudFormation template.&lt;/li&gt;

&lt;li&gt;AWS CloudFormation, infrastructure as code. Deploys our Lambda function among other things.&lt;/li&gt;

&lt;li&gt;AWS Route53, (optional) domain name setup.&lt;/li&gt;

&lt;li&gt;AWS CloudWatch, Lambda function execution logs can be found here for this stack.&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  2. Setup AWS S3 buckets
&lt;/h1&gt;

&lt;p&gt;First, let's set up the website S3 bucket. Login to the AWS Console and head on over to S3:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create your website bucket, choose a name in the format of the URL you will host it on. For instance, 'blog.fxaugury.com'.&lt;/li&gt;
&lt;li&gt;Make sure in the bucket properties the bucket is set to website hosting and that CORS is enabled.

&lt;ul&gt;
&lt;li&gt;CORS will make sure we can access same origin content.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Check permissions on your website S3 bucket, click on your website S3 bucket go to Permissions &amp;gt; view table "Manage public permissions".

&lt;ul&gt;
&lt;li&gt;Make sure the "Everyone" group is set to read permissions for both "Object access" and "Permissions Access".&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Please note: When testing your website S3 bucket, if the index.html does not display. The likely cause is that the index.html (and possible other files as well) file is not set to public.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  3. Setup AWS Route53 DNS
&lt;/h1&gt;

&lt;p&gt;Now that the S3 bucket for your static site has been setup, we can (optionally) connect it to our domain via Route53. Follow the steps outlined on Amazon to register a domain &lt;em&gt;OR&lt;/em&gt; transfer an existing domain to Amazon. Once that is done, we need to adapt the hosted zone for the domain you want to use.&lt;/p&gt;

&lt;p&gt;Go to Route53, select your domain name you want to use for the static site. Create a new CNAME record set:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Value: Set the S3 bucket endpoint &lt;strong&gt;URL&lt;/strong&gt;.

&lt;ul&gt;
&lt;li&gt;It's the URL that contains the string; "s3-website". To find the endpoint, go to your website bucket &amp;gt; properties &amp;gt; Static website hosting &amp;gt; "Endpoint"&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Alias: &lt;strong&gt;No&lt;/strong&gt;
&lt;/li&gt;

&lt;li&gt;TTL: &lt;strong&gt;300 seconds&lt;/strong&gt;, default propagation is sufficient. Unless you're migrating then you probably want to lower this number to avoid downtime.&lt;/li&gt;

&lt;li&gt;Routing Policy: &lt;strong&gt;Simple&lt;/strong&gt;
&lt;/li&gt;

&lt;li&gt;Name: The domain name you would like to use instead, e.g. &lt;strong&gt;blog.fxaugury.com&lt;/strong&gt;
&lt;/li&gt;

&lt;li&gt;

&lt;em&gt;Save&lt;/em&gt; record set.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Done! After 300 seconds the changes should have propagated and you can use the domain name that you have registered.&lt;/p&gt;

&lt;h1&gt;
  
  
  4. Setup AWS CodeCommit
&lt;/h1&gt;

&lt;p&gt;To setup CodeCommit, there are two steps that need to be executed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Setup local git environment with Hugo and the SSH parameters.&lt;/li&gt;
&lt;li&gt;Setup and connect to the AWS CodeCommit git repo.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;First order of business, our local GIT repository and Hugo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install Hugo, &lt;code&gt;hugo new site &amp;lt;site name&amp;gt;&lt;/code&gt;.

&lt;ul&gt;
&lt;li&gt;See &lt;a href="https://gohugo.io/overview/quickstart/" rel="noopener noreferrer"&gt;https://gohugo.io/overview/quickstart/&lt;/a&gt; for a quick guide on how to start with Hugo.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;In your Hugo site directory, init your git if you haven't already, &lt;code&gt;git init&lt;/code&gt;.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Second, setup the SSH parameters. These instructions for ssh-keygen terminal application are for Linux based machines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open up a terminal and create your ssh key via &lt;code&gt;ssh-keygen&lt;/code&gt;, follow the onscreen instructions.&lt;/li&gt;
&lt;li&gt;To use your SSH Key, go to AWS Identity and Access Management (IAM) &amp;gt; Users &amp;gt; 'User name' &amp;gt; tab "Security credentials" &amp;gt; "SSH keys for AWS CodeDeploy":

&lt;ul&gt;
&lt;li&gt;Copy paste the public key here.&lt;/li&gt;
&lt;li&gt;Create config file in ~/.ssh/config&lt;/li&gt;
&lt;li&gt;Paste below, where User is your SSH key ID you just created in IAM
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Host git-codecommit.&lt;span class="k"&gt;*&lt;/span&gt;.amazonaws.com
User APKAEIBAERJR2EXAMPLE
IdentityFile ~/.ssh/codecommit_rsa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Change permssions of the "Config" file just created
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;600 config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok the local setup is done for now. Head on over to AWS CodeCommit.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the AWS console, go to CodeCommit &amp;gt; Create repository.&lt;/li&gt;
&lt;li&gt;Creation of the repository is done, no we need to test the SSH connection to this repositry
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh git-codecommit.us-east-2.amazonaws.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add this server to known server list.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The CodeCommit git url depends on where your CodeCommit git is created from, in my case it's created in Oregon (region: us-east-2)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We can succesfully connect via SSH to AWS CodeCommit. Now to add our local repository data to AWS CodeCommit. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to "Code" then "Connect", copy paste the url where it says "clone". Instead of cloning we'll push our local git to this remote:&lt;/li&gt;
&lt;li&gt;In our local git repo directory, enter:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git remote add &amp;lt;repo name&amp;gt; ssh://git-codecommit.us-west-2.amazonaws.com/v1/repos/&amp;lt;repo name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;now we've added the remote repository, next thing is to push it.&lt;/li&gt;
&lt;li&gt;Before you push it, make sure you have removed any git repo trace from the theme submodule in your Hugo themes folder. It will be recognized as such because each theme will have a .git directory.&lt;/li&gt;
&lt;li&gt;Remove the .git directory, clear the cache and add the theme directory to our repo:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &amp;lt;theme directory path&amp;gt;/.git
git &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nt"&gt;--cached&lt;/span&gt; &amp;lt;theme directory path&amp;gt;
git add &amp;lt;theme directory path&amp;gt;
git commit &lt;span class="nt"&gt;-am&lt;/span&gt; &lt;span class="s1"&gt;'theme added'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now push our local git changes to CodeCommit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git push &lt;span class="nt"&gt;--set-upstream&lt;/span&gt; &amp;lt;repo name&amp;gt; master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We should see all the code from our local git repository in AWS CodeCommit repository. For subsequent changes we can just use &lt;code&gt;git push&lt;/code&gt; to push our changes to CodeCommit master branch.&lt;/p&gt;

&lt;h1&gt;
  
  
  5. AWS Lambda: Static site Generator
&lt;/h1&gt;

&lt;p&gt;So we have setup an S3 bucket for your website, a domain name (optionally), and a local git and CodeCommit repo.&lt;/p&gt;

&lt;p&gt;Next we'll review the static site generation program code briefly and upload the code as a zip file to a new S3 bucket.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To review the commented code, see: &lt;a href="https://github.com/rpstreef/static-site-generator/blob/master/generate_static_site.py" rel="noopener noreferrer"&gt;https://github.com/rpstreef/static-site-generator/blob/master/generate_static_site.py&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;CodeCommit source code pulled to our CodeCommit S3 bucket is encrypted, to be able to read it in our Lambda function we need to use signature version 4 for the S3 API to handle KMS encrypted files.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When you view these objects in your AWS S3 CodePipeline bucket, you'll see "Encryption AWS-KMS" in its properties and you wont be able to view it's contents.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The following two lines after the &lt;em&gt;boto3 python sdk import&lt;/em&gt; will allow the application to read the contents of the files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;botocore.client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt;

&lt;span class="n"&gt;S3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signature_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s3v4&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's deploy the code and the hugo binary to a new S3 bucket:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a new bucket, any name will suffice, preferably in the same region as your CodePipeline.&lt;/li&gt;
&lt;li&gt;Upload a zip file in this new bucket containing two files:

&lt;ol&gt;
&lt;li&gt;The hugo executable binary: &lt;code&gt;hugo&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Download the latest binary from here: &lt;a href="https://github.com/spf13/hugo/releases" rel="noopener noreferrer"&gt;https://github.com/spf13/hugo/releases&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Generate static site program: &lt;code&gt;generate_static_site.py&lt;/code&gt;
&lt;/li&gt;

&lt;/ol&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;We'll need to enter these details when we execute the CloudFormation template in the next section of this guide.&lt;/p&gt;

&lt;h1&gt;
  
  
  6. Custom AWS CodePipeline setup with AWS CloudFormation
&lt;/h1&gt;

&lt;p&gt;Almost there, now we need to setup a custom CodePipeline using a CloudFormation template. When the CloudFormation template is executed, we'll just have one more step to set this automation in motion.&lt;/p&gt;

&lt;p&gt;First, we need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an S3 Bucket to transfer source code from the Master (trunk) git branch.

&lt;ul&gt;
&lt;li&gt;This will be created for us in this template when we don't have one yet.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;a Lambda function to Generate, from that source code, a static site.

&lt;ul&gt;
&lt;li&gt;We just uploaded the zip file containing the Lambda function program and hugo static site generator binary. &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Custom CodePipeline template that will outline these steps.

&lt;ul&gt;
&lt;li&gt;This is definined in the CloudFormation template we're going to discuss in this chapter.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Please review the full CloudFormation template here: &lt;a href="https://github.com/rpstreef/static-site-generator/blob/master/cf_stack.yml" rel="noopener noreferrer"&gt;https://github.com/rpstreef/static-site-generator/blob/master/cf_stack.yml&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The custom CodePipeline will look as follows in our CloudFormation template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Fetch code from Master branch, execute Lambda function to generate, compress and deploy static site.&lt;/span&gt;
  &lt;span class="na"&gt;CodePipeline&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AWS::CodePipeline::Pipeline"&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${DomainName}-codepipeline"&lt;/span&gt;
      &lt;span class="na"&gt;ArtifactStore&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;S3&lt;/span&gt;
        &lt;span class="na"&gt;Location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;ExistingCodePipelineBucket&lt;/span&gt;
      &lt;span class="na"&gt;RestartExecutionOnUpdate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="na"&gt;RoleArn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arn:aws:iam::${AWS::AccountId}:role/${CodePipelineRole}"&lt;/span&gt;
      &lt;span class="na"&gt;Stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Source&lt;/span&gt;
          &lt;span class="na"&gt;Actions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SourceAction&lt;/span&gt;
              &lt;span class="na"&gt;ActionTypeId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;Category&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Source&lt;/span&gt;
                &lt;span class="na"&gt;Owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS&lt;/span&gt;
                &lt;span class="na"&gt;Provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CodeCommit&lt;/span&gt;
                &lt;span class="na"&gt;Version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
              &lt;span class="na"&gt;Configuration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;RepositoryName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;ExistingGitRepository&lt;/span&gt;
                &lt;span class="na"&gt;BranchName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;
              &lt;span class="na"&gt;OutputArtifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SiteSource&lt;/span&gt;
              &lt;span class="na"&gt;RunOrder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;InvokeGenerator&lt;/span&gt;
          &lt;span class="na"&gt;Actions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;InvokeAction&lt;/span&gt;
              &lt;span class="na"&gt;InputArtifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SiteSource&lt;/span&gt;
              &lt;span class="na"&gt;ActionTypeId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;Category&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Invoke&lt;/span&gt;
                &lt;span class="na"&gt;Owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS&lt;/span&gt;
                &lt;span class="na"&gt;Provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Lambda&lt;/span&gt;
                &lt;span class="na"&gt;Version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
              &lt;span class="na"&gt;Configuration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;FunctionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;GeneratorLambdaFunction&lt;/span&gt;
              &lt;span class="na"&gt;OutputArtifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SiteContent&lt;/span&gt;
              &lt;span class="na"&gt;RunOrder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pipeline consists of just two steps;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;"Source": CodeCommit repository with the name "!Ref ExistingGitRepository", will supply source code from the master branche. The !Ref part means it references what has been entered during CloudFormation stack creation for this field name.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;"InvokeGenerator" will invoke the Lambda function by the name of "!Ref GeneratorLambdaFunction". This function will retrieve code stored in our CodePipeline bucket (made availble in step 1), transform it to a static site, GZIP it, copy the result to our website S3 bucket.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To be able for this CodePipeline to execute, we need authorization to do so. There are two Roles to be defined; the first for the CodePipeline itself, the second for the Lambda Function. The CodePipeline role must be able to assume roles set for AWS Lambda services and the CodePipeline.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Allow all actions on Lambda and CodePipeline&lt;/span&gt;
  &lt;span class="na"&gt;CodePipelineRole&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AWS::IAM::Role"&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;AssumeRolePolicyDocument&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2012-10-17"&lt;/span&gt;
        &lt;span class="na"&gt;Statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
            &lt;span class="na"&gt;Principal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;Service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lambda.amazonaws.com"&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;codepipeline.amazonaws.com"&lt;/span&gt;
            &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sts:AssumeRole"&lt;/span&gt;
      &lt;span class="na"&gt;Path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/"&lt;/span&gt;
      &lt;span class="na"&gt;Policies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;PolicyName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;codepipeline-service"&lt;/span&gt;
          &lt;span class="na"&gt;PolicyDocument&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2012-10-17"&lt;/span&gt;
            &lt;span class="na"&gt;Statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Allow"&lt;/span&gt;
                &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;
                &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The role it will assume is outlined below, "${DomainName}-execution-policy". It defines the execution policies for the AWS Lambda function which will do all the heavy lifting in this pipeline. It's allowed all the actions specified in CodePipeline and S3 services:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;#Allow Lambda access to; List, upload, get, delete, put object or acl on S3 and Set job results on CodePipeline&lt;/span&gt;
  &lt;span class="na"&gt;LambdaExecutionRole&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::IAM::Role&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;AssumeRolePolicyDocument&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2012-10-17'&lt;/span&gt;
        &lt;span class="na"&gt;Statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
            &lt;span class="na"&gt;Principal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;Service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;lambda.amazonaws.com&lt;/span&gt;
            &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sts:AssumeRole&lt;/span&gt;
      &lt;span class="na"&gt;Path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/"&lt;/span&gt;
      &lt;span class="na"&gt;Policies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;PolicyName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${DomainName}-execution-policy"&lt;/span&gt;
          &lt;span class="na"&gt;PolicyDocument&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2012-10-17'&lt;/span&gt;
            &lt;span class="na"&gt;Statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
                &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;logs:*"&lt;/span&gt;
                &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arn:aws:logs:*:*:*"&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
                &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;codepipeline:PutJobSuccessResult&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;codepipeline:PutJobFailureResult&lt;/span&gt;
                &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
                &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;s3:GetBucketLocation&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;s3:ListBucket&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;s3:ListBucketMultipartUploads&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;s3:AbortMultipartUpload&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;s3:DeleteObject&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;s3:GetObject&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;s3:GetObjectAcl&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;s3:ListMultipartUploadParts&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;s3:PutObject&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;s3:PutObjectAcl&lt;/span&gt;
                &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;!Join&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arn:aws:s3:::"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="nv"&gt;ExistingSiteBucket&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/*"&lt;/span&gt;&lt;span class="pi"&gt;]]&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;!Join&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arn:aws:s3:::"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="nv"&gt;ExistingCodePipelineBucket&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/*"&lt;/span&gt;&lt;span class="pi"&gt;]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last bit of code in our CloudFormation template defines the Lambda resource to be created. It will provide an environment variable that points to our website bucket. The sizing, 256Mb of memory, should be sufficient to run the conversion and copy commands within 5-6 seconds per run.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;GeneratorLambdaFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AWS::Lambda::Function"&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Static&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;site&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;generator&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;${DomainName}"&lt;/span&gt;
      &lt;span class="na"&gt;Role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;LambdaExecutionRole.Arn&lt;/span&gt;
      &lt;span class="na"&gt;MemorySize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;256&lt;/span&gt;
      &lt;span class="na"&gt;Timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;300&lt;/span&gt;
      &lt;span class="na"&gt;Runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;GeneratorLambdaFunctionRuntime&lt;/span&gt;
      &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;GeneratorLambdaFunctionHandler&lt;/span&gt;
      &lt;span class="na"&gt;Code&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;S3Bucket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;GeneratorLambdaFunctionS3Bucket&lt;/span&gt;
        &lt;span class="na"&gt;S3Key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;GeneratorLambdaFunctionS3Key&lt;/span&gt;
      &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;SiteBucket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;ExistingSiteBucket&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To execute this template: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In your AWS Console go to; CloudFormation &amp;gt; Create Stack &amp;gt; Choose a template &amp;gt; Upload to S3 &amp;gt; Choose &lt;code&gt;cf_stack.yml&lt;/code&gt; and then click next.&lt;/li&gt;
&lt;li&gt;Set all the other properties to the values we have covered in the last chapters. Click next.&lt;/li&gt;
&lt;li&gt;Optionally you can set tags for this stack to your liking. Click next.&lt;/li&gt;
&lt;li&gt;Place a check mark at, "I acknowledge that AWS CloudFormation might create IAM resources." then press Create. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now it will do it's work and it should show you in a minute or two that it is done successfully creating the stack.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To activate this automation we only need to push our local git repo changes to our CodeCommit master branch remote repo and it will trigger the CodePipeline and subsequently our Lambda function to deploy our static site with the latest changes!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now execute a push to your AWS CodeCommit repo and watch the CodePipeline execution do it's thing to automatically generate the static site and deploy it to your website S3 bucket.&lt;/p&gt;

&lt;h1&gt;
  
  
  7. What does it cost?
&lt;/h1&gt;

&lt;p&gt;Now that we've finally got the whole thing running :) What does it actually cost to run on a monthly basis:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;Subtotal&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;S3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5 GB storage&lt;/td&gt;
&lt;td&gt;$0.12&lt;/td&gt;
&lt;td&gt;$0.12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;5000 put/list requests&lt;/td&gt;
&lt;td&gt;$0.03&lt;/td&gt;
&lt;td&gt;$0.15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;100,000 Get and Other Requests&lt;/td&gt;
&lt;td&gt;$0.04&lt;/td&gt;
&lt;td&gt;$0.19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Inter region bucket transfers of 1GB/month (depends on CodePipeline/Commit region availability)&lt;/td&gt;
&lt;td&gt;$0.02&lt;/td&gt;
&lt;td&gt;$0.21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Route53&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hosted zone&lt;/td&gt;
&lt;td&gt;$0.50&lt;/td&gt;
&lt;td&gt;$0.71&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Standard queries: 1 million / month&lt;/td&gt;
&lt;td&gt;$0.40&lt;/td&gt;
&lt;td&gt;$1.11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CodePipeLine&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1 Free pipeline per month&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;$1.11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CodeCommit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;First 5 users free with 50GB storage and 10,000 git requests/month&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;$1.11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Lambda&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Memory: 256Mb you get 1,600,000 seconds of compute time for free&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;$1.11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Free tier&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Discount&lt;/td&gt;
&lt;td&gt;-$0.14&lt;/td&gt;
&lt;td&gt;$0.97&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.97&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Compare this to running your blog on &lt;strong&gt;Wordpress&lt;/strong&gt; (no custom domain costs included):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Monetize&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Static site on AWS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;$1.11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Wordpress Free&lt;/strong&gt; (No custom domain)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Wordpress Personal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;$2.99&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Wordpress Premium&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;$8.25&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;If you need; blog monetization, analytics, storage flexibility, your own branding, and google analytics. Running a &lt;strong&gt;static site on AWS&lt;/strong&gt; offers much better value at about 15% of the cost of the &lt;strong&gt;Wordpress Premium&lt;/strong&gt; option.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  8. Comments
&lt;/h1&gt;

&lt;p&gt;I hope this guide has been useful, please leave a comment below to let me know what you liked, did not like, suggestions and so on. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Next week I'll continue my OpenAPI series (not enough time this week) and I'll cover what I promised, CI/CD with CodePipeline and Terraform.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>devops</category>
      <category>aws</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
