<?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>DEV Community: Julien Goux</title>
    <description>The latest articles on DEV Community by Julien Goux (@jgoux).</description>
    <link>https://dev.to/jgoux</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%2F576467%2Fa67c397d-7a45-4234-8800-bf92a0a226df.jpeg</url>
      <title>DEV Community: Julien Goux</title>
      <link>https://dev.to/jgoux</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jgoux"/>
    <language>en</language>
    <item>
      <title>Preview environments per Pull Request using AWS CDK and Github Actions</title>
      <dc:creator>Julien Goux</dc:creator>
      <pubDate>Sun, 14 Mar 2021 16:35:27 +0000</pubDate>
      <link>https://dev.to/jgoux/preview-environments-per-pull-request-using-aws-cdk-and-github-actions-bfi</link>
      <guid>https://dev.to/jgoux/preview-environments-per-pull-request-using-aws-cdk-and-github-actions-bfi</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; The code is 👉 &lt;a href="https://github.com/jgoux/preview-environments-per-pull-request-using-aws-cdk-and-github-actions" rel="noopener noreferrer"&gt;here&lt;/a&gt; 👈. The rest of this post gives information and context about it.&lt;/p&gt;

&lt;h1&gt;
  
  
  📖 Table of contents
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;👋 Introduction&lt;/li&gt;
&lt;li&gt;🎯 Target&lt;/li&gt;
&lt;li&gt;✅ Prerequisites&lt;/li&gt;
&lt;li&gt;
☁️ AWS

&lt;ul&gt;
&lt;li&gt;Architecture&lt;/li&gt;
&lt;li&gt;CDK&lt;/li&gt;
&lt;li&gt;
File structure

&lt;ul&gt;
&lt;li&gt;cdk.json&lt;/li&gt;
&lt;li&gt;bin/app.ts&lt;/li&gt;
&lt;li&gt;lib/awesome-stack.ts&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

CLI

&lt;ul&gt;
&lt;li&gt;yarn bootstrap&lt;/li&gt;
&lt;li&gt;yarn deploy&lt;/li&gt;
&lt;li&gt;yarn destroy&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;

🤖 Github automation

&lt;ul&gt;
&lt;li&gt;Github Deployments API&lt;/li&gt;
&lt;li&gt;Github Actions&lt;/li&gt;
&lt;li&gt;Pull Request deploy&lt;/li&gt;
&lt;li&gt;Pull Request clean-up&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;📸 Workflow in pictures&lt;/li&gt;

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

&lt;/ul&gt;

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

&lt;p&gt;One of the biggest challenges today when delivering software is to move &lt;strong&gt;fast&lt;/strong&gt;. Moving fast and keeping a high quality and confidence in your code is not an easy task. Having a tight feedback loop is our number one priority at &lt;a href="https://www.clovis.pro" rel="noopener noreferrer"&gt;Clovis&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Services like &lt;a href="https://vercel.com/" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt;, &lt;a href="https://www.netlify.com/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt;, &lt;a href="https://render.com/" rel="noopener noreferrer"&gt;Render&lt;/a&gt;, &lt;a href="https://www.qovery.com/" rel="noopener noreferrer"&gt;Qovery&lt;/a&gt; or &lt;a href="http://railway.app/" rel="noopener noreferrer"&gt;Railway&lt;/a&gt; are all working toward that goal, making our lives &lt;strong&gt;way easier&lt;/strong&gt;. All these platforms allow us to quickly iterate, delivering value by focusing only on our code and forgetting about the nightmare that devops can be. I'm grateful for each of them.&lt;/p&gt;

&lt;p&gt;But what if devops could be &lt;strong&gt;good old regular code&lt;/strong&gt;? So accessible that you could actually spin up your entire infrastructure in few lines of code. By directly using a cloud provider you have no more limits, at a lower cost!&lt;/p&gt;

&lt;p&gt;As an example, I'm going to show you how to setup preview environments in your Pull Requests on Github, using AWS CDK and Github Actions. Preview environments are a terrific tool to share your work before it goes to production or to run end-to-end tests. It gives a lot of confidence to have a mirror of your production infrastructure to play with.&lt;/p&gt;

&lt;h1&gt;
  
  
  🎯 Target
&lt;/h1&gt;

&lt;p&gt;At the end of this tutorial, we should be able to :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trigger a preview environment deployment by labelling a Pull Request with &lt;code&gt;🚀 deploy&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;As long as the Pull Request is labeled with &lt;code&gt;🚀 deploy&lt;/code&gt;, we should have our preview environment updated on each push&lt;/li&gt;
&lt;li&gt;When the label &lt;code&gt;🚀 deploy&lt;/code&gt; is removed from the Pull Request or the Pull Request is closed, the preview environment should be destroyed&lt;/li&gt;
&lt;li&gt;All the deployments should be integrated with Github Deployments API to have useful feedbacks from within the Pull Requests or on the repository's home page&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  ✅ Prerequisites
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Node.js LTS (v14.16.0)&lt;/li&gt;
&lt;li&gt;AWS :

&lt;ul&gt;
&lt;li&gt;Sign up and get your &lt;a href="https://docs.aws.amazon.com/powershell/latest/userguide/pstools-appendix-sign-up.html" rel="noopener noreferrer"&gt;access keys&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.github.com/en/actions/reference/encrypted-secrets#creating-encrypted-secrets-for-a-repository" rel="noopener noreferrer"&gt;Set the access keys as secrets&lt;/a&gt; under the names &lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; and &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt; in your repository.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/setup-credentials.html" rel="noopener noreferrer"&gt;Set them on your development machine&lt;/a&gt; to run the various cdk commands. Don't forget to set a default region (previous link) if you want to deploy in a different region than &lt;code&gt;us-east-1&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  ☁️ AWS
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fwamluxonpu3k7otzf2l2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fwamluxonpu3k7otzf2l2.png" alt="post"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The architecture is composed of &lt;a href="https://aws.amazon.com/cloudfront/" rel="noopener noreferrer"&gt;CloudFront&lt;/a&gt; (CDN) in front of a private &lt;a href="https://aws.amazon.com/s3/" rel="noopener noreferrer"&gt;S3 Bucket&lt;/a&gt; (file storage). CloudFront serves the sources stored in the bucket to the outside world.&lt;/p&gt;

&lt;h2&gt;
  
  
  CDK
&lt;/h2&gt;

&lt;p&gt;The AWS &lt;strong&gt;C&lt;/strong&gt;loud &lt;strong&gt;D&lt;/strong&gt;evelopment &lt;strong&gt;K&lt;/strong&gt;it is an open source software development framework to define your cloud application resources using familiar programming languages such as TypeScript in the context of this post.&lt;/p&gt;

&lt;p&gt;TypeScript gives us autocompletion and type safety on every part of our infrastructure, no more back and forth with the documentation, everything is available with a few keystrokes.&lt;/p&gt;

&lt;p&gt;You can forget the hundreds of lines of CloudFormation yaml files or the confusing AWS Console UI. As an infrastructure as code framework, CDK is an abstraction over these low level constructs with sensible defaults.&lt;/p&gt;

&lt;h3&gt;
  
  
  File structure
&lt;/h3&gt;

&lt;p&gt;A typical CDK project is composed of :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;bin/app.ts&lt;/code&gt; the main file instantiating the stacks and called by the cdk CLI&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lib/*-stack.ts&lt;/code&gt; the stacks implementations&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cdk.json&lt;/code&gt; the configuration file for the cdk CLI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's see what is inside each of these files.&lt;/p&gt;

&lt;h4&gt;
  
  
  cdk.json
&lt;/h4&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;"app"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"yarn ts-node bin/app.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context"&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;"@aws-cdk/core:newStyleStackSynthesis"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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 &lt;code&gt;app&lt;/code&gt; key is used to tell cdk how to start our app.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;context&lt;/code&gt; key can contain feature flags like the one used here. In a future release of the AWS CDK, "new style" stack synthesis will become the default, but for now we need to opt in using the feature flag.&lt;/p&gt;

&lt;h4&gt;
  
  
  bin/app.ts
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@aws-cdk/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;AwesomeStack&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../lib/awesome-stack&lt;/span&gt;&lt;span class="dl"&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * The name of the stack depends on the STAGE environment variable so we can deploy the infrastructure multiple times in parallel
 * @example
 * AwesomeStack-pr-1-awesome-branch
 * AwesomeStack-production
 */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stackName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AwesomeStack-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&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;env&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AwesomeStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stackName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The stack's name is an important concept. This is what we will be referring to when deploying with the cdk CLI. You can note that in our case, this name depends on a STAGE environment variable. This allow us to dynamically deploy a whole new stack by Pull Request as the STAGE variable is derived from the Pull Request number and the branch name (more on this later)!&lt;/p&gt;

&lt;h4&gt;
  
  
  lib/awesome-stack.ts
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;cloudfront&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-cdk/aws-cloudfront&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;cloudfrontOrigins&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-cdk/aws-cloudfront-origins&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-cdk/aws-s3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;s3Deployment&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-cdk/aws-s3-deployment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-cdk/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * The CloudFormation stack holding all our resources
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AwesomeStack&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;cdk&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="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Construct&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StackProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&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;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * The S3 Bucket hosting our build
     */&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Bucket&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="na"&gt;autoDeleteObjects&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="na"&gt;removalPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RemovalPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DESTROY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * The CloudFront distribution caching and proxying our requests to our bucket
     */&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;distribution&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cloudfront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Distribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Distribution&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="na"&gt;defaultBehavior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cloudfrontOrigins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;S3Origin&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="na"&gt;viewerProtocolPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cloudfront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ViewerProtocolPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REDIRECT_TO_HTTPS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;defaultRootObject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;index.html&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="cm"&gt;/**
     * Output the distribution's url so we can pass it to external systems
     */&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CfnOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DeploymentUrl&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="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;distribution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;distributionDomainName&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Upload our build to the bucket and invalidate the distribution's cache
     */&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;s3Deployment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BucketDeployment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;BucketDeployment&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="na"&gt;destinationBucket&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="nx"&gt;distribution&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;distributionPaths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/index.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;s3Deployment&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="nf"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./website&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="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 stack class is the unit where we declare all the resources that will be created during the deployment. To create a stack you need to extend the &lt;code&gt;cdk.Stack&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;removalPolicy&lt;/code&gt; is often available on stateful resources such as S3 bucket or RDS databases. The default is to keep you from making a mistake so data are kept after destroying an environment. In our case we want to clean-up everything so we have to tell AWS to explicitly &lt;code&gt;DESTROY&lt;/code&gt; our bucket.&lt;/p&gt;

&lt;p&gt;At the beginning of this post I talked about CDK providing sensible defaults. The general rule is that every resource you create is private by default. It means that if you want to expose them you have to do it explicitly. Security wise this is a very good point. The objects in our bucket here are not publicly accessible, only CloudFront can read them.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cdk.CfnOutput&lt;/code&gt; is a construct that allow you to export arbitrary values at the end of the deployment. It will be very useful to pass the deployed URL to Github Deployment API.&lt;/p&gt;

&lt;p&gt;This is it, this is our infrastructure, in 30 lines of actual code. Not bad!&lt;/p&gt;

&lt;h3&gt;
  
  
  CLI
&lt;/h3&gt;

&lt;p&gt;Now that the infrastructure is sorted out, let's see how we interact with the CDK CLI. I defined 3 main commands as scripts in the &lt;code&gt;package.json&lt;/code&gt; file.&lt;/p&gt;

&lt;h4&gt;
  
  
  yarn bootstrap
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;CDK_NEW_BOOTSTRAP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 cdk bootstrap &lt;span class="nt"&gt;--cloudformation-execution-policies&lt;/span&gt; arn:aws:iam::aws:policy/AdministratorAccess
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deploying AWS CDK apps into an AWS environment (a combination of an AWS account and region) may require that you provision resources the AWS CDK needs to perform the deployment. These resources include an Amazon S3 bucket for storing files and IAM roles that grant permissions needed to perform deployments. The process of provisioning these initial resources is called bootstrapping. More infos &lt;a href="https://docs.aws.amazon.com/cdk/latest/guide/bootstrapping.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Basically you need to call this command &lt;strong&gt;manually&lt;/strong&gt; only once per account/region you want to deploy to.&lt;/p&gt;

&lt;h4&gt;
  
  
  yarn deploy
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cdk deploy &lt;span class="s2"&gt;"AwesomeStack-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;STAGE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--require-approval&lt;/span&gt; never &lt;span class="nt"&gt;--outputs-file&lt;/span&gt; cdk.out.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command reads from the &lt;code&gt;cdk.json&lt;/code&gt; config file and triggers the deployment of the resources. Here I specified that I don't want it to be interactive (I accept all the resources/roles creation). Also I save all the values exported with &lt;code&gt;cdk.CfnOutput&lt;/code&gt; to a json file.&lt;/p&gt;

&lt;p&gt;If you run the command multiple times, CloudFormation will diff your changes automatically and only update the resources accordingly!&lt;/p&gt;

&lt;p&gt;You can notice that in order to run this command, I expect to have the STAGE environment variable set. Also, I'm only targeting a single stack but &lt;code&gt;cdk deploy&lt;/code&gt; also accepts globs if you have multiple stacks to deploy.&lt;/p&gt;

&lt;h4&gt;
  
  
  yarn destroy
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cdk destroy &lt;span class="s2"&gt;"AwesomeStack-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;STAGE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the destruction of the stack on AWS, the &lt;code&gt;--force&lt;/code&gt; option is used to be non interactive as we'll call these commands inside our Github Actions.&lt;/p&gt;

&lt;p&gt;This concludes the infrastructure part of this post, I hope you're still there for the automation!&lt;/p&gt;

&lt;h1&gt;
  
  
  🤖 Github automation
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Github Deployments API
&lt;/h2&gt;

&lt;p&gt;Github has dedicated UIs and status integration when deploying code in a repository. It's pretty deep in their &lt;a href="https://docs.github.com/en/rest/reference/repos#deployments" rel="noopener noreferrer"&gt;REST API&lt;/a&gt; and I didn't notice a lot of adoption for this feature.&lt;/p&gt;

&lt;p&gt;I see a lot of benefits using it :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Having nice feedbacks directly in the Pull Request about the status of the current deployment.&lt;/li&gt;
&lt;li&gt;Seeing all the active deployments from the repository's home page.&lt;/li&gt;
&lt;li&gt;Automatic communication with third-party services such as &lt;a href="https://www.checklyhq.com/docs/cicd/github/" rel="noopener noreferrer"&gt;Checkly&lt;/a&gt; that need infos about the deployments in order to start their own workflow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Github as the concept of environment to group deployments together in a sequential way. In our case we will create one environment per Pull Request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Github Actions
&lt;/h2&gt;

&lt;p&gt;GitHub Actions makes it easy to automate all your software workflows, now with world-class CI/CD. Build, test, and deploy your code right from GitHub.&lt;/p&gt;

&lt;p&gt;One of the big strength of Github Actions is its ecosystem and marketplace. As someone using a lot of OSS packages on npm I feel like home, there is always a package to achieve what I want!&lt;/p&gt;

&lt;p&gt;To achieve our automation target, we will need 2 actions. One for the deployment of the stack during the lifecycle of the Pull Request, and one for cleaning everything up when the Pull Request is closed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pull Request deploy
&lt;/h3&gt;

&lt;p&gt;This first action is triggered in two cases :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When you add the label &lt;code&gt;🚀 deploy&lt;/code&gt; on the Pull Request&lt;/li&gt;
&lt;li&gt;When you open or push to a Pull Request labeled with &lt;code&gt;🚀 deploy&lt;/code&gt;. The &lt;code&gt;push&lt;/code&gt; event on a Pull Request is part of the &lt;code&gt;synchronized&lt;/code&gt; event.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first deployment will create all the resources for the environment and is often the slowest. Subsequent deployments will be way faster as CloudFormation diff the changes and only update the required resources.&lt;/p&gt;

&lt;p&gt;In order to have &lt;strong&gt;one isolated environment per Pull Request&lt;/strong&gt; we need to &lt;strong&gt;derive the STAGE environment variable from the Pull Request number and the branch name&lt;/strong&gt; so the resulting stack name is unique to our Pull Request on AWS. I made a special step for this purpose.&lt;/p&gt;

&lt;p&gt;There is a last npm script that I didn't mention earlier that eases the passage of the deployment's url from the step &lt;code&gt;deploy the stack on AWS&lt;/code&gt; to the step &lt;code&gt;update the github deployment status&lt;/code&gt;, the &lt;code&gt;postdeploy&lt;/code&gt; script :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;--eval&lt;/span&gt; &lt;span class="s2"&gt;"console.log('::set-output name=env_url::' + require('./cdk.out.json')['AwesomeStack-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;STAGE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'].DeploymentUrl)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It reads the &lt;code&gt;DeploymentUrl&lt;/code&gt; exported by the &lt;code&gt;cdk.CfnOutput&lt;/code&gt; in the &lt;code&gt;cdk.out.json&lt;/code&gt; after the deployment is over. Then it set the value as a &lt;a href="https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idoutputs" rel="noopener noreferrer"&gt;step's output&lt;/a&gt; in order to be accessible by the next steps.&lt;br&gt;
The &lt;code&gt;postdeploy&lt;/code&gt; script is automatically called after the &lt;code&gt;deploy&lt;/code&gt; script thanks to the &lt;a href="https://docs.npmjs.com/cli/v7/using-npm/scripts#pre--post-scripts" rel="noopener noreferrer"&gt;pre and post scripts convention&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Notable actions from the community which were very convenient in this workflow :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;rlespinasse/github-slug-action&lt;/code&gt; exposes the slug/short values of some GitHub environment variables inside the workflow. It allowed me to directly get a slug version of the branch name to build the STAGE environment variable. (refs/heads/feat/new_feature -&amp;gt; feat-new-feature)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aws-actions/configure-aws-credentials&lt;/code&gt; configures AWS credential and region environment variables for use in other GitHub Actions. The environment variables will be detected by all AWS tools to determine the credentials and region to use for AWS API calls.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bobheadxi/deployments&lt;/code&gt; is abstracting over the Github Deployments API and makes it a breeze to create/update/delete Github deployments.
&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pull&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Request&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;deploy"&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;pull_request&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;labeled&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;opened&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;synchronize&lt;/span&gt;&lt;span class="pi"&gt;]&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;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;(github.event.action == 'labeled' &amp;amp;&amp;amp; github.event.label.name == ':rocket: deploy') ||&lt;/span&gt;
      &lt;span class="s"&gt;(github.event.action != 'labeled' &amp;amp;&amp;amp; contains(github.event.pull_request.labels.*.name, ':rocket: deploy'))&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;inject slug/short variables&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;rlespinasse/github-slug-action@v3.x&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 STAGE variable in environment for next steps&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;echo "STAGE=pr-${{ github.event.number }}-${{ env.GITHUB_HEAD_REF_SLUG }}" &amp;gt;&amp;gt; $GITHUB_ENV&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;create a github deployment&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;bobheadxi/deployments@v0.5.2&lt;/span&gt;
        &lt;span class="na"&gt;id&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;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;start&lt;/span&gt;
          &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.STAGE }}&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;${{ github.head_ref }}&lt;/span&gt;
          &lt;span class="na"&gt;no_override&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;transient&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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 the files&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@v2&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 node dependencies&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;bahmutov/npm-install@v1&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;eu-west-3&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;deploy the stack on AWS&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cdk_deploy&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;yarn deploy&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 the github deployment status&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;bobheadxi/deployments@v0.5.2&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&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;step&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;finish&lt;/span&gt;
          &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ job.status }}&lt;/span&gt;
          &lt;span class="na"&gt;deployment_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.deployment.outputs.deployment_id }}&lt;/span&gt;
          &lt;span class="na"&gt;env_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.cdk_deploy.outputs.env_url }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pull Request clean-up
&lt;/h3&gt;

&lt;p&gt;The clean-up action is triggered in two cases :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When you remove the label &lt;code&gt;🚀 deploy&lt;/code&gt; from the Pull Request&lt;/li&gt;
&lt;li&gt;When you close the Pull Request labeled with &lt;code&gt;🚀 deploy&lt;/code&gt;. The &lt;code&gt;closed&lt;/code&gt; event is also emitted after the Pull Request is merged as it's automatically closed by Github.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This actions ensures that your preview environment is destroyed on AWS as soon as you don't need it anymore. It also delete the associated Github deployments so you don't see them in the UI anymore.&lt;/p&gt;

&lt;p&gt;Notable action from the community which was very convenient in this workflow :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;strumwolf/delete-deployment-environment&lt;/code&gt; finds and delete all deployments as well as the GitHub environment they are deployed to. We can't delete a Github environment if it contains any non &lt;code&gt;inactive&lt;/code&gt; deployment. This action marks all the deployments as inactive and delete the whole chain.
&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pull&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Request&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;clean-up"&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;pull_request&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;unlabeled&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;closed&lt;/span&gt;&lt;span class="pi"&gt;]&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;clean-up&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;(github.event.action == 'unlabeled' &amp;amp;&amp;amp; github.event.label.name == ':rocket: deploy') ||&lt;/span&gt;
      &lt;span class="s"&gt;(github.event.action == 'closed' &amp;amp;&amp;amp; contains(github.event.pull_request.labels.*.name, ':rocket: deploy'))&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;inject slug/short variables&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;rlespinasse/github-slug-action@v3.x&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 STAGE variable in environment for next steps&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;echo "STAGE=pr-${{ github.event.number }}-${{ env.GITHUB_HEAD_REF_SLUG }}" &amp;gt;&amp;gt; $GITHUB_ENV&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 the files&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@v2&lt;/span&gt;

      &lt;span class="c1"&gt;# there is a bug with the actions/cache used in bahmutov/npm-install@v1 on "closed" event&lt;/span&gt;
      &lt;span class="c1"&gt;# more infos here : https://github.com/actions/cache/issues/478&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 node 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;yarn --frozen-lockfile&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;eu-west-3&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;destroy the stack on AWS&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;yarn destroy&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;delete the github deployments and the corresponding environment&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;strumwolf/delete-deployment-environment@v1.1.0&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;token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.STAGE }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  📸 Workflow in pictures
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;🚀 deploy&lt;/code&gt; added on the Pull Request, imminent take-off&lt;br&gt;
&lt;a href="https://media.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%2Fl514vv7lgmdxrrok3jwj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fl514vv7lgmdxrrok3jwj.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Oops, something went wrong, let's fix this&lt;br&gt;
&lt;a href="https://media.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%2Fax0utzl04vwoxnycjf3i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fax0utzl04vwoxnycjf3i.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;New push, here we go again, the previous deployment is marked as Destroyed&lt;br&gt;
&lt;a href="https://media.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%2Fhmq25qq7abyw6sy8uyy1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fhmq25qq7abyw6sy8uyy1.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;YAY it worked!&lt;br&gt;
&lt;a href="https://media.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%2F63sk4vwifz061ucn6abu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F63sk4vwifz061ucn6abu.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clicking on the &lt;code&gt;View deployment&lt;/code&gt; link led me here, interesting&lt;br&gt;
&lt;a href="https://media.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%2Ft5ouao8108d41i5sxqwa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Ft5ouao8108d41i5sxqwa.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can notice the new Environments block in the bottom right corner&lt;br&gt;
&lt;a href="https://media.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%2F4h91bs5roor9ot92hvw7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F4h91bs5roor9ot92hvw7.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  🌇 Conclusion
&lt;/h1&gt;

&lt;p&gt;You now have all the building blocks to make &lt;strong&gt;your own workflow&lt;/strong&gt;, how cool is that?! The cloud is the limit now!&lt;/p&gt;

&lt;p&gt;Here are some ideas (and maybe future posts) :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using your own custom domain to have pretty preview urls like &lt;code&gt;https://pr-1-my-awesome-branch.jgoux.dev&lt;/code&gt; using AWS Route53.&lt;/li&gt;
&lt;li&gt;Going full stack and serverless, adding lambdas and a RDS database to the stack with the same previews and isolation guarantees!&lt;/li&gt;
&lt;li&gt;Generating infrastructure dynamically based on your application's code&lt;/li&gt;
&lt;li&gt;Strategies to speed up the deployment with CDK and optimizing costs by splitting your stack into an always up and shared &lt;code&gt;StableStack&lt;/code&gt; and temporaries &lt;code&gt;DynamicStack-${STAGE}&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I want to use this post to thanks all the people that took the time to answer my &lt;em&gt;numerous&lt;/em&gt; questions across Twitter, Github issues, Discord, Zoom, Slack and more recently the &lt;a href="https://cdk.dev" rel="noopener noreferrer"&gt;AWS CDK Slack community&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I can't list everyone, I ask too many questions 😂, but I want to give a special thanks to &lt;a href="https://twitter.com/hoegertn" rel="noopener noreferrer"&gt;Thorsten Hoeger&lt;/a&gt; and &lt;a href="https://twitter.com/KenWin0x539" rel="noopener noreferrer"&gt;Kenneth Winner&lt;/a&gt; for proofreading this post. I'm very grateful to be in communities of such talented, open-minded and nice people.&lt;/p&gt;

&lt;p&gt;This is my first post ever, over the years I greatly benefited from OSS and I want to give back to the community. ❤️&lt;/p&gt;

&lt;p&gt;Don't hesitate to ping me on &lt;a href="https://twitter.com/_jgx_" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; 🐦 if you want to chat about anything, my DMs are always open!&lt;/p&gt;

</description>
      <category>github</category>
      <category>preview</category>
      <category>aws</category>
      <category>cdk</category>
    </item>
  </channel>
</rss>
