<?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: David Hyppolite</title>
    <description>The latest articles on DEV Community by David Hyppolite (@dhayv).</description>
    <link>https://dev.to/dhayv</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%2F2534894%2Fdf554f0e-abf1-4f7f-a19e-8e170838cbe9.png</url>
      <title>DEV Community: David Hyppolite</title>
      <link>https://dev.to/dhayv</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dhayv"/>
    <language>en</language>
    <item>
      <title>How to Deploy a FastAPI App to Azure with Docker, ACR, and GitHub Actions</title>
      <dc:creator>David Hyppolite</dc:creator>
      <pubDate>Sat, 26 Apr 2025 05:43:29 +0000</pubDate>
      <link>https://dev.to/dhayv/how-to-deploy-a-fastapi-app-to-azure-with-docker-acr-and-github-actions-30bk</link>
      <guid>https://dev.to/dhayv/how-to-deploy-a-fastapi-app-to-azure-with-docker-acr-and-github-actions-30bk</guid>
      <description>&lt;p&gt;Shipping a containerized app to the cloud doesn’t have to be complicated.&lt;/p&gt;

&lt;p&gt;In this blog post, I’ll walk you through deploying a &lt;strong&gt;Dockerized&lt;/strong&gt; application to &lt;strong&gt;Azure&lt;/strong&gt; step-by-step from pushing your image to &lt;strong&gt;Azure Container Registry&lt;/strong&gt; (ACR) to automating your deployments with &lt;strong&gt;GitHub Actions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Whether you're building your first cloud native app or just need a quick blueprint for setting up Azure CI/CD, this walkthrough has you covered with screenshots, code snippets, and tips to avoid common pitfalls.&lt;/p&gt;

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

&lt;p&gt;Before starting, ensure you have the Azure CLI installed and you're logged in. Follow the installation guide&lt;a href="https://learn.microsoft.com/en-us/cli/azure/install-azure-cli" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A GitHub repository containing your Dockerfile &amp;amp; FastAPI code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1. Create an Azure Container Registry (ACR)
&lt;/h2&gt;

&lt;p&gt;We'll:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a Container Registry.&lt;/li&gt;
&lt;li&gt;Push our Docker image to ACR&lt;/li&gt;
&lt;li&gt;Take note of the login server, username, and password&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Create Container Registry
&lt;/h3&gt;

&lt;p&gt;In the Azure Portal, click Create a resource &amp;gt; Container Registry.&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%2Fn0pth2ezhzz8bwh2pgsh.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%2Fn0pth2ezhzz8bwh2pgsh.png" alt="createresource" width="800" height="112"&gt;&lt;/a&gt;&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%2Fsylzyy71t7b8j7navx9l.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%2Fsylzyy71t7b8j7navx9l.png" alt="createcontainer" width="800" height="617"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use these settings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Subscription:&lt;/strong&gt; Your Azure subscription&lt;br&gt;
&lt;strong&gt;Resource group:&lt;/strong&gt;  Create or use an existing one&lt;br&gt;
&lt;strong&gt;Registry name:&lt;/strong&gt; Give your container a unique name&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; Choose your region&lt;br&gt;
&lt;strong&gt;Pricing plan:&lt;/strong&gt; Basic&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%2Ffw9q2zihpyxp47g3n1g1.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%2Ffw9q2zihpyxp47g3n1g1.png" alt="networking" width="800" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Skip Networking, Encryption, and Tags.&lt;/p&gt;

&lt;p&gt;Click Create after "Validation passed" appears. &lt;/p&gt;
&lt;h3&gt;
  
  
  Push Your Image
&lt;/h3&gt;

&lt;p&gt;Your ACR deployment is now created and completed we have to push our image store in local machine (VSCode) to the remote container.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Make sure you are in the folder/path that contains the image&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once deployed:&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Go to resource&lt;/strong&gt;. &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%2F16kmfe34y2w6utwknac7.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%2F16kmfe34y2w6utwknac7.png" alt=" " width="800" height="526"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Scroll down and select &lt;strong&gt;Pushing a container image&lt;/strong&gt;&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%2F6xmejxds3ucmuyuyfleg.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%2F6xmejxds3ucmuyuyfleg.png" alt=" " width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll see instructions like these (note: Azure provides generic hello-world example commands that we'll need to modify):&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%2Fr01yjpfj1hhzotdjgaf6.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%2Fr01yjpfj1hhzotdjgaf6.png" alt=" " width="800" height="1056"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To push your FastAPI application instead of the hello-world example, use these commands modify the example commands to fit your project:&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;# Login to your Azure Container Registry&lt;/span&gt;
az acr login &lt;span class="nt"&gt;--name&lt;/span&gt; &amp;lt;acr-name&amp;gt;

&lt;span class="c"&gt;# Build your FastAPI Docker image (if not already built)&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; &amp;lt;acr-name&amp;gt; &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Tag your local image with your ACR address&lt;/span&gt;
docker tag fastapidemo myfastapidemo.azurecr.io/fastapidemo:latest

&lt;span class="c"&gt;# Push your tagged image to Azure Container Registry&lt;/span&gt;
docker push myfastapidemo.azurecr.io/fastapidemo:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Best practice: use explicit version tags (v1.0, v2025‑04‑23, etc.).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You should see an output similar to mines.&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%2Fr1zxreubispup0hco4jz.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%2Fr1zxreubispup0hco4jz.png" alt=" " width="800" height="478"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Verify your image was pushed successfully with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az acr repository list &lt;span class="nt"&gt;--name&lt;/span&gt; &amp;lt;acr-name&amp;gt; &lt;span class="nt"&gt;--output&lt;/span&gt; table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fgvpmowh2phefbl9ei8na.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%2Fgvpmowh2phefbl9ei8na.png" alt="repository-list" width="800" height="104"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also see it from the Azure portal.&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%2Fl580sgl3rtcnshv43ayb.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%2Fl580sgl3rtcnshv43ayb.png" alt="azure-portal" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2. Create an APP Service &amp;amp; Web App
&lt;/h2&gt;

&lt;p&gt;In this Section you will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create an App Service Plan (Linux)&lt;/li&gt;
&lt;li&gt;Create a Web App for Containers&lt;/li&gt;
&lt;li&gt;Link it to ACR&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;An &lt;strong&gt;AppService&lt;/strong&gt; is an HTTP-based service for hosting web applications, REST APIs, and mobile back ends.&lt;/p&gt;

&lt;p&gt;A WebApp is an AppService that focuses on hosting web applications.&lt;/p&gt;

&lt;p&gt;In Azure Portal, search &lt;strong&gt;App Services&lt;/strong&gt; &amp;gt; &lt;strong&gt;Create&lt;/strong&gt; &amp;gt; &lt;strong&gt;Web App&lt;/strong&gt;&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%2Fnlmt3yzl6lcc9vc02r2f.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%2Fnlmt3yzl6lcc9vc02r2f.png" alt="create-webapp" width="695" height="249"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Basics
&lt;/h4&gt;

&lt;p&gt;Every Field with an (* = required) must be filled&lt;/p&gt;

&lt;p&gt;Use these Settings:&lt;br&gt;
&lt;strong&gt;Resource Group:&lt;/strong&gt; Select your resource group &lt;br&gt;
&lt;strong&gt;Name:&lt;/strong&gt; Create a Unique name&lt;br&gt;
&lt;strong&gt;Publish(*):&lt;/strong&gt; Container&lt;br&gt;
&lt;strong&gt;Operating System(*):&lt;/strong&gt; Linux&lt;br&gt;
&lt;strong&gt;Region:&lt;/strong&gt; Select your region&lt;br&gt;
&lt;strong&gt;Pricing plan:&lt;/strong&gt; Free F1 (to keep cost low)&lt;/p&gt;

&lt;p&gt;Example.&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%2Ffkl5xs9pye19m4g6td32.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%2Ffkl5xs9pye19m4g6td32.png" alt="webapp-settings" width="800" height="822"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hit &lt;strong&gt;Next&lt;/strong&gt; and skip for the &lt;strong&gt;Database&lt;/strong&gt; page&lt;/p&gt;
&lt;h4&gt;
  
  
  Configure the container
&lt;/h4&gt;

&lt;p&gt;Use the settings:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Image Source:&lt;/strong&gt; Azure Container Registry&lt;br&gt;
&lt;strong&gt;Registry:&lt;/strong&gt; Select the ACR registry&lt;br&gt;
&lt;strong&gt;Authentication:&lt;/strong&gt; Managed identity&lt;br&gt;
&lt;strong&gt;Image:&lt;/strong&gt; fastapidemo (your image name)&lt;br&gt;
&lt;strong&gt;Tag:&lt;/strong&gt; latest (your image tag)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For the tag its best practice to avoid generic tags such as :latest instead use 1.O I only use latest for demonstration purposes&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%2Fi2ollsnckjh900mi7i5w.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%2Fi2ollsnckjh900mi7i5w.png" alt="webapp-container" width="739" height="636"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hit &lt;strong&gt;Next&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Networking
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Enable public access:&lt;/strong&gt; On&lt;/p&gt;

&lt;p&gt;Hit &lt;strong&gt;Next&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Monitor + secure
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Enable Application Insights:&lt;/strong&gt; No&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In production its best to turn on to track and logs your apps data&lt;br&gt;
&lt;strong&gt;Enable Defender for App Service:&lt;/strong&gt; leave blank&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hit &lt;strong&gt;Next&lt;/strong&gt; and Skip the &lt;strong&gt;Tags&lt;/strong&gt; page&lt;/p&gt;
&lt;h4&gt;
  
  
  Review + Create -&amp;gt; Create
&lt;/h4&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%2Fa8nl1hkpykptejvvs6i8.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%2Fa8nl1hkpykptejvvs6i8.png" alt="webapp-create" width="719" height="796"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should now see your basic web app all you have to do is verify your configurations now click &lt;strong&gt;Create&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you receive any failed deployments error switch to a different region and redeploy&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%2Fzv9gx5dwfzyawmrquxkk.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%2Fzv9gx5dwfzyawmrquxkk.png" alt="created-webapp" width="444" height="414"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Access &amp;amp; Verify Web App
&lt;/h4&gt;

&lt;p&gt;On success, select &lt;strong&gt;Go to resource&lt;/strong&gt; and open the &lt;strong&gt;Default Domain&lt;/strong&gt; to confirm the app loads.&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%2Fd8gknc5u8l3wfiv76xl1.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%2Fd8gknc5u8l3wfiv76xl1.png" alt="default-domain" width="797" height="548"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you set everything up right you should see your containers page.&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%2Fvvrcea4ek8nacd9y0v52.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%2Fvvrcea4ek8nacd9y0v52.png" alt="container-page" width="800" height="543"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have any issues on the resource page under &lt;strong&gt;Deployment Center&lt;/strong&gt; &amp;gt; &lt;strong&gt;View logs&lt;/strong&gt; you will see where your deployment failed.&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%2Fldrhvgol60j0bhbdb10j.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%2Fldrhvgol60j0bhbdb10j.png" alt="deployment-log" width="800" height="85"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 3. Set Up GitHub Actions CI/CD
&lt;/h3&gt;

&lt;p&gt;We will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enable Deployment Center&lt;/li&gt;
&lt;li&gt;Generate a workflow file&lt;/li&gt;
&lt;li&gt;Add GitHub secrets&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;
  
  
  Enable deployment Center
&lt;/h4&gt;

&lt;p&gt;In your Web App portal on the left side select &lt;strong&gt;Deployment&lt;/strong&gt; &amp;gt; &lt;strong&gt;Deployment Center&lt;/strong&gt;&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%2Fn6ggbw146egpnouofdt1.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%2Fn6ggbw146egpnouofdt1.png" alt=" " width="262" height="99"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you see the red "SCM basic authentication is disabled for your app. Click here to go to your configuration settings to enable." it needs to be enabled.&lt;/p&gt;

&lt;p&gt;Click it and under &lt;strong&gt;Platform settings&lt;/strong&gt; toggle &lt;strong&gt;SCM Basic Auth Publishing&lt;/strong&gt;: On &lt;/p&gt;

&lt;p&gt;Save the changes above and continue to update the app.&lt;/p&gt;
&lt;h4&gt;
  
  
  Connect to GitHub
&lt;/h4&gt;

&lt;p&gt;Source: &lt;strong&gt;Github Actions&lt;/strong&gt;&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%2Fny2q4qjv707muhg8ujtp.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%2Fny2q4qjv707muhg8ujtp.png" alt=" " width="800" height="133"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Authorize GitHub, select repo + branch once completed fill in your repos details.&lt;/p&gt;

&lt;p&gt;The rest of your &lt;strong&gt;Registry settings&lt;/strong&gt; should auto‑populate (note the managed identity it shows).&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%2Fuzj5lyvlzdsp6n4tf5c0.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%2Fuzj5lyvlzdsp6n4tf5c0.png" alt=" " width="800" height="594"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In a new tab access your container registry.&lt;/p&gt;

&lt;p&gt;Azure Portal &amp;gt; your ACR &amp;gt; Settings &amp;gt; Identity &amp;gt; User Assigned.&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%2Fydsh020setxewvka35l0.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%2Fydsh020setxewvka35l0.png" alt=" " width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add&lt;/strong&gt; the user assigned managed identity you have listed  in Deployment Center.&lt;/p&gt;

&lt;p&gt;Now head on back to your deployment center &lt;/p&gt;

&lt;p&gt;Hit the &lt;strong&gt;Save&lt;/strong&gt; button up above&lt;/p&gt;

&lt;p&gt;Our build will fail we will resolve this issue in the next.&lt;/p&gt;
&lt;h4&gt;
  
  
  ✏️ Fix the generated workflow
&lt;/h4&gt;

&lt;p&gt;Head over to your GitHub repo — you'll see a new folder named &lt;strong&gt;&lt;code&gt;.github/workflows&lt;/code&gt;&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
This was automatically created by Azure Deployment Center when you enabled CI/CD.&lt;/p&gt;

&lt;p&gt;Open the &lt;code&gt;.yml&lt;/code&gt; file inside (e.g., &lt;code&gt;azure-webapps.yml&lt;/code&gt;) — and click the ✏️ &lt;strong&gt;pencil icon&lt;/strong&gt; to edit it directly in GitHub.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The code is a general-purpose template — we’ll customize it to match our actual Azure setup.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Replace the Docker login block with Azure‑based auth:&lt;/p&gt;


&lt;h3&gt;
  
  
  🐳 &lt;strong&gt;Original Build &amp;amp; Login Block:&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;You'll see something 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;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&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;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&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;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;Set up Docker Buildx&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;docker/setup-buildx-action@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;Log in to registry&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;docker/login-action@v2&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;registry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://myfastapidemo.azurecr.io/&lt;/span&gt;
        &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AzureAppService_ContainerUsername_dac00c4d6aa44559aceb4c95969103fb }}&lt;/span&gt;
        &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AzureAppService_ContainerPassword_acd4866e025148088786c136581277b5 }}&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 and push container image to registry&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;docker/build-push-action@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;push&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;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myfastapidemo.azurecr.io/${{ secrets.AzureAppService_ContainerUsername_dac00c4d6aa44559aceb4c95969103fb }}/fastapidemo:${{ github.sha }}&lt;/span&gt;
        &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./Dockerfile&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This login method assumes credentials for Docker Hub or a public registry — but we’re using Azure Container Registry (ACR) with Azure credentials.&lt;/p&gt;

&lt;p&gt;✅ Replace It With:&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;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&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;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&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;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;Set up Docker Buildx&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;docker/setup-buildx-action@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;Azure Login&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;azure/login@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;creds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZURE_CREDENTIALS }}&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Build&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;push&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;image'&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;azure/docker-login@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;login-server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.REGISTRY_LOGIN_SERVER }}&lt;/span&gt;
        &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.REGISTRY_USERNAME }}&lt;/span&gt;
        &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.REGISTRY_PASSWORD }}&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;docker build . -t ${{ secrets.REGISTRY_LOGIN_SERVER }}/&amp;lt;youracr&amp;gt;:${{ github.sha }}&lt;/span&gt;
        &lt;span class="s"&gt;docker push ${{ secrets.REGISTRY_LOGIN_SERVER }}//&amp;lt;youracr&amp;gt;:${{ github.sha }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;This uses the Azure CLI to securely authenticate to ACR using a service principal instead of Docker username/password.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Add GitHub secrets
&lt;/h2&gt;

&lt;p&gt;In this section we will be setting the AZURE_CREDENTIALS secret.&lt;/p&gt;

&lt;p&gt;Open your terminal and run the following commands&lt;/p&gt;

&lt;p&gt;This command shows all your active resource groups look for you resource group related to your ACR/webapp. Copy the resource group "id".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az group list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a service principal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az ad sp create-for-rbac &lt;span class="nt"&gt;--scopes&lt;/span&gt; &amp;lt;resource‑group‑id&amp;gt; &lt;span class="nt"&gt;--role&lt;/span&gt; Contributor &lt;span class="nt"&gt;--sdk-auth&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az ad sp create-for-rbac &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--scopes&lt;/span&gt; /subscriptions/88888839283023/resourceGroups/FastApI &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--role&lt;/span&gt; Contributor &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--sdk-auth&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output will look similar to:&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="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"clientId"&lt;/span&gt;: &lt;span class="s2"&gt;"xxxx6ddc-xxxx-xxxx-xxx-ef78a99dxxxx"&lt;/span&gt;,
  &lt;span class="s2"&gt;"clientSecret"&lt;/span&gt;: &lt;span class="s2"&gt;"xxxx79dc-xxxx-xxxx-xxxx-aaaaaec5xxxx"&lt;/span&gt;,
  &lt;span class="s2"&gt;"subscriptionId"&lt;/span&gt;: &lt;span class="s2"&gt;"aaaa0a0a-bb1b-cc2c-dd3d-eeeeee4e4e4e"&lt;/span&gt;,
  &lt;span class="s2"&gt;"tenantId"&lt;/span&gt;: &lt;span class="s2"&gt;"aaaabbbb-0000-cccc-1111-dddd2222eeee"&lt;/span&gt;,
  &lt;span class="s2"&gt;"activeDirectoryEndpointUrl"&lt;/span&gt;: &lt;span class="s2"&gt;"https://login.microsoftonline.com"&lt;/span&gt;,
  &lt;span class="s2"&gt;"resourceManagerEndpointUrl"&lt;/span&gt;: &lt;span class="s2"&gt;"https://management.azure.com/"&lt;/span&gt;,
  &lt;span class="s2"&gt;"activeDirectoryGraphResourceId"&lt;/span&gt;: &lt;span class="s2"&gt;"https://graph.windows.net/"&lt;/span&gt;,
  &lt;span class="s2"&gt;"sqlManagementEndpointUrl"&lt;/span&gt;: &lt;span class="s2"&gt;"https://management.core.windows.net:8443/"&lt;/span&gt;,
  &lt;span class="s2"&gt;"galleryEndpointUrl"&lt;/span&gt;: &lt;span class="s2"&gt;"https://gallery.azure.com/"&lt;/span&gt;,
  &lt;span class="s2"&gt;"managementEndpointUrl"&lt;/span&gt;: &lt;span class="s2"&gt;"https://management.core.windows.net/"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the JSON output because it's used in a later step.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Take note of the clientId, which you need to update the service principal in the next section.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now we need to update the service principal to allow ACR to push and pull&lt;/p&gt;

&lt;p&gt;Lets get our registry name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az acr list &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"[].{Name:name}"&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Get the resource ID of your container registry. Replace &lt;strong&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;/strong&gt;.&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="nv"&gt;registryId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;az acr show &lt;span class="nt"&gt;--name&lt;/span&gt; &amp;lt;registry-name&amp;gt; &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &amp;lt;resource-group-name&amp;gt; &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; tsv&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assign &lt;strong&gt;AcrPush&lt;/strong&gt; to the service principal, which gives push and pull access to the registry. Substitute the client ID of your service principal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az role assignment create &lt;span class="nt"&gt;--assignee&lt;/span&gt; &amp;lt;ClientId&amp;gt; &lt;span class="nt"&gt;--scope&lt;/span&gt; &lt;span class="nv"&gt;$registryId&lt;/span&gt; &lt;span class="nt"&gt;--role&lt;/span&gt; AcrPush
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Save the credentials to your GitHub repo, go to your repository Settings under Security &amp;gt; Secrets and variables &amp;gt; Actions &amp;gt; New repository secret.
&lt;/h4&gt;

&lt;p&gt;Add the following secrets:&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%2F1urkdubfxwpzpxq807z9.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%2F1urkdubfxwpzpxq807z9.png" alt=" " width="800" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AZURE_CREDENTIALS&lt;/strong&gt; paste full JSON including "{}"&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For enterprise-grade setups, consider using Workload Identity Federation or certificate-based service principals to avoid storing long-term secrets.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Test the Pipeline
&lt;/h3&gt;

&lt;p&gt;Lets make a change to our code to see if any changes made in our code will build and deploy and show on our Azure app service.&lt;/p&gt;

&lt;p&gt;Any changes to the repo will automatically trigger our GitHub action to Build and deploy to Azure.&lt;/p&gt;

&lt;p&gt;For me I will be making the changes to my fastapi code from &lt;strong&gt;{"Welcome": "To Azure"}&lt;/strong&gt; to {"Novice": "To Azure"} and commit the code.&lt;/p&gt;

&lt;p&gt;After you have made the changes wait for your actions to deploy than check back to you app service domain.&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%2Fvz5w87ehb0qy697w88hm.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%2Fvz5w87ehb0qy697w88hm.png" alt="novice" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see the change have been reflected Automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleanup  resources
&lt;/h2&gt;

&lt;p&gt;Now that we have deployed everything we need to cleanup the resources to not accrue charges.&lt;/p&gt;

&lt;p&gt;Delete the resource group to stop charges:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az group delete &lt;span class="nt"&gt;--name&lt;/span&gt; ExampleResourceGroup &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;strong&gt;ExampleResourceGroup&lt;/strong&gt; with your resource group name you can locate your resource groups using&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az group list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or remove it via Azure Portal.&lt;/p&gt;

&lt;p&gt;Search for &lt;strong&gt;Resource groups&lt;/strong&gt; in the azure portal and locate the resource group you create and select it and just hit &lt;strong&gt;Delete resource group&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;If you followed along, you now have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Docker image safely stored in Azure Container Registry (ACR)&lt;/li&gt;
&lt;li&gt;A FastAPI web app running live on Azure App Service&lt;/li&gt;
&lt;li&gt;An end‑to‑end CI/CD pipeline powered by GitHub Actions that redeploys on every push&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Deploying Grafana on an AWS EC2 Instance(2025)</title>
      <dc:creator>David Hyppolite</dc:creator>
      <pubDate>Wed, 05 Mar 2025 00:51:54 +0000</pubDate>
      <link>https://dev.to/dhayv/deploying-grafana-on-an-aws-ec2-instance2025-3li8</link>
      <guid>https://dev.to/dhayv/deploying-grafana-on-an-aws-ec2-instance2025-3li8</guid>
      <description>&lt;p&gt;This blog post walks you through setting up &lt;strong&gt;Grafana&lt;/strong&gt; on an &lt;strong&gt;AWS EC2&lt;/strong&gt; instance to visualize real-time performance metrics and run stress tests, all while being secured by using Instance Connect to bypass the need for SSH.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;Cloud infrastructure issues often go undetected until they cause downtime costing businesses thousands per minute and putting immense pressure on engineering teams.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;This tutorial demonstrates how to implement proactive monitoring with Grafana on AWS EC2. You’ll learn to deploy an EC2 instance, attach the necessary IAM role, integrate CloudWatch, and configure Grafana to visualize performance metrics in real time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;Mastering this setup enables you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configure IAM roles effectively.&lt;/li&gt;
&lt;li&gt;Deploy and secure EC2 instances using Instance Connect.&lt;/li&gt;
&lt;li&gt;Integrate AWS CloudWatch for comprehensive monitoring.&lt;/li&gt;
&lt;li&gt;Create dynamic Grafana dashboards for real-time performance visualization.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What You'll Learn
&lt;/h2&gt;

&lt;p&gt;By following this guide, you will achieve a complete AWS monitoring setup that not only demonstrates your infrastructure deployment skills but also provides visual proof of your ability to monitor and manage real-time performance metrics effectively.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools Used
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWS Console:&lt;/strong&gt; For managing EC2 and IAM roles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grafana:&lt;/strong&gt; For visualization, dashboard creation, and alerting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon Linux 2:&lt;/strong&gt; As the operating system for the EC2 instance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudWatch:&lt;/strong&gt; For monitoring EC2 metrics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stress:&lt;/strong&gt; Command-line tool to simulate load on the instance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instance Connect:&lt;/strong&gt; For connecting to the EC2 instance without SSH.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1 Create your IAM role
&lt;/h3&gt;

&lt;p&gt;In the AWS Console, navigate to &lt;strong&gt;Roles&lt;/strong&gt; and click &lt;strong&gt;Create Role&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Select Trusted Entity:&lt;/strong&gt; AWS service&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Service Use Case:&lt;/strong&gt; EC2&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Next&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Attach the permission policy: &lt;strong&gt;AmazonGrafanaCloudWatchAccess&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Next&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Provide a unique name for your role.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Create Role&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2 Launch your instance
&lt;/h3&gt;

&lt;p&gt;Launch and install Grafana on an EC2 instance.&lt;/p&gt;

&lt;p&gt;In the AWS Console, search for &lt;strong&gt;EC2&lt;/strong&gt; and click &lt;strong&gt;Launch Instance&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Configure your instance with these settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name:&lt;/strong&gt; Choose a unique name&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AMI&lt;/strong&gt;: Amazon Linux 2&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instance type&lt;/strong&gt;: t2.micro&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Key pair (login):&lt;/strong&gt; Proceed without a key pair.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Network settings
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Firewall (security groups):&lt;/strong&gt; Create a new security group.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Allow SSH traffic from:&lt;/strong&gt; &lt;code&gt;com.amazonaws.us-east-1.ec2-instance-connect&lt;/code&gt; (replace &lt;code&gt;us-east-1&lt;/code&gt; with your region)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Allow HTTP traffic from the internet&lt;/strong&gt;: Checked&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;replace &lt;em&gt;us-east-1&lt;/em&gt; with your region This will allow us to instance connect.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;
  
  
  Advanced details
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;IAM instance profile:&lt;/strong&gt;  Attach the IAM role you created earlier. This is crucial for granting the EC2 instance the necessary permissions to access CloudWatch via Grafana.&lt;/p&gt;
&lt;h5&gt;
  
  
  User Data
&lt;/h5&gt;

&lt;p&gt;Paste the following script to install and start Grafana:&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;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="c"&gt;# Install Grafana&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; https://dl.grafana.com/enterprise/release/grafana-enterprise-11.5.2-1.x86_64.rpm

&lt;span class="c"&gt;# Enable/Start Grafana&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;grafana-server
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start grafana-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Click &lt;strong&gt;Launch instance&lt;/strong&gt; to create your EC2 instance.&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%2Flfabud4o1zmn6xuava8r.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%2Flfabud4o1zmn6xuava8r.png" alt="Instance-connect" width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Connect to your instance using &lt;strong&gt;Instance Connect&lt;/strong&gt; (instead of SSH).&lt;/p&gt;

&lt;p&gt;To verify the installation run:&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;sudo &lt;/span&gt;systemctl status grafana-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see Grafana running, similar to the provided screenshot.&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%2Ftyt3ba08p3irx321benc.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%2Ftyt3ba08p3irx321benc.png" alt="grafana check" width="800" height="246"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 Connect to Grafana UI
&lt;/h2&gt;

&lt;p&gt;Gain Access to the security group of your EC2 instance.&lt;/p&gt;

&lt;p&gt;Edit the inbound rules of your instance’s security group&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Type:&lt;/strong&gt; Custom TCP&lt;br&gt;
&lt;strong&gt;Port range:&lt;/strong&gt; 3000&lt;br&gt;
&lt;strong&gt;Source:&lt;/strong&gt; Custom&lt;br&gt;
&lt;strong&gt;CIDR Block:&lt;/strong&gt; 0.0.0.0/0&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%2F4drsgeqd8208du7fkg4f.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%2F4drsgeqd8208du7fkg4f.png" alt="grafana-port" width="800" height="214"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Access Grafana by copying your instance's public IP and appending port 3000 (e.g., 54.164.96.183:3000).&lt;/p&gt;

&lt;p&gt;You should see a web page 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%2F768quaw7d3kajys7z721.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%2F768quaw7d3kajys7z721.png" alt="grafana-page" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Log in using the default credentials:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;email: admin
password: admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can skip the password change prompt if desired.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 Configure a data source
&lt;/h2&gt;

&lt;p&gt;Grafana needs to be provided a data source to fetch and display data.&lt;/p&gt;

&lt;p&gt;In Grafana, navigate to the &lt;strong&gt;Data Sources&lt;/strong&gt;:&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%2Fbm3avfz7by07wi19n1tf.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%2Fbm3avfz7by07wi19n1tf.png" alt="grafana-homepage" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select &lt;strong&gt;CloudWatch&lt;/strong&gt;&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%2Fp3hxxpkhgiy1x8ap3u3a.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%2Fp3hxxpkhgiy1x8ap3u3a.png" alt="aws-cloudwatch" width="800" height="204"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use these settings:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authentication Provider:&lt;/strong&gt; AWS SDK default&lt;br&gt;
&lt;strong&gt;Default Region:&lt;/strong&gt; us-east-1&lt;br&gt;
&lt;strong&gt;Namespaces of Custom Metrics&lt;/strong&gt; ec2-monitoring&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Save &amp;amp; test&lt;/strong&gt;. You should see a confirmation message indicating that the queries to the CloudWatch metrics and logs APIs were successful.&lt;/p&gt;
&lt;h3&gt;
  
  
  Build a Dashboard
&lt;/h3&gt;

&lt;p&gt;On the success page, click &lt;strong&gt;Build a dashboard&lt;/strong&gt; &amp;gt; &lt;strong&gt;Add visualization&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Select your CloudWatch data source.&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%2Fwm757lbt0pq20zashpdw.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%2Fwm757lbt0pq20zashpdw.png" alt="add-visualization" width="800" height="262"&gt;&lt;/a&gt;&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%2Fpg77robki33mz39omxmx.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%2Fpg77robki33mz39omxmx.png" alt="dashboard-source" width="800" height="279"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the moment "No data" will be shown.&lt;/p&gt;

&lt;p&gt;Delete the default query.&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%2Fl7h869utdytyi465wc4p.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%2Fl7h869utdytyi465wc4p.png" alt="default-query" width="800" height="633"&gt;&lt;/a&gt;&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%2F96fmluhc2rsomhcqyi6t.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%2F96fmluhc2rsomhcqyi6t.png" alt="add-query" width="604" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add a new query with these parameters:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Namespace:&lt;/strong&gt; AWS/EC2&lt;br&gt;
&lt;strong&gt;Metric name:&lt;/strong&gt; CPUUtilization&lt;br&gt;
&lt;strong&gt;Statistic:&lt;/strong&gt; Average&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;add dimension&lt;br&gt;
&lt;strong&gt;Dimensions:&lt;/strong&gt; InstanceId (select your Grafana instance's ID)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Click &lt;strong&gt;Run Queries&lt;/strong&gt; to view your data.&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%2Fh1dqtzyiatv5sfkm1tjd.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%2Fh1dqtzyiatv5sfkm1tjd.png" alt="query-button" width="800" height="206"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Congratultions! You should now see your first queried Data from your EC2 instance.&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%2Fwig0s9fpusvqczfwbq1w.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%2Fwig0s9fpusvqczfwbq1w.png" alt="grafana-data" width="800" height="238"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 5 Stress test the EC2
&lt;/h2&gt;

&lt;p&gt;We can stress test our EC2 to see check the results on Grafana.&lt;/p&gt;

&lt;p&gt;Reconnect to your EC2 instance using &lt;strong&gt;Instance Connect&lt;/strong&gt; if necessary.&lt;/p&gt;

&lt;p&gt;Install the stress testing tool:&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;sudo &lt;/span&gt;amazon-linux-extras &lt;span class="nb"&gt;install &lt;/span&gt;epel &lt;span class="nt"&gt;-y&lt;/span&gt;

&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install &lt;/span&gt;stress &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the stress test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
stress &lt;span class="nt"&gt;--cpu&lt;/span&gt; 4 &lt;span class="nt"&gt;--timeout&lt;/span&gt; 120s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fe0c10dihepthw78at68g.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%2Fe0c10dihepthw78at68g.png" alt="stress-test" width="800" height="238"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the test completes, check your Grafana dashboard to see the updated metrics.&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%2Fokv6g3gnxiatink8i8b4.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%2Fokv6g3gnxiatink8i8b4.png" alt="stess-visual" width="800" height="238"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;By following these steps, you've successfully deployed Grafana on an EC2 instance, attached the necessary IAM role, and set up monitoring for CPU utilization using CloudWatch. This guide demonstrates a straightforward approach to achieving real-time monitoring with AWS and Grafana, providing essential insights for system performance analysis and stress testing.&lt;/p&gt;

&lt;p&gt;Happy monitoring!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Securely Deploy a EKS Cluster in a Private AWS VPC with Terraform &amp; AWS SSM (No Bastion Hosts or SSH Keys Required)</title>
      <dc:creator>David Hyppolite</dc:creator>
      <pubDate>Fri, 14 Feb 2025 19:30:52 +0000</pubDate>
      <link>https://dev.to/dhayv/managing-kubernetes-in-a-private-aws-vpc-a-secure-and-automated-approach-4n4c</link>
      <guid>https://dev.to/dhayv/managing-kubernetes-in-a-private-aws-vpc-a-secure-and-automated-approach-4n4c</guid>
      <description>&lt;p&gt;In this guide, I demonstrate how to deploy a &lt;strong&gt;production-grade Amazon EKS cluster&lt;/strong&gt; within a &lt;strong&gt;private AWS VPC&lt;/strong&gt; using &lt;strong&gt;Terraform&lt;/strong&gt; and &lt;strong&gt;AWS Systems Manager (SSM)&lt;/strong&gt;. This approach enhances security by eliminating the need for SSH keys and bastion hosts, showcasing expertise in &lt;strong&gt;infrastructure as code&lt;/strong&gt;, &lt;strong&gt;cloud security&lt;/strong&gt;, and &lt;strong&gt;Kubernetes orchestration&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What You'll Learn
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deploy a production-grade EKS cluster&lt;/strong&gt; in a private VPC&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implement secure access&lt;/strong&gt; using AWS Systems Manager&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automate infrastructure&lt;/strong&gt; with Terraform&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configure networking and security&lt;/strong&gt; best practices&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deploy a sample FastAPI application&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Table of Contents
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;
Introduction

&lt;ul&gt;
&lt;li&gt;Why This Approach?&lt;/li&gt;
&lt;li&gt;Critical Access Configuration&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Infrastructure Setup and Configuration

&lt;ul&gt;
&lt;li&gt;Step 1: GitHub Repository&lt;/li&gt;
&lt;li&gt;Step 2: IAM Role Configuration&lt;/li&gt;
&lt;li&gt;Step 3: Create ECR Repository&lt;/li&gt;
&lt;li&gt;Step 4: Infrastructure Deployment&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Deployment and Access Configuration

&lt;ul&gt;
&lt;li&gt;Step 6: Creating the EC2 Instance&lt;/li&gt;
&lt;li&gt;Kubernetes Manifests and Deployment&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Resource Cleanup&lt;/li&gt;

&lt;li&gt;Optional: Implement CI/CD with GitHub Actions&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Technologies &amp;amp; Skills
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AWS EKS&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AWS Systems Manager (SSM)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Terraform&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FastAPI&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Amazon ECR&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Infrastructure as Code (IaC)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloud Security&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kubernetes Orchestration&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Basic understanding of AWS networking concepts (VPCs, subnets, routing)&lt;/li&gt;
&lt;li&gt;AWS Account with appropriate permissions&lt;/li&gt;
&lt;li&gt;AWS CLI installed and configured&lt;/li&gt;
&lt;li&gt;kubectl installed&lt;/li&gt;
&lt;li&gt;Terraform installed&lt;/li&gt;
&lt;li&gt;Docker installed&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Deploying an &lt;strong&gt;EKS cluster in a private VPC&lt;/strong&gt; ensures that your Kubernetes API server is not exposed to the public internet, enhancing security. However, this setup introduces challenges in managing access and automating infrastructure provisioning. By leveraging &lt;strong&gt;Terraform&lt;/strong&gt; and &lt;strong&gt;AWS SSM&lt;/strong&gt;, you can automate the entire deployment process, manage IAM roles effectively, and securely access your cluster without exposing it publicly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Approach?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced Security:&lt;/strong&gt; Deploy EKS clusters in private subnets without exposing SSH ports&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplified Access:&lt;/strong&gt; Use IAM roles instead of managing SSH keys&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost Effective:&lt;/strong&gt; Eliminate the need for dedicated bastion hosts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation Ready:&lt;/strong&gt; Infrastructure as Code with Terraform&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production Grade:&lt;/strong&gt; Suitable for enterprise environments&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Critical Access Configuration ⚠️
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Two key points about access management:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The IAM role &lt;code&gt;EC2RoleForSSM_EKS&lt;/code&gt; (or similar) must exist and have the necessary permissions for both SSM and EKS interactions. Without this, you will be unable to access the cluster from within the private VPC.&lt;/li&gt;
&lt;li&gt;When an EKS cluster is created, only the user/role that creates the cluster (e.g., Terraform) automatically receives &lt;code&gt;system:master&lt;/code&gt; permissions. All other users or roles requiring cluster access must be explicitly added to the cluster's RBAC configuration.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Missing either of these configurations will prevent access to resources in the private VPC, whether through SSM or via the CLI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Takeaways
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mastered deploying EKS in a secure, private environment&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Implemented infrastructure automation with Terraform&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enhanced security using AWS Systems Manager (SSM)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Demonstrated proficiency in Kubernetes and cloud networking&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Infrastructure Setup and Configuration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: GitHub Repository
&lt;/h3&gt;

&lt;p&gt;Start by organizing your project repository to manage your FastAPI application, Terraform configurations, and deployment scripts.&lt;/p&gt;

&lt;p&gt;You can use your own repository or follow along with mine:&lt;/p&gt;

&lt;p&gt;Fork this repository: &lt;a href="https://github.com/dhayv/aws-kubernetes-deploy" rel="noopener noreferrer"&gt;Github&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Repo Contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
Repository Structure:
├── main.py        &lt;span class="c"&gt;# FastApi application&lt;/span&gt;
├── Dockerfile        &lt;span class="c"&gt;# Docker&lt;/span&gt;
├── terraform/         &lt;span class="c"&gt;# terraform module&lt;/span&gt;
├── fastapi-deploy.yaml           &lt;span class="c"&gt;# service scripts&lt;/span&gt;
├── fastapi-service.yaml           &lt;span class="c"&gt;# service scripts&lt;/span&gt;
└── requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: IAM Role Configuration
&lt;/h3&gt;

&lt;p&gt;First, we'll create the necessary IAM role for Systems Manager access:&lt;/p&gt;

&lt;p&gt;In the AWS console search up &lt;strong&gt;Roles&lt;/strong&gt; &amp;gt; &lt;strong&gt;Create role&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trusted Entity:&lt;/strong&gt; EC2 &amp;gt; EC2 use case.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Next&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add Permissions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AmazonSSMManagedInstanceCore&lt;/li&gt;
&lt;li&gt;AmazonEKSWorkerNodePolicy&lt;/li&gt;
&lt;li&gt;AmazonEKSServicePolicy&lt;/li&gt;
&lt;li&gt;AmazonEKSClusterPolicy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Click &lt;strong&gt;Next&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Provide a name for the role.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EC2RoleForSSM_EKS
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Review your settings then click &lt;strong&gt;Create role&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Create ECR repository
&lt;/h3&gt;

&lt;p&gt;To store container image for the FastAPI application, we'll be using Amazon Elastic Container Registry(ECR). While the infrastructure for VPC and EKS will be automated with Terraform, we'll create the ECR repository manually via the AWS console.&lt;/p&gt;

&lt;p&gt;In the AWS Console search &lt;strong&gt;ECR&lt;/strong&gt; &amp;gt; &lt;strong&gt;Create&lt;/strong&gt;&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%2Fkf1lq25fuzmjafwz8y80.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%2Fkf1lq25fuzmjafwz8y80.png" alt="create-ecr" width="800" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set the repository name: fastapi-app&lt;/li&gt;
&lt;li&gt;Leave the default settings&lt;/li&gt;
&lt;li&gt;click &lt;strong&gt;Create&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;View Repository Commands:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;After creation select the repo&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;View push commands&lt;/strong&gt; to get the commands to build and push your Docker image to ECR. Follow the commands.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;blockquote&gt;
&lt;p&gt;Note: These command are what you use to build and push your local Docker image to your ECR container to hold.&lt;/p&gt;
&lt;/blockquote&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;The ECR repo stores the Docker image, which will later be pulled by the EKS cluster during deployment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once your image is pushed let's move on to setting up our VPC and EKS using Terraform.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Infrastructure Deployment
&lt;/h3&gt;

&lt;p&gt;Automate the creation of a private VPC, EKS cluster, and node groups using Terraform.&lt;/p&gt;

&lt;p&gt;The Terraform configuration creates three key modules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;VPC Module:&lt;/strong&gt; Provisions a private VPC with public and private subnets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Group Module:&lt;/strong&gt; Configures security groups for ALB and EKS communication.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EKS Module:&lt;/strong&gt; Deploys the EKS cluster and worker nodes in private subnets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below are snippets for the complete Terraform configuration. The full code can be found in the GitHub repository: &lt;a href="https://github.com/dhayv/aws-kubernetes-deploy/tree/main/terraform/modules" rel="noopener noreferrer"&gt;Repo&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Set up the VPC
&lt;/h4&gt;

&lt;p&gt;First, we'll set up our VPC with public and private subnets. The public subnets will host our NAT Gateway, while private subnets will contain our EKS nodes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Terraform Configuration:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;data &lt;span class="s2"&gt;"aws_availability_zones"&lt;/span&gt; &lt;span class="s2"&gt;"available"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  state &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"available"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Create vpc &lt;/span&gt;
resource &lt;span class="s2"&gt;"aws_vpc"&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    cidr_block &lt;span class="o"&gt;=&lt;/span&gt; var.vpc_cidr
    enable_dns_support &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true
    &lt;/span&gt;enable_dns_hostnames &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true

    &lt;/span&gt;tags &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      Name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.project_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-vpc"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Create public subnets&lt;/span&gt;
resource &lt;span class="s2"&gt;"aws_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"public"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  count &lt;span class="o"&gt;=&lt;/span&gt; length&lt;span class="o"&gt;(&lt;/span&gt;var.azs&lt;span class="o"&gt;)&lt;/span&gt;      
  vpc_id     &lt;span class="o"&gt;=&lt;/span&gt; aws_vpc.main.id
  cidr_block &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.index + 1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.0/24"&lt;/span&gt;
  availability_zone &lt;span class="o"&gt;=&lt;/span&gt; var.azs[count.index]

  map_public_ip_on_launch &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true

  &lt;/span&gt;tags &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    Name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.project_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-public-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.index + 1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# internet gateway for public subnets&lt;/span&gt;
resource &lt;span class="s2"&gt;"aws_internet_gateway"&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    vpc_id &lt;span class="o"&gt;=&lt;/span&gt; aws_vpc.main.id

    tags &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        Name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.project_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-igw"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# route table for public subnets&lt;/span&gt;
resource &lt;span class="s2"&gt;"aws_route_table"&lt;/span&gt; &lt;span class="s2"&gt;"public"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  vpc_id &lt;span class="o"&gt;=&lt;/span&gt; aws_vpc.main.id

  route &lt;span class="o"&gt;{&lt;/span&gt;
    cidr_block &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;
    gateway_id &lt;span class="o"&gt;=&lt;/span&gt; aws_internet_gateway.main.id
  &lt;span class="o"&gt;}&lt;/span&gt;

  tags &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    Name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.project_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-public-rt"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# &lt;/span&gt;
resource &lt;span class="s2"&gt;"aws_route_table_association"&lt;/span&gt; &lt;span class="s2"&gt;"public"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  count &lt;span class="o"&gt;=&lt;/span&gt; length&lt;span class="o"&gt;(&lt;/span&gt;var.azs&lt;span class="o"&gt;)&lt;/span&gt;  
  subnet_id      &lt;span class="o"&gt;=&lt;/span&gt; aws_subnet.public[count.index].id
  route_table_id &lt;span class="o"&gt;=&lt;/span&gt; aws_route_table.public.id
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Private subnets
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
&lt;span class="c"&gt;# create private subnets&lt;/span&gt;
resource &lt;span class="s2"&gt;"aws_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"private"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  count &lt;span class="o"&gt;=&lt;/span&gt; length&lt;span class="o"&gt;(&lt;/span&gt;var.azs&lt;span class="o"&gt;)&lt;/span&gt;      
  vpc_id     &lt;span class="o"&gt;=&lt;/span&gt; aws_vpc.main.id
  cidr_block &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.index + 10&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.0/24"&lt;/span&gt;
  availability_zone &lt;span class="o"&gt;=&lt;/span&gt; var.azs[count.index]

  tags &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    Name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.project_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-private-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.index + 1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

resource &lt;span class="s2"&gt;"aws_eip"&lt;/span&gt; &lt;span class="s2"&gt;"nat"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  domain &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"vpc"&lt;/span&gt;

  tags &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    Name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.project_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-nat-eip"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

resource &lt;span class="s2"&gt;"aws_nat_gateway"&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  allocation_id &lt;span class="o"&gt;=&lt;/span&gt; aws_eip.nat.id
  subnet_id &lt;span class="o"&gt;=&lt;/span&gt; aws_subnet.public[0].id

  tags &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    Name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.project_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-ngw"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    depends_on &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; aws_internet_gateway.main &lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# route table for private subnets&lt;/span&gt;
resource &lt;span class="s2"&gt;"aws_route_table"&lt;/span&gt; &lt;span class="s2"&gt;"private"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  vpc_id &lt;span class="o"&gt;=&lt;/span&gt; aws_vpc.main.id

  route &lt;span class="o"&gt;{&lt;/span&gt;
    cidr_block &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;
    nat_gateway_id &lt;span class="o"&gt;=&lt;/span&gt; aws_nat_gateway.main.id
  &lt;span class="o"&gt;}&lt;/span&gt;

  tags &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    Name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.project_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-private-rt"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

resource &lt;span class="s2"&gt;"aws_route_table_association"&lt;/span&gt; &lt;span class="s2"&gt;"private"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  count &lt;span class="o"&gt;=&lt;/span&gt; length&lt;span class="o"&gt;(&lt;/span&gt;var.azs&lt;span class="o"&gt;)&lt;/span&gt;  
  subnet_id      &lt;span class="o"&gt;=&lt;/span&gt; aws_subnet.private[count.index].id
  route_table_id &lt;span class="o"&gt;=&lt;/span&gt; aws_route_table.private.id
&lt;span class="o"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: The NAT Gateway and Internet Gateway ensure proper routing between private subnets and external services.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Security Group Configuration
&lt;/h4&gt;

&lt;p&gt;These security groups control access to our EKS cluster and load balancer:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ALB Security Group:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allows inbound traffic on port 80 from the internet.&lt;/li&gt;
&lt;li&gt;Permits unrestricted outbound traffic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;EKS Security Group:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allows inbound traffic from the ALB for internal communication.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Terraform Configuration:&lt;/strong&gt;&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;# ALB Security group&lt;/span&gt;
resource &lt;span class="s2"&gt;"aws_security_group"&lt;/span&gt; &lt;span class="s2"&gt;"alb"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.project_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-alb-sg"&lt;/span&gt;
  vpc_id &lt;span class="o"&gt;=&lt;/span&gt; var.vpc_id

  tags &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    Name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.project_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-eks-alb"&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# EKS Secuirty group&lt;/span&gt;
resource &lt;span class="s2"&gt;"aws_security_group"&lt;/span&gt; &lt;span class="s2"&gt;"eks"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.project_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-eks-sg"&lt;/span&gt;
  vpc_id &lt;span class="o"&gt;=&lt;/span&gt; var.vpc_id

  tags &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    Name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.project_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-eks-sg"&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Allow ALB to communicate with EKS&lt;/span&gt;

resource &lt;span class="s2"&gt;"aws_security_group_rule"&lt;/span&gt; &lt;span class="s2"&gt;"allow_alb"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ingress"&lt;/span&gt;
  security_group_id &lt;span class="o"&gt;=&lt;/span&gt; aws_security_group.eks.id
  source_security_group_id &lt;span class="o"&gt;=&lt;/span&gt; aws_security_group.alb.id
  from_port         &lt;span class="o"&gt;=&lt;/span&gt; 80
  protocol       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
  to_port           &lt;span class="o"&gt;=&lt;/span&gt; 80

&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Allow EKS to outbound traffic&lt;/span&gt;
resource &lt;span class="s2"&gt;"aws_security_group_rule"&lt;/span&gt; &lt;span class="s2"&gt;"allow_eks_egress"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"egress"&lt;/span&gt;
  security_group_id &lt;span class="o"&gt;=&lt;/span&gt; aws_security_group.eks.id
  from_port         &lt;span class="o"&gt;=&lt;/span&gt; 0
  protocol       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"-1"&lt;/span&gt;
  to_port           &lt;span class="o"&gt;=&lt;/span&gt; 0
  cidr_blocks &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Tip: Always define strict ingress and egress rules to maintain security.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  EKS Cluster Configuration
&lt;/h4&gt;

&lt;p&gt;The EKS cluster is deployed in private subnets, and the ALB will forward traffic to the worker nodes. We let AWS manage the security up for cluster and node group creation this reduces the need for manual intervention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Terraform Configuration:&lt;/strong&gt;&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;# Create the EKS Cluster&lt;/span&gt;
resource &lt;span class="s2"&gt;"aws_eks_cluster"&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  name     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"fastAPI-cluster"&lt;/span&gt;
  role_arn &lt;span class="o"&gt;=&lt;/span&gt; aws_iam_role.cluster.arn
  version  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1.31"&lt;/span&gt;

  vpc_config &lt;span class="o"&gt;{&lt;/span&gt;
    endpoint_private_access &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true
    &lt;/span&gt;endpoint_public_access  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;false
    &lt;/span&gt;subnet_ids              &lt;span class="o"&gt;=&lt;/span&gt; var.private_subnet_ids
  &lt;span class="o"&gt;}&lt;/span&gt;

  depends_on &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;
    aws_iam_role_policy_attachment.cluster_AmazonEKSClusterPolicy,
  &lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Create EKS Node Group&lt;/span&gt;
resource &lt;span class="s2"&gt;"aws_eks_node_group"&lt;/span&gt; &lt;span class="s2"&gt;"main_node"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  cluster_name    &lt;span class="o"&gt;=&lt;/span&gt; aws_eks_cluster.main.name
  node_group_name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"fastAPI-node"&lt;/span&gt;
  node_role_arn   &lt;span class="o"&gt;=&lt;/span&gt; aws_iam_role.node_group.arn
  subnet_ids      &lt;span class="o"&gt;=&lt;/span&gt; var.private_subnet_ids

  scaling_config &lt;span class="o"&gt;{&lt;/span&gt;
    desired_size &lt;span class="o"&gt;=&lt;/span&gt; 3
    max_size     &lt;span class="o"&gt;=&lt;/span&gt; 6
    min_size     &lt;span class="o"&gt;=&lt;/span&gt; 3
  &lt;span class="o"&gt;}&lt;/span&gt;

  remote_access &lt;span class="o"&gt;{&lt;/span&gt;
    source_security_group_ids &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; var.eks_sg_id &lt;span class="o"&gt;]&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  instance_types &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"t2.micro"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;

  depends_on &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;
    aws_iam_role_policy_attachment.node_AmazonEKSWorkerNodePolicy,
    aws_iam_role_policy_attachment.node_AmazonEKS_CNI_Policy,
aws_iam_role_policy_attachment.node_AmazonEC2ContainerRegistryReadOnly,
  &lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  EKS Access Entries and Policy Associations
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  data &lt;span class="s2"&gt;"aws_iam_role"&lt;/span&gt; &lt;span class="s2"&gt;"console_role"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"EC2RoleForSSM_EKS"&lt;/span&gt; &lt;span class="c"&gt;# Replace for with your roles name&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;# Map IAM Role to Kubernetes Group using EKS Access Entry&lt;/span&gt;
  resource &lt;span class="s2"&gt;"aws_eks_access_entry"&lt;/span&gt; &lt;span class="s2"&gt;"additional_access"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  cluster_name      &lt;span class="o"&gt;=&lt;/span&gt; aws_eks_cluster.main.name
  principal_arn     &lt;span class="o"&gt;=&lt;/span&gt; data.aws_iam_role.console_role.arn
  kubernetes_groups &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"eks:admin"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; 
  &lt;span class="nb"&gt;type&lt;/span&gt;              &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"STANDARD"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# # Associate Access Policy with Access Entry&lt;/span&gt;
resource &lt;span class="s2"&gt;"aws_eks_access_policy_association"&lt;/span&gt; &lt;span class="s2"&gt;"AdminPolicy"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  cluster_name  &lt;span class="o"&gt;=&lt;/span&gt; aws_eks_cluster.main.name
  policy_arn    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:eks::aws:cluster-access-policy/AmazonEKSAdminPolicy"&lt;/span&gt;
  principal_arn &lt;span class="o"&gt;=&lt;/span&gt; data.aws_iam_role.console_role.arn

  access_scope &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;type&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"cluster"&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In the EKS access control replace your name with your create role name&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Applying Terraform Configuration
&lt;/h4&gt;

&lt;p&gt;Run the following commands to deploy your infrastructure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
terraform init

terraform validate

terraform plan

terraform apply &lt;span class="nt"&gt;-auto-approve&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deployment and Access Configuration
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Prerequisites Check:&lt;/strong&gt; Before proceeding, verify that the IAM role 'EC2RoleForSSM_EKS' or similar exists and has the correct permissions for both SSM and EKS. Remember that only the cluster creator has default access - all other users/roles need explicit RBAC configuration. Missing these steps will prevent cluster access.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 6. Creating the EC2 Instance for Cluster Access
&lt;/h3&gt;

&lt;p&gt;We'll create an EC2 instance in our private subnet that will serve as our secure access point to the EKS cluster.&lt;/p&gt;

&lt;p&gt;In the AWS console search up &lt;strong&gt;EC2&lt;/strong&gt; &amp;gt; &lt;strong&gt;launch Instance&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Launch EC2 Instance
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Name:&lt;/strong&gt; Give a unique name&lt;br&gt;
&lt;strong&gt;AMI:&lt;/strong&gt; Amazon Linux 2023&lt;br&gt;
&lt;strong&gt;Instance Type:&lt;/strong&gt; t2.micro&lt;br&gt;
&lt;strong&gt;Key pair (login):&lt;/strong&gt; Proceed without a key pair.&lt;br&gt;
&lt;strong&gt;Network settings:&lt;/strong&gt; Click edit&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;VPC:&lt;/strong&gt; Select your custom vpc managed by terraform&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subnet:&lt;/strong&gt; Select your private subnet managed by terraform&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-assign public IP:&lt;/strong&gt; disabled&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Firewall (security groups):&lt;/strong&gt; Create security group(make a note of name)&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Remove all inbound rules to ensure the instance is accessible only via SSM.&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%2Fkk9d8kyn9w99c8asah0w.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%2Fkk9d8kyn9w99c8asah0w.png" alt="network-details" width="800" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Under Advanced details:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IAM instance profile:&lt;/strong&gt; EC2RoleForSSM_EKS&lt;/p&gt;
&lt;h4&gt;
  
  
  User Data Script
&lt;/h4&gt;

&lt;p&gt;Include this script in the EC2 instance user data:&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;#!/usr/bin/env bash&lt;/span&gt;

yum &lt;span class="nt"&gt;-y&lt;/span&gt; upgrade
yum &lt;span class="nt"&gt;-y&lt;/span&gt; update 

&lt;span class="c"&gt;# install kubectl to interact with the private EKS cluster.&lt;/span&gt;
curl &lt;span class="nt"&gt;-O&lt;/span&gt; https://s3.us-west-2.amazonaws.com/amazon-eks/1.31.2/2024-11-15/bin/linux/amd64/kubectl

&lt;span class="nb"&gt;chmod&lt;/span&gt; +x ./kubectl

&lt;span class="nb"&gt;sudo mv&lt;/span&gt; ./kubectl /usr/local/bin/kubectl

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

&lt;/div&gt;



&lt;p&gt;Click &lt;strong&gt;Launch instance&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Select your new instance.&lt;/p&gt;

&lt;p&gt;Select the Security tab.&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%2Fcwgeqm794geo4bfp8dh7.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%2Fcwgeqm794geo4bfp8dh7.png" alt="security-tab" width="800" height="49"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make a Note of the Security Group ID&lt;/p&gt;

&lt;h5&gt;
  
  
  Allow EC2 access to control plane security group
&lt;/h5&gt;

&lt;p&gt;In the AWS  console search &lt;strong&gt;VPC&lt;/strong&gt; &amp;gt; &lt;strong&gt;Security Groups&lt;/strong&gt;&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%2F2fbym9tizbsz7ss7y2v8.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%2F2fbym9tizbsz7ss7y2v8.png" alt="vpc-sg-aws" width="448" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Search and &lt;strong&gt;select&lt;/strong&gt; the control plane security group "eks-cluster-sg-fastAPI-cluster-XXXXXX".&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;look for a description "EKS created security group applied to ENI that is attached to EKS Control Plane master nodes, as well as any managed workloads."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Click &lt;strong&gt;Edit inbound rules&lt;/strong&gt;&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%2Fp44tvfwp7cq6i0jlz5h8.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%2Fp44tvfwp7cq6i0jlz5h8.png" alt="inbound-rules" width="800" height="204"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add a new rule:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Type:&lt;/strong&gt; HTTPS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protocol:&lt;/strong&gt; TCP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Port Range:&lt;/strong&gt; 443&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source:&lt;/strong&gt; Select "Custom" and enter the Security Group ID of your EC2 instance (e.g., sg-xxxxxx).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Click &lt;strong&gt;Save rules&lt;/strong&gt; to apply the changes.&lt;/p&gt;

&lt;h4&gt;
  
  
  Connecting via Systems Manager
&lt;/h4&gt;

&lt;p&gt;In the AWS Console Search "Systems Manager"&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%2Fps84ua9lfeuvnskdknwd.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%2Fps84ua9lfeuvnskdknwd.png" alt="session-manager-aws" width="800" height="656"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Under &lt;strong&gt;Node Tools&lt;/strong&gt; Click &lt;strong&gt;Session Manager&lt;/strong&gt; &amp;gt; &lt;strong&gt;Start Session&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Select the Instance you created.&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%2F7vol7ian6gg9d3o9w8m7.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%2F7vol7ian6gg9d3o9w8m7.png" alt="instance-aws" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click Start session.&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;sudo &lt;/span&gt;su
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To gain root access&lt;/p&gt;

&lt;p&gt;Verify Kubectl is installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
kubectl version &lt;span class="nt"&gt;--client&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Configure kubectl to connect to the EKS cluster:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws eks &lt;span class="nt"&gt;--region&lt;/span&gt; &amp;lt;region&amp;gt; update-kubeconfig &lt;span class="nt"&gt;--name&lt;/span&gt; &amp;lt;cluster-name&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;I used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws eks &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1 update-kubeconfig &lt;span class="nt"&gt;--name&lt;/span&gt; fastAPI-cluster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Kubernetes Manifests and Deployment
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Understanding the Manifests
&lt;/h3&gt;

&lt;p&gt;Our application deployment requires two Kubernetes manifests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Deployment manifest to manage our FastAPI application pods&lt;/li&gt;
&lt;li&gt;A Service manifest to expose our application through a load balancer&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Deployment Manifest
&lt;/h2&gt;

&lt;p&gt;The Deployment manifest (&lt;code&gt;fastapi-deploy.yaml&lt;/code&gt;) defines how our application should 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;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;fastapi-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;fastapi&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;fastapi&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;fastapi&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;fastapi&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;ACCOUNT_ID.dkr.ecr.REGION.amazonaws.com/fastapi-app:latest&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;Key components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;replicas: 3&lt;/code&gt;: Maintains three running instances of our application&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;app: fastapi&lt;/code&gt;: Label used to identify our application pods&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;image&lt;/code&gt;: References our container in ECR (replace with your ECR URI)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example ECR URI format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;123456789012.dkr.ecr.us-east-1.amazonaws.com/fastapi-app:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Service Manifest
&lt;/h2&gt;

&lt;p&gt;The Service manifest (&lt;code&gt;fastapi-service.yaml&lt;/code&gt;) configures how to access our application:&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;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;Service&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;fastapi-service&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;service.beta.kubernetes.io/aws-load-balancer-scheme&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;internet-facing"&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;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LoadBalancer&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;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fastapi&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;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;aws-load-balancer-scheme: "internet-facing"&lt;/code&gt;: Creates an public facing load balancer&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;type: LoadBalancer&lt;/code&gt;: Provisions an AWS Load Balancer&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;selector: app: fastapi&lt;/code&gt;: Connects to pods with matching labels&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h4&gt;
  
  
  1. Create the deployment file
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano fastapi-deploy.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste the deployment manifest content.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Create the service file
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano fastapi-service.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste the service manifest content.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Apply the manifests
&lt;/h4&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; fastapi-deploy.yaml
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; fastapi-service.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4. Verify the deployment
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check pods status&lt;/span&gt;
kubectl get pods

&lt;span class="c"&gt;# Check service and load balancer&lt;/span&gt;
kubectl get services
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected outputs:&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;# Pods output&lt;/span&gt;
NAME                                  READY   STATUS    RESTARTS   
fastapi-deployment-xxxxxxxxxx-xxxx   1/1     Running   0          
fastapi-deployment-xxxxxxxxxx-xxxx   1/1     Running   0          
fastapi-deployment-xxxxxxxxxx-xxxx   1/1     Running   0          

&lt;span class="c"&gt;# Services output&lt;/span&gt;
NAME              TYPE           CLUSTER-IP      EXTERNAL-IP                                    
fastapi-service   LoadBalancer   XXX.XXX.XXX.XX   internal-xxxxx.us-east-1.elb.amazonaws.com   
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Troubleshooting Tips
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;If pods aren't starting, check the logs: &lt;code&gt;kubectl logs &amp;lt;pod-name&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;For service issues: &lt;code&gt;kubectl describe service fastapi-service&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;To verify ECR access: &lt;code&gt;kubectl describe pod &amp;lt;pod-name&amp;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%2Fr3alxizpse4qgy5i2byq.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%2Fr3alxizpse4qgy5i2byq.png" alt="ssm-commands" width="800" height="157"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Access the application using the IP&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%2Fxrduk2wrprdba6d8fhgq.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%2Fxrduk2wrprdba6d8fhgq.png" alt="docker-container" width="800" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Resource Cleanup
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Clean Kubernetes Resources:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl delete &lt;span class="nt"&gt;--all&lt;/span&gt; deployments
kubectl delete &lt;span class="nt"&gt;--all&lt;/span&gt; services
kubectl delete &lt;span class="nt"&gt;--all&lt;/span&gt; pods
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;AWS Console Cleanup:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Remove EC2 inbound rule from Kubernetes control plane security group&lt;/li&gt;
&lt;li&gt;Terminate EC2 instance&lt;/li&gt;
&lt;li&gt;Delete ECR repository if no longer needed&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure Cleanup:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform destroy &lt;span class="nt"&gt;-auto-approve&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;If you run into terraform resources not being deleted. Delete through console.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;Deploying an EKS cluster within a private VPC enhances security by restricting API access. By leveraging Terraform and AWS Systems Manager, you can automate the provisioning process, manage IAM roles effectively, and securely access your cluster without exposing it to the public internet. This approach not only simplifies infrastructure management but also adheres to best practices for security and scalability in cloud environments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best Practices Implemented:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Private VPC deployment for enhanced security&lt;/li&gt;
&lt;li&gt;IAM role-based access control&lt;/li&gt;
&lt;li&gt;Systems Manager for secure instance access&lt;/li&gt;
&lt;li&gt;Infrastructure as Code for consistency&lt;/li&gt;
&lt;li&gt;Containerized application deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🟢(Optional) Implement CI/CD with GitHub Actions
&lt;/h3&gt;

&lt;p&gt;To further streamline your deployment process, integrating a CI/CD pipeline can automate the building and deployment of your application. While this blog focuses on setting up the EKS cluster and deploying the application manually, you can enhance your workflow by implementing CI/CD using GitHub Actions check out my blog post here that covers that&lt;a href="https://dev.to/dhayv/build-a-secure-cicd-pipeline-for-amazon-eks-using-github-actions-and-aws-oidc-3b0m"&gt;Build a Secure CI/CD Pipeline for Amazon EKS Using GitHub Actions and AWS OIDC&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Build a Secure CI/CD Pipeline for Amazon EKS Using GitHub Actions and AWS OIDC</title>
      <dc:creator>David Hyppolite</dc:creator>
      <pubDate>Wed, 29 Jan 2025 01:37:19 +0000</pubDate>
      <link>https://dev.to/dhayv/build-a-secure-cicd-pipeline-for-amazon-eks-using-github-actions-and-aws-oidc-3b0m</link>
      <guid>https://dev.to/dhayv/build-a-secure-cicd-pipeline-for-amazon-eks-using-github-actions-and-aws-oidc-3b0m</guid>
      <description>&lt;p&gt;In this guide, I’ll show you how to build a secure &lt;strong&gt;AWS EKS(Kubernetes)&lt;/strong&gt; CI/CD pipeline for your FastAPI app complete with &lt;strong&gt;GitHub Actions&lt;/strong&gt;, &lt;strong&gt;Docker&lt;/strong&gt;, and &lt;strong&gt;OpenID Connect (OIDC)&lt;/strong&gt; all while following AWS security best practices like least-privilege IAM policies. We won’t obsess over every detail of FastAPI or Docker, but we’ll cover enough to get your application running on EKS with confidence.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: The primary focus of this blog is on CI/CD processes and AWS configuration, so we won’t dive too deeply into FastAPI or Docker fundamentals.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Table of Contents
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Step 1: GitHub Repository&lt;/li&gt;
&lt;li&gt;Step 2: Create an Amazon ECR Repository&lt;/li&gt;
&lt;li&gt;Step 3: Create an Amazon EKS Cluster with Eksctl&lt;/li&gt;
&lt;li&gt;Step 4: Set Up a GitHub Actions Workflow for EKS Deployment&lt;/li&gt;
&lt;li&gt;Step 5: Github Actions Workflow&lt;/li&gt;
&lt;li&gt;Clean Up Resources&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Technologies &amp;amp; Skills
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWS EKS&lt;/strong&gt; (Kubernetes on AWS)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FastAPI&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Amazon ECR&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloud Security &amp;amp; IAM&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kubernetes Orchestration&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWS Account:&lt;/strong&gt; Admin permissions for creating EKS clusters, IAM roles, and ECR repositories.&lt;/li&gt;
&lt;li&gt;AWS CLI installed and configured: &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" rel="noopener noreferrer"&gt;Install Guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;eksctl installed: &lt;a href="https://eksctl.io/installation/" rel="noopener noreferrer"&gt;Installation Guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docker installed: &lt;a href="https://www.docker.com/get-started/" rel="noopener noreferrer"&gt;Get Started with Docker&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Managing Kubernetes on AWS via EKS is a great approach, but configuring secure access for CI/CD can be tricky. With OIDC, GitHub Actions can assume roles in AWS without storing secret keys, so no static credentials, Let’s walk through setting up an EKS cluster, creating an ECR repo, and configuring GitHub Actions to deploy our FastAPI service to Kubernetes automatically.&lt;/p&gt;

&lt;p&gt;This guide walks through:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Setting up an EKS cluster with eksctl&lt;/li&gt;
&lt;li&gt;Creating an ECR repository for your Docker images&lt;/li&gt;
&lt;li&gt;Configuring OIDC so GitHub Actions can assume an IAM role&lt;/li&gt;
&lt;li&gt;Deploying the FastAPI application to EKS&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Key Takeaways
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Security-First CI/CD:&lt;/strong&gt; By using OIDC, there’s no need to embed AWS credentials in GitHub.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Least-Privilege Access:&lt;/strong&gt; You’ll attach minimal IAM permissions, restricted to your ECR repo or EKS cluster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy Automation:&lt;/strong&gt;  GitHub Actions automatically builds, pushes, and deploys whenever you push code to your main branch.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step-by-Step Guide
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: GitHub Repository
&lt;/h3&gt;

&lt;p&gt;Create (or use) a repository that holds your FastAPI app and Kubernetes manifests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
Repository Structure:
├── main.py        &lt;span class="c"&gt;# FastApi application&lt;/span&gt;
├── Dockerfile        &lt;span class="c"&gt;# Docker&lt;/span&gt;
├── fastapi-deploy.yaml           &lt;span class="c"&gt;# deploy scripts&lt;/span&gt;
├── fastapi-service.yaml           &lt;span class="c"&gt;# service scripts&lt;/span&gt;
└── requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Keep your code organized. The .yaml files will be applied to EKS to deploy the FastAPI service.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 2: Create an Amazon ECR Repository
&lt;/h3&gt;

&lt;p&gt;Amazon ECR is used to store your Docker images.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to AWS Console &amp;gt; Search &lt;strong&gt;ECR&lt;/strong&gt; &amp;gt; &lt;strong&gt;Create Repository&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Name it (e.g., fastapi-app) or anything you prefer&lt;/li&gt;
&lt;li&gt;Leave defaults, then Create&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%2Fkf1lq25fuzmjafwz8y80.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%2Fkf1lq25fuzmjafwz8y80.png" alt="create-ecr" width="800" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set the repository name: fastapi-app&lt;/li&gt;
&lt;li&gt;Leave the default settings&lt;/li&gt;
&lt;li&gt;click &lt;strong&gt;Create&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;View Repository Commands:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;After creation select the repo&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;View push commands&lt;/strong&gt; on your new repo to see the exact Docker build/push steps.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Retrieve the ECR ARN for use in IAM policies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws ecr describe-repositories &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;This is important if you want to create inline IAM policies restricting actions to this specific repository.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 3: Create an Amazon EKS Cluster with Eksctl
&lt;/h3&gt;

&lt;p&gt;Make sure you have the AWS CLI and Eksctl installed on your device to create the EKS cluster.&lt;br&gt;
Using eksctl, create the EKS cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eksctl create cluster &lt;span class="nt"&gt;--name&lt;/span&gt; cluster-name &lt;span class="se"&gt;\&lt;/span&gt;
                      &lt;span class="nt"&gt;--region&lt;/span&gt; &amp;lt;YOUR_REGION&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
                      &lt;span class="nt"&gt;--node-type&lt;/span&gt; instance-type &lt;span class="se"&gt;\&lt;/span&gt;
                      &lt;span class="nt"&gt;--nodes-min&lt;/span&gt; 2 &lt;span class="se"&gt;\&lt;/span&gt;
                      &lt;span class="nt"&gt;--nodes-max&lt;/span&gt; 6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I used.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eksctl create cluster &lt;span class="nt"&gt;--name&lt;/span&gt; fastapi-demo &lt;span class="se"&gt;\&lt;/span&gt;
                      &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1 &lt;span class="se"&gt;\&lt;/span&gt;
                      &lt;span class="nt"&gt;--node-type&lt;/span&gt; t3.medium &lt;span class="se"&gt;\&lt;/span&gt;
                      &lt;span class="nt"&gt;--nodes-min&lt;/span&gt; 2 &lt;span class="se"&gt;\&lt;/span&gt;
                      &lt;span class="nt"&gt;--nodes-max&lt;/span&gt; 6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will take several minutes. Once finished, your kubeconfig will be updated so you can run kubectl commands against the new cluster.&lt;/p&gt;

&lt;p&gt;Verify 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;kubectl get nodes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Set Up a GitHub Actions Workflow for EKS Deployment
&lt;/h3&gt;

&lt;p&gt;To avoid storing permanent AWS credentials in GitHub, we’ll configure OpenID Connect (OIDC) so GitHub Actions can assume roles in AWS. &lt;a href="https://aws.amazon.com/blogs/security/use-iam-roles-to-connect-github-actions-to-actions-in-aws/" rel="noopener noreferrer"&gt;Guthub Action in AWS&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have previously created a Identity provider for GitHub skip this step if not create one:&lt;/p&gt;

&lt;h4&gt;
  
  
  Add a New OIDC Provider
&lt;/h4&gt;

&lt;p&gt;Go to the &lt;strong&gt;AWS Console&lt;/strong&gt; &amp;gt; &lt;strong&gt;IAM&lt;/strong&gt; &amp;gt; &lt;strong&gt;Identity Providers&lt;/strong&gt; &amp;gt; &lt;strong&gt;Add provider&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Provider type:&lt;/strong&gt; OpenID Connect&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provider Url:&lt;/strong&gt; &lt;a href="https://token.actions.githubusercontent.com" rel="noopener noreferrer"&gt;https://token.actions.githubusercontent.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audience:&lt;/strong&gt; sts.amazonaws.com&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next &lt;strong&gt;Add provider&lt;/strong&gt;.&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%2Fzxkmr82acbcr2ki9b5ki.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%2Fzxkmr82acbcr2ki9b5ki.png" alt=" " width="800" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Assign a role
&lt;/h4&gt;

&lt;p&gt;Copy the Identity provider role arn.&lt;/p&gt;

&lt;p&gt;Select the newly your newly created provider on the Identity providers screen&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%2F1no2ncorfyp2am8hh2ma.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%2F1no2ncorfyp2am8hh2ma.png" alt="assign-role" width="800" height="39"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Assign a role&lt;/strong&gt;&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%2Fld4jz7v1774b5u9g0wqo.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%2Fld4jz7v1774b5u9g0wqo.png" alt="new-role" width="613" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Role options:&lt;/strong&gt; Create a new role&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%2Faarppzu5dmwp5zekjyuc.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%2Faarppzu5dmwp5zekjyuc.png" alt="trusted-entity-type" width="800" height="39"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trusted entity type:&lt;/strong&gt; Custom trust policy&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;next&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GitHub organization , the repository named , and the branch named . Update the Federated ARN with the GitHub IdP ARN that you copied previously.&lt;/p&gt;

&lt;p&gt;Replace ,, and  with your AWS account ID, GitHub repository owner, repository name, and branch name.&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="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"Version"&lt;/span&gt;: &lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;,
  &lt;span class="s2"&gt;"Statement"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"Effect"&lt;/span&gt;: &lt;span class="s2"&gt;"Allow"&lt;/span&gt;,
      &lt;span class="s2"&gt;"Principal"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"Federated"&lt;/span&gt;: &lt;span class="s2"&gt;"arn:aws:iam::&amp;lt;AWS_ACCOUNT_ID&amp;gt;:oidc-provider/token.actions.githubusercontent.com"&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt;,
      &lt;span class="s2"&gt;"Action"&lt;/span&gt;: &lt;span class="s2"&gt;"sts:AssumeRoleWithWebIdentity"&lt;/span&gt;,
      &lt;span class="s2"&gt;"Condition"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"StringEquals"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"token.actions.githubusercontent.com:aud"&lt;/span&gt;: &lt;span class="s2"&gt;"sts.amazonaws.com"&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;,
        &lt;span class="s2"&gt;"StringLike"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"token.actions.githubusercontent.com:sub"&lt;/span&gt;: &lt;span class="s2"&gt;"repo:&amp;lt;OWNER&amp;gt;/&amp;lt;REPO&amp;gt;:ref:refs/heads/&amp;lt;BRANCH&amp;gt;"&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Than click &lt;strong&gt;Next&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Attach the policies below:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;AmazonEKSClusterPolicy&lt;/li&gt;
&lt;li&gt;AmazonEKSWorkerNodePolicy&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Than click &lt;strong&gt;Next&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Name the role, e.g. &lt;strong&gt;GitHubActionsEKSECRRole&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next click &lt;strong&gt;Create Role&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Access the newly created role.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add Inline Policy for ECR:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add Permissions&lt;/strong&gt; &amp;gt; &lt;strong&gt;Create Inline policy&lt;/strong&gt; &amp;gt; Use the &lt;strong&gt;JSON&lt;/strong&gt; editor&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%2Fpkdtmj1nzl53lyv3c5p0.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%2Fpkdtmj1nzl53lyv3c5p0.png" alt="c" width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Copy the code below to restrict to you repository ARN, as shown below.&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="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"Version"&lt;/span&gt;: &lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;,
  &lt;span class="s2"&gt;"Statement"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"Effect"&lt;/span&gt;: &lt;span class="s2"&gt;"Allow"&lt;/span&gt;,
      &lt;span class="s2"&gt;"Action"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"ecr:GetAuthorizationToken"&lt;/span&gt;
      &lt;span class="o"&gt;]&lt;/span&gt;,
      &lt;span class="s2"&gt;"Resource"&lt;/span&gt;: &lt;span class="s2"&gt;"*"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;,
    &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"Effect"&lt;/span&gt;: &lt;span class="s2"&gt;"Allow"&lt;/span&gt;,
      &lt;span class="s2"&gt;"Action"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"ecr:BatchCheckLayerAvailability"&lt;/span&gt;,
        &lt;span class="s2"&gt;"ecr:GetDownloadUrlForLayer"&lt;/span&gt;,
        &lt;span class="s2"&gt;"ecr:GetRepositoryPolicy"&lt;/span&gt;,
        &lt;span class="s2"&gt;"ecr:DescribeRepositories"&lt;/span&gt;,
        &lt;span class="s2"&gt;"ecr:ListImages"&lt;/span&gt;,
        &lt;span class="s2"&gt;"ecr:DescribeImages"&lt;/span&gt;,
        &lt;span class="s2"&gt;"ecr:BatchGetImage"&lt;/span&gt;,
        &lt;span class="s2"&gt;"ecr:GetLifecyclePolicy"&lt;/span&gt;,
        &lt;span class="s2"&gt;"ecr:GetLifecyclePolicyPreview"&lt;/span&gt;,
        &lt;span class="s2"&gt;"ecr:ListTagsForResource"&lt;/span&gt;,
        &lt;span class="s2"&gt;"ecr:DescribeImageScanFindings"&lt;/span&gt;,
        &lt;span class="s2"&gt;"ecr:InitiateLayerUpload"&lt;/span&gt;,
        &lt;span class="s2"&gt;"ecr:UploadLayerPart"&lt;/span&gt;,
        &lt;span class="s2"&gt;"ecr:CompleteLayerUpload"&lt;/span&gt;,
        &lt;span class="s2"&gt;"ecr:PutImage"&lt;/span&gt;
      &lt;span class="o"&gt;]&lt;/span&gt;,
      &lt;span class="s2"&gt;"Resource"&lt;/span&gt;: &lt;span class="s2"&gt;"arn:aws:ecr:&amp;lt;AWS-REGION&amp;gt;:&amp;lt;ACCOUNT-ID&amp;gt;:repository/&amp;lt;REPO-NAME&amp;gt;"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Give your role policy a name; &lt;strong&gt;GitHubActionsEKSECRRole&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Next&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of editing aws-auth ConfigMap, you can give this role direct EKS access:&lt;/p&gt;

&lt;p&gt;Run the following code in your CLI.&lt;/p&gt;

&lt;p&gt;Replace cluster-name with the name of 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;&lt;span class="s2"&gt;"aws eks create-access-entry --cluster-name &amp;lt;cluster-name&amp;gt; --principal-arn arn:aws:iam::&amp;lt;AWS_ACCOUNT_ID&amp;gt;:role/GitHubActionsEKSECRRole --type STANDARD"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then associate the policy that allows cluster admin actions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws eks associate-access-policy &lt;span class="nt"&gt;--cluster-name&lt;/span&gt; &amp;lt;cluster-name&amp;gt; &lt;span class="nt"&gt;--principal-arn&lt;/span&gt; arn:aws:iam::&amp;lt;AWS_ACCOUNT_ID&amp;gt;:role/GitHubActionsEKSECRRole &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--access-scope&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cluster &lt;span class="nt"&gt;--policy-arn&lt;/span&gt; arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can verify your entry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws eks list-access-entries &lt;span class="nt"&gt;--cluster-name&lt;/span&gt; fastapi-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5: Github actions Workflow
&lt;/h3&gt;

&lt;p&gt;Finally, set up your GitHub Actions workflow to build, push, and deploy automatically.&lt;/p&gt;

&lt;p&gt;In your GitHub repo, navigate to &lt;strong&gt;Actions&lt;/strong&gt; &amp;gt; &lt;strong&gt;New Workflow.&lt;/strong&gt;&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%2Fq0e6zetc3bikd1cks4ma.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%2Fq0e6zetc3bikd1cks4ma.png" alt=" " width="800" height="67"&gt;&lt;/a&gt;&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%2Fugjmze78ihzxgq4jzmmr.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%2Fugjmze78ihzxgq4jzmmr.png" alt=" " width="800" height="85"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Copy the code below and replace with your information. Anything that is full capitilized and begins with a "$" is sensitive information that will not be easily shown in your code.&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to AWS&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;workflow_dispatch&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;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&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@v4.2.2&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@v4&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;role-to-assume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_ROLE_ARN }}&lt;/span&gt;
          &lt;span class="na"&gt;role-session-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GitHubActionsSession&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;${{ secrets.AWS_REGION }}&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;Login to Amazon ECR&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;login-ecr&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/amazon-ecr-login@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;Build, tag, and push docker image to Amazon ECR&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;REGISTRY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.login-ecr.outputs.registry }}&lt;/span&gt;
          &lt;span class="na"&gt;REPOSITORY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fastapi-app&lt;/span&gt; &lt;span class="c1"&gt;# your ECR repository name&lt;/span&gt;
          &lt;span class="na"&gt;IMAGE_TAG&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.sha }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;docker build -t $REGISTRY/$REPOSITORY:$IMAGE_TAG .&lt;/span&gt;
          &lt;span class="s"&gt;docker push $REGISTRY/$REPOSITORY:$IMAGE_TAG&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 kube config&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 eks update-kubeconfig --name ${{ secrets.EKS_CLUSTER_NAME }} --region ${{ secrets.AWS_REGION }}&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 to EKS&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;ECR_REGISTRY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.login-ecr.outputs.registry }}&lt;/span&gt;        
          &lt;span class="na"&gt;IMAGE_TAG&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.sha }}&lt;/span&gt;
          &lt;span class="na"&gt;EKS_CLUSTER_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.EKS_CLUSTER_NAME }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;sed -i.bak "s|DOCKER_IMAGE|$ECR_REGISTRY/$REPOSITORY:$IMAGE_TAG|g" fastapi-deploy.yaml&lt;/span&gt;
          &lt;span class="s"&gt;kubectl apply -f fastapi-deploy.yaml&lt;/span&gt;
          &lt;span class="s"&gt;kubectl apply -f fastapi-service.yaml&lt;/span&gt;

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  Understanding Our GitHub Actions Workflow for AWS Deployment
&lt;/h4&gt;

&lt;p&gt;Let's walk through our CI/CD pipeline that automates deploying applications to AWS using GitHub Actions. This workflow handles everything from building Docker images to deploying on EKS, all while maintaining security best practices.&lt;/p&gt;

&lt;h5&gt;
  
  
  Workflow Triggers and Permissions
&lt;/h5&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 to AWS&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;workflow_dispatch&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our pipeline springs into action in two scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When code is pushed to the main branch&lt;/li&gt;
&lt;li&gt;Manually through workflow_dispatch (useful for testing or one-off deployments)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The workflow needs specific permissions to interact with AWS securely:&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;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These permissions enable OIDC authentication with AWS while keeping access to our GitHub repository read-only - following the principle of least privilege.&lt;/p&gt;

&lt;h5&gt;
  
  
  Secure AWS Authentication
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&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@v4&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;role-to-assume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_ROLE_ARN }}&lt;/span&gt;
    &lt;span class="na"&gt;role-session-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GitHubActionsSession&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;${{ secrets.AWS_REGION }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This step establishes secure communication with AWS using OIDC. Instead of storing long-lived credentials, we assume an IAM role temporarily. This role grants just enough permissions to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Push images to ECR&lt;/li&gt;
&lt;li&gt;Deploy to our EKS cluster&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  Container Image Management
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&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;Login to Amazon ECR&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;login-ecr&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/amazon-ecr-login@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;Build, tag, and push docker image to Amazon ECR&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;REGISTRY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.login-ecr.outputs.registry }}&lt;/span&gt;
    &lt;span class="na"&gt;REPOSITORY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fastapi-app&lt;/span&gt;
    &lt;span class="na"&gt;IMAGE_TAG&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.sha }}&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;docker build -t $REGISTRY/$REPOSITORY:$IMAGE_TAG .&lt;/span&gt;
    &lt;span class="s"&gt;docker push $REGISTRY/$REPOSITORY:$IMAGE_TAG&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's where we handle our Docker image:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;First, we authenticate with Amazon ECR&lt;/li&gt;
&lt;li&gt;Then we build the image using our Dockerfile&lt;/li&gt;
&lt;li&gt;We tag it with the git commit SHA for version tracking&lt;/li&gt;
&lt;li&gt;Finally, we push it to our ECR repository&lt;/li&gt;
&lt;/ol&gt;

&lt;h5&gt;
  
  
  EKS Deployment
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&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 kube config&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 eks update-kubeconfig --name ${{ secrets.EKS_CLUSTER_NAME }} --region ${{ secrets.AWS_REGION }}&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 to EKS&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;ECR_REGISTRY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.login-ecr.outputs.registry }}&lt;/span&gt;        
    &lt;span class="na"&gt;IMAGE_TAG&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.sha }}&lt;/span&gt;
    &lt;span class="na"&gt;EKS_CLUSTER_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.EKS_CLUSTER_NAME }}&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;sed -i.bak "s|DOCKER_IMAGE|$ECR_REGISTRY/$REPOSITORY:$IMAGE_TAG|g" fastapi-deploy.yaml&lt;/span&gt;
    &lt;span class="s"&gt;kubectl apply -f fastapi-deploy.yaml&lt;/span&gt;
    &lt;span class="s"&gt;kubectl apply -f fastapi-service.yaml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final stage deploys our application to EKS:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We configure kubectl to communicate with our EKS cluster&lt;/li&gt;
&lt;li&gt;Update our Kubernetes manifests with the new image details&lt;/li&gt;
&lt;li&gt;Apply both the deployment and service configurations&lt;/li&gt;
&lt;/ol&gt;

&lt;h5&gt;
  
  
  Security Considerations
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;All sensitive values are stored as GitHub secrets&lt;/li&gt;
&lt;li&gt;OIDC provides secure, temporary AWS credentials&lt;/li&gt;
&lt;li&gt;The workflow uses specific, versioned actions to prevent supply chain attacks&lt;/li&gt;
&lt;li&gt;Each step follows the principle of least privilege&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Remember to replace the placeholder values (anything starting with $) with your specific information when setting up this workflow.n&lt;/p&gt;

&lt;h3&gt;
  
  
  Github Secrets
&lt;/h3&gt;

&lt;p&gt;Store your AWS account details as secrets:&lt;br&gt;
It is good practice to not hardcode sensitive information but we still need to use it to save sensitive infornation we will be using GitHub secrets.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to your repo &amp;gt; &lt;strong&gt;Settings&lt;/strong&gt; → &lt;strong&gt;Secrets &amp;amp; Variables&lt;/strong&gt; &amp;gt; &lt;strong&gt;Actions&lt;/strong&gt; to add them.&lt;/li&gt;
&lt;li&gt;You're going to create a &lt;strong&gt;New Repository Secret&lt;/strong&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%2Fhl6cmkj4o3hyjagcbcg9.png" alt="github-security" width="800" height="48"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your entries will look 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%2Faqkq5fm5gafqyvpsmeje.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%2Faqkq5fm5gafqyvpsmeje.png" alt="aws-region" width="800" height="663"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add entries for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;AWS_REGION

AWS_ROLE_ARN &lt;span class="c"&gt;# Role created&lt;/span&gt;

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  Push Code and Access Cluster
&lt;/h4&gt;

&lt;p&gt;You can monitor the progress of your CI/CD pipeline by visiting the “GitHub Actions” tab in your repository. Here, you’ll see the status of each step in your workflow.&lt;/p&gt;

&lt;p&gt;Here is what a successful deployment looks like.&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%2Fdxrxtszocq94c9w7fy6g.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%2Fdxrxtszocq94c9w7fy6g.png" alt=" " width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;IN the AWS console got to &lt;strong&gt;EC2&lt;/strong&gt; &amp;gt; &lt;strong&gt;Load balancer&lt;/strong&gt;&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%2F6y71j4d3elzagtibol31.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%2F6y71j4d3elzagtibol31.png" alt=" " width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Copy the DNS name and go to port 80.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clean up resources
&lt;/h3&gt;

&lt;p&gt;When you’re done, it’s best to delete unneeded resources to avoid costs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eksctl delete cluster &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fastapi-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, remove or delete the ECR repository if you no longer need it.&lt;/p&gt;

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

&lt;p&gt;Congratulations! You’ve created a fully automated pipeline that deploys a FastAPI app to EKS, all without embedding long-lived AWS credentials in GitHub. By pairing OIDC with carefully scoped IAM policies, you maintain strong security practices while enjoying a streamlined deployment process.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Understanding Kubernetes Manifests: A Beginner’s Guide to Deployments and Services</title>
      <dc:creator>David Hyppolite</dc:creator>
      <pubDate>Mon, 13 Jan 2025 22:21:35 +0000</pubDate>
      <link>https://dev.to/dhayv/understanding-kubernetes-manifests-a-beginners-guide-to-deployments-and-services-2h90</link>
      <guid>https://dev.to/dhayv/understanding-kubernetes-manifests-a-beginners-guide-to-deployments-and-services-2h90</guid>
      <description>&lt;p&gt;When working with Kubernetes, you will have to use manifests to configure your workloads. I wrote this guide to help break down the two most basic k8s YAML files that you'll need. Kubernetes manifests serve as instruction manuals that define resources like Pods, Services, and Deployments, written in YAML format.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tip: YAML is strict with indentation—use an online validator to avoid syntax errors.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Github with full examples &lt;a href="https://github.com/dhayv/k8-manifest-example" rel="noopener noreferrer"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Key Kubernetes Manifests
&lt;/h2&gt;

&lt;p&gt;Two common manifests in a Kubernetes cluster are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Deployment:&lt;/strong&gt; Manages Pods and ensures high availability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service:&lt;/strong&gt; Exposes applications and manages networking.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Parts of a Kubernetes Configuration
&lt;/h2&gt;

&lt;p&gt;There are 3 parts to a Kubernetes configuration:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Metadata:&lt;/strong&gt; Where you define the name and labels for your resource&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spec (Specification):&lt;/strong&gt; The actual configuration of what you want, specific to each resource type&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Status:&lt;/strong&gt; Automatically generated by Kubernetes to reflect the current state of the resource.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Metadata
&lt;/h3&gt;

&lt;p&gt;Metadata includes essential information about the resource, such as its name and labels. Labels are key-value pairs that help categorize and select Kubernetes resources. They enable flexible organization, such as grouping resources by environment (e.g., env: production), tier (e.g., tier: backend), or other custom criteria.&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;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="c1"&gt;# Name of the 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="c1"&gt;# Labels to identify the application&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Spec (Specification)
&lt;/h3&gt;

&lt;p&gt;The spec section defines the desired state of the resource. For example, in a Deployment, the spec includes the number of replicas, the selector for matching Pods, and the template for Pod 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;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;2&lt;/span&gt;              &lt;span class="c1"&gt;# Number of pod replicas for high availability&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="c1"&gt;# Must match the labels in the template&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="c1"&gt;# Labels applied to the pods&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="c1"&gt;# Name of the container&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.14.2&lt;/span&gt; &lt;span class="c1"&gt;# Replace with the URL of your container image&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;span class="c1"&gt;# Port the container listens on&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Status
&lt;/h3&gt;

&lt;p&gt;The status section is automatically generated by Kubernetes. It reflects the current state of the resource, such as the number of available replicas in a Deployment. Kubernetes continuously monitors the &lt;strong&gt;actual state&lt;/strong&gt; against the &lt;strong&gt;desired state&lt;/strong&gt; and takes action to reconcile any differences, leveraging its self-healing capabilities.&lt;/p&gt;

&lt;p&gt;For example, in our deployment manifest we specified we wanted 2 replicas:&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;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the deployment is created, Kubernetes will continuously check the status to make sure we have 2 replicas running at all times. If only 1 replica(&lt;strong&gt;actual state&lt;/strong&gt;) is running, it's not at the &lt;strong&gt;desired state&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full YAML Example
&lt;/h2&gt;

&lt;p&gt;Deployment:&lt;/p&gt;

&lt;p&gt;A Deployment manages a set of identical Pods, ensuring that the specified number of replicas are running at all times. This ensures high availability and load distribution across your application.&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="c1"&gt;# Create a deployment resource&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="c1"&gt;# Name of the 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="c1"&gt;# Labels to identify the application&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;2&lt;/span&gt; &lt;span class="c1"&gt;# Number of pod replicas for high availability&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="c1"&gt;# Must match the labels in the template&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="c1"&gt;# Labels applied to the pods&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="c1"&gt;# Name of the container&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.14.2&lt;/span&gt; &lt;span class="c1"&gt;# Replace with the URL of your container image&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;span class="c1"&gt;# Port the container listens on&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Service:&lt;/p&gt;

&lt;p&gt;A Service exposes your application to the network, managing how traffic is directed to the Pods. By setting the Service type to LoadBalancer, Kubernetes provisions an external load balancer (if supported by your cloud provider) to route traffic to your Service, allowing users to access your application via a public IP address.&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;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;Service&lt;/span&gt; &lt;span class="c1"&gt;# Create a service resource&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-service&lt;/span&gt; &lt;span class="c1"&gt;# Name of the service&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;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LoadBalancer&lt;/span&gt; &lt;span class="c1"&gt;# Exposes the service via a cloud provider's load balancer&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;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt; &lt;span class="c1"&gt;# Links this service to Pods with the label app: fastapi&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;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt; &lt;span class="c1"&gt;# Exposed port for external traffic&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt; &lt;span class="c1"&gt;# Port on the container to route traffic to&lt;/span&gt;
      &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt; &lt;span class="c1"&gt;# Communication protocol&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Resource Declaration (First Two Lines)
&lt;/h3&gt;

&lt;p&gt;The first two line in a Kubernetes yaml manifest is where you declare to Kubernetes the resource type you want to create:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Service&lt;/strong&gt;&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;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;Service&lt;/span&gt; &lt;span class="c1"&gt;# Create a service resource&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Deployment&lt;/strong&gt;&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="c1"&gt;# Create a deployment resource&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Connecting Services to deployments
&lt;/h2&gt;

&lt;p&gt;Service and deployments are connected through &lt;strong&gt;selectors&lt;/strong&gt; and &lt;strong&gt;labels&lt;/strong&gt;. This connection ensures that traffic directed to the Service is properly routed to the appropriate Pods managed by the Deployment.&lt;/p&gt;

&lt;p&gt;In the deployment file:&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;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="c1"&gt;# Labels to identify the application&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2&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="c1"&gt;# Must match the labels in the template&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&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="c1"&gt;# Labels applied to the pods&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Service File&lt;/strong&gt;&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;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="c1"&gt;# Labels to identify the application&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Labels:&lt;/strong&gt; In the Deployment file, the &lt;strong&gt;metadata.labels&lt;/strong&gt; and &lt;strong&gt;template.metadata.labels&lt;/strong&gt; sections assign the label &lt;strong&gt;app: nginx&lt;/strong&gt; to the Deployment and its Pods. Labels are key-value pairs that help categorize and select Kubernetes resources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Selectors:&lt;/strong&gt; In the Deployment's &lt;strong&gt;spec.selector.matchLabels&lt;/strong&gt; and the Service's &lt;strong&gt;spec.selector&lt;/strong&gt;, the label &lt;strong&gt;app: nginx&lt;/strong&gt; is used to match and connect the Service to the appropriate Pods.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traffic Routing:&lt;/strong&gt; The Service uses the selector to identify which Pods to send traffic to. When external traffic arrives at the Service's &lt;strong&gt;port&lt;/strong&gt;, it is forwarded to the &lt;strong&gt;targetPort&lt;/strong&gt; on the matched Pods.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Port Definitions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&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;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt; &lt;span class="c1"&gt;# Exposed port for external traffic&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt; &lt;span class="c1"&gt;# Port on the container to route traffic to&lt;/span&gt;
      &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt; &lt;span class="c1"&gt;# Communication protocol&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Think of it this way:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;External traffic comes to the Service on 'port'&lt;/li&gt;
&lt;li&gt;The Service forwards that traffic to 'targetPort' on your pods&lt;/li&gt;
&lt;li&gt;Your container needs to be listening on 'targetPort'&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These ports must align properly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Service's "targetPort" must match the "containerPort" in your Deployment&lt;/li&gt;
&lt;li&gt;The Service's "port" can be different, but is commonly set to the same number for simplicity&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Enhancing Clarity with Detailed Explanations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Metadata
&lt;/h3&gt;

&lt;p&gt;Metadata provides essential information about your Kubernetes resources. Proper labeling is crucial for organizing and managing resources efficiently. Labels allow you to group resources based on various criteria, such as environment (env: production), application tier (tier: backend), or custom categories (app: nginx).&lt;/p&gt;

&lt;h3&gt;
  
  
  Spec (Specification)
&lt;/h3&gt;

&lt;p&gt;The spec section is the core of your manifest, defining the desired state of the resource. For Deployments, key spec fields include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Replicas:&lt;/strong&gt; Specifies the number of Pod instances to run. For example, replicas: 2 ensures that two Pods are always running, providing high availability.
&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;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Selector:&lt;/strong&gt; Determines how the Deployment identifies which Pods to manage. It must match the labels defined in the Pod template.
&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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Template:&lt;/strong&gt; Defines the Pod's metadata and specifications, including labels, containers, and ports.
&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;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.14.2&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;h3&gt;
  
  
  Status
&lt;/h3&gt;

&lt;p&gt;The status section is managed by Kubernetes and provides real-time information about the resource's current state. It helps Kubernetes track whether the actual state matches the desired state defined in the spec. If discrepancies are detected, Kubernetes takes corrective actions, such as restarting Pods to maintain the desired number of replicas.&lt;/p&gt;

&lt;h4&gt;
  
  
  Example of Self-Healing:
&lt;/h4&gt;

&lt;p&gt;If a Pod crashes or becomes unhealthy, Kubernetes will detect that the actual number of running Pods does not match the desired replicas specified in the Deployment. It will then automatically create a new Pod to replace the failed one, ensuring that the application remains available.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding the Big Picture
&lt;/h3&gt;

&lt;p&gt;Now that we've broken down each component of Kubernetes manifests, you can see how they work together to create a robust and self-healing application infrastructure. The combination of proper metadata, well-defined specs, and Kubernetes' built-in status monitoring ensures your applications run reliably and efficiently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;After reading this guide, you should have a solid foundation for working with Kubernetes manifests. You now understand how to read, structure, and use both Deployment and Service configurations to manage and deploy applications within your Kubernetes cluster. The examples and explanations provided here will serve as a practical reference as you begin your Kubernetes journey.&lt;/p&gt;

&lt;p&gt;Ready to dive deeper? Check out &lt;a href="https://kubernetes.io/docs/home/" rel="noopener noreferrer"&gt;Kubernetes Official Documentation&lt;/a&gt; for more in-depth information and advanced configurations.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Secure EC2 Access Without SSH Using AWS Systems Manager</title>
      <dc:creator>David Hyppolite</dc:creator>
      <pubDate>Wed, 08 Jan 2025 14:54:12 +0000</pubDate>
      <link>https://dev.to/dhayv/secure-ec2-access-without-ssh-using-aws-systems-manager-3dff</link>
      <guid>https://dev.to/dhayv/secure-ec2-access-without-ssh-using-aws-systems-manager-3dff</guid>
      <description>&lt;p&gt;&lt;strong&gt;Need to access your EC2 instances securely without managing SSH keys?&lt;/strong&gt; AWS Systems Manager (SSM) Session Manager provides a secure, auditable solution that might be exactly what you're looking for. Let's explore how it works.&lt;/p&gt;

&lt;p&gt;While SSH is secure, managing SSH access at scale across multiple VPCs and regions creates significant operational complexity. Organizations often struggle with key management, rotation policies, and maintaining bastion hosts - all of which increase both security risks and operational overhead.&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%2Ftpency8jzor9veja3irp.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%2Ftpency8jzor9veja3irp.png" alt="aws-ssm" width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenge
&lt;/h2&gt;

&lt;p&gt;Consider a common scenario: You're managing EC2 instances across multiple VPCs and regions, with strict security policies prohibiting open SSH ports. Traditional approaches might require maintaining multiple bastion hosts - even a single t2.micro bastion host running 24/7 adds unnecessary cost and complexity. SSM eliminates this overhead while providing secure, auditable access to your instances.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Systems Manager
&lt;/h2&gt;

&lt;p&gt;AWS Systems Manager (SSM) Session Manager offers a compelling alternative, especially when working with cloud-native architectures. For teams using AWS-supported AMIs - including Amazon Linux 2023, Ubuntu, Windows Server, and many others - the SSM agent comes pre-installed, eliminating additional setup steps, making it a truly managed service that adds an extra layer of security without the complexity of key management.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits
&lt;/h2&gt;

&lt;p&gt;When working with cloud-native technologies, teams frequently spin up instances to test services and scripts in private subnets. While traditional solutions might rely on bastion hosts or VPC endpoints, SSM provides direct access to these private instances through IAM roles - simplifying your architecture while maintaining strict security controls.&lt;/p&gt;

&lt;h3&gt;
  
  
  Business Value
&lt;/h3&gt;

&lt;p&gt;From a business perspective, SSM offers several key advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Comprehensive Audit Capabilities:&lt;/strong&gt; Through session logging and integration with CloudWatch, every action is recorded for compliance and troubleshooting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session History Tracking:&lt;/strong&gt; Easily track who accessed which instance and when.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexible Log Storage:&lt;/strong&gt; Option to send session logs to Amazon S3 for long-term storage or stream them to CloudWatch Logs for real-time monitoring.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced Security:&lt;/strong&gt; Eliminates the need for open inbound ports, reducing the attack surface and leveraging IAM for access control.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  IAM Role
&lt;/h2&gt;

&lt;h3&gt;
  
  
  IAM Instance Profile
&lt;/h3&gt;

&lt;p&gt;An &lt;strong&gt;IAM instance profile&lt;/strong&gt; is a container for an IAM role that you can assign to an EC2 instance. This allows the instance to securely interact with other AWS services without the need to manage credentials manually.&lt;/p&gt;

&lt;h4&gt;
  
  
  Creating an IAM Role for SSM Access
&lt;/h4&gt;

&lt;p&gt;In the AWS console search up &lt;strong&gt;Roles&lt;/strong&gt; &amp;gt; &lt;strong&gt;Create role&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trusted Entity:&lt;/strong&gt; EC2 &amp;gt; EC2 use case.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Next&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add Permissions:&lt;/strong&gt; Attach the &lt;strong&gt;AmazonSSMManagedInstanceCore&lt;/strong&gt; policy to grant necessary permissions for SSM.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Next&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Name the role.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provide a name for the role, such as &lt;strong&gt;DemoEC2RoleForSSM&lt;/strong&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Review your settings then click &lt;strong&gt;Create role&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a EC2 instance
&lt;/h3&gt;

&lt;p&gt;In the AWS console search up &lt;strong&gt;EC2&lt;/strong&gt; &amp;gt; &lt;strong&gt;launch Instance&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Name:&lt;/strong&gt; Give a unique name&lt;br&gt;
&lt;strong&gt;AMI:&lt;/strong&gt; Amazon Linux 2023&lt;br&gt;
&lt;strong&gt;Instance Type:&lt;/strong&gt; t2.micro&lt;br&gt;
&lt;strong&gt;Key pair (login):&lt;/strong&gt; Proceed without a key pair.&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%2Fw4c327x6yq1keog214hj.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%2Fw4c327x6yq1keog214hj.png" alt="network-settings" width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Network settings
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Auto-assign public IP:&lt;/strong&gt; Disable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Firewall (security groups):&lt;/strong&gt; Create a new security group.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Allow SSH traffic from:&lt;/strong&gt; Uncheck to ensure SSH is not open.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  For Custom VPC with Private Subnets
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;VPC:&lt;/strong&gt; Select your custom vpc&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subnet:&lt;/strong&gt; Choose your private subnet&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-assign public IP:&lt;/strong&gt; Disabled.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Firewall (security groups):&lt;/strong&gt; Remove the security group; SSM will handle access.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Advanced details
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;IAM instance profile:&lt;/strong&gt; Select DemoEC2RoleForSSM.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Launch instance&lt;/strong&gt; to create your EC2 instance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Access EC2 via SSM
&lt;/h2&gt;

&lt;p&gt;In the AWS Console Search "Systems Manager"&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%2Fps84ua9lfeuvnskdknwd.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%2Fps84ua9lfeuvnskdknwd.png" alt="session-manager-aws" width="800" height="656"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start a Session:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Under &lt;strong&gt;Node Tools&lt;/strong&gt; Click on &lt;strong&gt;Session Manager.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Start Session&lt;/strong&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%2Fccnucy3kg7a304b69gu4.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%2Fccnucy3kg7a304b69gu4.png" alt="start-session-ssm" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select your Instance click &lt;strong&gt;Start Session.&lt;/strong&gt;&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%2Fufe3d2l9ntrmrg8yfomv.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%2Fufe3d2l9ntrmrg8yfomv.png" alt="Instance-session" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will now be logged into your EC2 instance without needing SSH.&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%2Fnwdc4qmxm96bpkcozgvn.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%2Fnwdc4qmxm96bpkcozgvn.png" alt="session-screen" width="800" height="347"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Terminal interface within Session Manager.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;By leveraging AWS Systems Manager Session Manager, you can achieve secure EC2 access without the hassles of SSH key management. This AWS Systems Manager tutorial demonstrates a streamlined, cost-effective approach to managing your EC2 instances across multiple VPCs and regions. Embrace this method to bypass SSH with AWS SSM, enhancing both your security posture and operational efficiency.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>EC2 CI/CD Pipeline using AWS CodePipeline, CodeBuild, CodeDeploy</title>
      <dc:creator>David Hyppolite</dc:creator>
      <pubDate>Mon, 30 Dec 2024 17:05:09 +0000</pubDate>
      <link>https://dev.to/dhayv/ec2-cicd-pipeline-using-aws-codepipeline-codebuild-codedeploy-1p21</link>
      <guid>https://dev.to/dhayv/ec2-cicd-pipeline-using-aws-codepipeline-codebuild-codedeploy-1p21</guid>
      <description>&lt;p&gt;Set up a fully automated CI/CD pipeline using AWS CodePipeline, CodeBuild, CodeDeploy, EC2, and GitHub. This guide will help you create a zero-touch solution that automates code updates, ensures reliable deployments, and eliminates the need for manual SSH sessions. By the end, you'll have a robust and efficient deployment process that enhances both reliability and speed.&lt;/p&gt;

&lt;h1&gt;
  
  
  Quick Links
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Overview &amp;amp; Benefits&lt;/li&gt;
&lt;li&gt;Step 1: GitHub Repository Setup&lt;/li&gt;
&lt;li&gt;Step 2: IAM Roles&lt;/li&gt;
&lt;li&gt;Step 3: EC2 Configuration&lt;/li&gt;
&lt;li&gt;Step 4: CodeDeploy Setup&lt;/li&gt;
&lt;li&gt;Step 5: CodePipeline Integration&lt;/li&gt;
&lt;li&gt;Conclusion&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%2F3zehpnu7t8spv7gs7iny.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%2F3zehpnu7t8spv7gs7iny.png" alt="cloud architecture" width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Technologies Used:
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; Stores the application’s source code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS CodePipeline:&lt;/strong&gt; Detects changes in the repository, triggers builds, and initiates deployments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS CodeBuild:&lt;/strong&gt; Builds your code into production-ready artifacts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS CodeDeploy:&lt;/strong&gt; Automates the deployment of your builds onto EC2 instances.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon EC2:&lt;/strong&gt; Hosts your web application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon S3 (Optional):&lt;/strong&gt; Stores build artifacts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IAM:&lt;/strong&gt; Manages secure permissions for the pipeline and services to interact.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;AWS Account with administrative access&lt;/li&gt;
&lt;li&gt;GitHub account&lt;/li&gt;
&lt;li&gt;Basic understanding of AWS services&lt;/li&gt;
&lt;li&gt;Familiarity with React/Vite applications&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Business Value
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Key Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Improved Reliability:&lt;/strong&gt; No more manual SSH deployments. CodeDeploy ensures consistency in every deployment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Faster Iteration:&lt;/strong&gt; Push code and let the pipeline test and deploy automatically, speeding up feedback loops.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rollback Capability:&lt;/strong&gt; If something goes wrong, CodeDeploy can roll back to a previous stable version quickly, minimizing downtime.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Problem Statement
&lt;/h3&gt;

&lt;p&gt;Your team currently deploys updates by SSHing into a live EC2 instance—leading to human error, missed steps, and risky rollbacks. This tutorial fixes that by detecting GitHub changes automatically, deploying them via CodeDeploy, and using a repeatable, testable process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem Solved
&lt;/h3&gt;

&lt;p&gt;Traditional SSH-based deployments lead to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Human errors during manual deployments&lt;/li&gt;
&lt;li&gt;Inconsistent deployment steps&lt;/li&gt;
&lt;li&gt;Difficult rollbacks&lt;/li&gt;
&lt;li&gt;Security vulnerabilities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This solution implements industry best practices for automated, secure, and reliable deployments.&lt;/p&gt;

&lt;p&gt;Ready to eliminate manual deployments? Let's build your pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation Guide
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: GitHub Repository
&lt;/h3&gt;

&lt;p&gt;You can use your own repo or follow along with mine:&lt;/p&gt;

&lt;p&gt;Fork this repository:&lt;/p&gt;

&lt;p&gt;Github Repo: &lt;a href="https://github.com/dhayv/ec2-deploy" rel="noopener noreferrer"&gt;Github EC2&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Repo Contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
Repository Structure:
├── my-react-app/        &lt;span class="c"&gt;# Sample React application&lt;/span&gt;
├── buildspec.yml        &lt;span class="c"&gt;# CodeBuild instructions&lt;/span&gt;
├── appspec.yml         &lt;span class="c"&gt;# CodeDeploy configuration&lt;/span&gt;
└── scripts/           &lt;span class="c"&gt;# Deployment scripts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: IAM Roles
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; If you attach a new role to an existing EC2 instance, reboot the instance so it recognizes the updated permissions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  EC2 Role
&lt;/h4&gt;

&lt;p&gt;In the AWS Console search &lt;strong&gt;Roles&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Create role&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trusted Entity:&lt;/strong&gt; EC2 &amp;gt; EC2 use case.&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%2Ffx3h3xk6epg4ed7drlsd.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%2Ffx3h3xk6epg4ed7drlsd.png" alt="trusted-entity" width="800" height="543"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Next&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add Permissions:&lt;/strong&gt; Attach the AmazonEC2RoleforAWSCodeDeploy policy.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Next&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Provide a name for the role.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EC2CodeDeployRole&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Review your settings then click "Create role".&lt;/p&gt;

&lt;h4&gt;
  
  
  CodeDeploy Role
&lt;/h4&gt;

&lt;p&gt;Click &lt;strong&gt;Create role&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trusted Entity:&lt;/strong&gt; EC2 &amp;gt; EC2 use case.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Next&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add Permissions:&lt;/strong&gt; Attach AWSCodeDeployRole.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Next&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Provide a name for the role.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CodeDeployRole&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Review your settings then click "Create role".&lt;/p&gt;

&lt;p&gt;Edit the trusted policy using:&lt;/p&gt;

&lt;p&gt;Search for the newly created &lt;strong&gt;CodeDeployRole&lt;/strong&gt; and select.&lt;/p&gt;

&lt;p&gt;Choose the &lt;strong&gt;Trust relationships&lt;/strong&gt; tab.&lt;/p&gt;

&lt;p&gt;Choose &lt;strong&gt;Edit trust policy&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Copy and paste this trust policy.&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="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"Version"&lt;/span&gt;: &lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;,
    &lt;span class="s2"&gt;"Statement"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
        &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"Sid"&lt;/span&gt;: &lt;span class="s2"&gt;""&lt;/span&gt;,
            &lt;span class="s2"&gt;"Effect"&lt;/span&gt;: &lt;span class="s2"&gt;"Allow"&lt;/span&gt;,
            &lt;span class="s2"&gt;"Principal"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="s2"&gt;"Service"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
                    &lt;span class="s2"&gt;"codedeploy.amazonaws.com"&lt;/span&gt;
                &lt;span class="o"&gt;]&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;,
            &lt;span class="s2"&gt;"Action"&lt;/span&gt;: &lt;span class="s2"&gt;"sts:AssumeRole"&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Click "Update policy".&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: EC2 Configuration
&lt;/h3&gt;

&lt;p&gt;In the AWS console search up &lt;strong&gt;EC2&lt;/strong&gt; &amp;gt; &lt;strong&gt;Launch Instance.&lt;/strong&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Launch an Instance
&lt;/h4&gt;

&lt;p&gt;Keep default settings.&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%2Fo55sqrhlyq14w0prvhuc.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%2Fo55sqrhlyq14w0prvhuc.png" alt="base-image" width="800" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Name and tags:&lt;/strong&gt; Name your instance.&lt;br&gt;
&lt;strong&gt;Application and OS Images:&lt;/strong&gt; Ubuntu&lt;br&gt;
&lt;strong&gt;Instance type:&lt;/strong&gt; t2.micro&lt;br&gt;
&lt;strong&gt;Key pair (login)&lt;/strong&gt;: Create or use an existing key pair.&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%2F5wgb0ny4fg49nqpdr8kq.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%2F5wgb0ny4fg49nqpdr8kq.png" alt="network-settings" width="800" height="478"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Network settings:&lt;/strong&gt; Use or create a security group allowing SSH (port 22) from your IP and HTTP (port 80) from the internet..&lt;/p&gt;

&lt;p&gt;Under &lt;strong&gt;Advanced details&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IAM instance profile:&lt;/strong&gt; EC2CodeDeployRole&lt;/p&gt;

&lt;p&gt;User data: Install the CodeDeploy agent on launch. Adjust region endpoints if you’re not using us-east-1:&lt;/p&gt;

&lt;p&gt;Use this &lt;a href="https://docs.aws.amazon.com/codedeploy/latest/userguide/resource-kit.html#resource-kit-bucket-names" rel="noopener noreferrer"&gt;link&lt;/a&gt; to get your region to fetch your appropriate code-deploy region identifier&lt;/p&gt;

&lt;p&gt;Edit this line in the script  "aws-codedeploy-us-east-1.s3.us-east-1." with your appropriate region and identifier.&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%2F9sexqsp88zsl7x67fzyy.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%2F9sexqsp88zsl7x67fzyy.png" alt="User data" width="800" height="604"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Copy and paste the code below.&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;

&lt;span class="c"&gt;# Update system&lt;/span&gt;
apt update &lt;span class="nt"&gt;-y&lt;/span&gt;

&lt;span class="c"&gt;# Install CodeDeploy Agent&lt;/span&gt;
apt &lt;span class="nb"&gt;install &lt;/span&gt;ruby-full &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; /home/ubuntu
wget https://aws-codedeploy-us-east-1.s3.us-east-1.amazonaws.com/latest/install
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x ./install
./install auto

systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;codedeploy-agent
systemctl start codedeploy-agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Click &lt;strong&gt;Launch Instance&lt;/strong&gt; and wait until it’s initialized..&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: CodeDeploy Setup
&lt;/h3&gt;

&lt;p&gt;We are setting up CodeDeploy first because it makes it easier to use and reference for later in the CodePipeline Section.&lt;/p&gt;

&lt;p&gt;In the AWS search and select &lt;strong&gt;CodeDeploy&lt;/strong&gt; &amp;gt; &lt;strong&gt;Create Application&lt;/strong&gt;.&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%2F9aqmo5pntewaahtha0z9.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%2F9aqmo5pntewaahtha0z9.png" alt="deployment-name" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Application name:&lt;/strong&gt; vite-react-deploy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compute platform&lt;/strong&gt; EC2/on-premise&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Click &lt;strong&gt;Create application&lt;/strong&gt;.&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%2F013gqzipin0nliruzevn.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%2F013gqzipin0nliruzevn.png" alt="deployment-group" width="800" height="274"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Create deployment group&lt;/strong&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Create deployment group
&lt;/h4&gt;

&lt;p&gt;![deployemnt-groutp(&lt;a href="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7tr2q3qqmwan6610vcew.png" rel="noopener noreferrer"&gt;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7tr2q3qqmwan6610vcew.png&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Deployment group name: vite-react-group&lt;br&gt;
Service role: CodeDeployRole (Select the service role we created earlier)&lt;/p&gt;

&lt;p&gt;Deployment type: In-place&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%2Fpoxr1brkq403klei85r1.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%2Fpoxr1brkq403klei85r1.png" alt="Environment-config" width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select "Amazon Instances".&lt;br&gt;
Key: Name&lt;br&gt;
Value: Select your Instance&lt;/p&gt;

&lt;p&gt;Leave the default settings for Agent configuration with AWS Systems Manager and Deployment settings.&lt;/p&gt;
&lt;h5&gt;
  
  
  Load balancer
&lt;/h5&gt;

&lt;p&gt;&lt;strong&gt;Enable load balancing:&lt;/strong&gt; Uncheck selection&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Create deployment group&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Review changes than Click &lt;strong&gt;Create deployment&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Now on the creating a deployment page we can leave this section empty(exit page) and just move on to the next section because CodePipeline will handle the deployment for.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;
  
  
  AppSpec Basics
&lt;/h4&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.0&lt;/span&gt;
&lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;linux&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="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/my-react-app/dist&lt;/span&gt;
    &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/www/html/&lt;/span&gt;
&lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;BeforeInstall&lt;/span&gt;&lt;span class="pi"&gt;:&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;scripts/before_install.sh&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;runas&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;AfterInstall&lt;/span&gt;&lt;span class="pi"&gt;:&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;scripts/after_install.sh&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;runas&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;ApplicationStart&lt;/span&gt;&lt;span class="pi"&gt;:&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;scripts/start_application.sh&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;runas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h5&gt;
  
  
  AppSpec Configuration Explained
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;source: /my-react-app/dist&lt;/code&gt; is where CodeBuild places the compiled files.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;destination: /var/www/html/&lt;/code&gt; is where Nginx serves static files.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hooks:&lt;/code&gt; are scripts you define to run before/after install and on app start.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Step 5 CodePipeline Integration
&lt;/h3&gt;

&lt;p&gt;In the AWS console search &lt;strong&gt;CodePipeline&lt;/strong&gt; &amp;gt; &lt;strong&gt;Create Pipeline&lt;/strong&gt;&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%2F8l6q2php3r18btk9cf0a.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%2F8l6q2php3r18btk9cf0a.png" alt="pipeline-step1" width="800" height="312"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This option allows us to build a custom pipeline to deploy from GitHub.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Next&lt;/strong&gt;.&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%2F9d5u7991pld2d7kmbeff.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%2F9d5u7991pld2d7kmbeff.png" alt="pipeline-settings" width="800" height="749"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name the Pipeline:&lt;/strong&gt; Choose a meaningful name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service Role:&lt;/strong&gt; Select New service role to allow AWS to create a role automatically.&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%2Fexnw2d023p8ppo614088.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%2Fexnw2d023p8ppo614088.png" alt="pipeline-step2.2" width="800" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Advanced settings:&lt;/strong&gt; Use the default location to allow AWS to create an S3 bucket for CodePipeline artifacts, which will maintain deployment history.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Source
&lt;/h4&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%2Fyjvh8uarv6zwwg2chmh2.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%2Fyjvh8uarv6zwwg2chmh2.png" alt="source" width="800" height="688"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have previously connected to GitHub so I will be using that connection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For New Connections:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Connect to GitHub&lt;/strong&gt;.&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%2Fxjs59qnrzck9wu4esa8h.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%2Fxjs59qnrzck9wu4esa8h.png" alt="github-app-connection" width="800" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Name your connection and click &lt;strong&gt;Connect to Github&lt;/strong&gt;.&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%2F0wejg7zcchncuj8uqaj4.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%2F0wejg7zcchncuj8uqaj4.png" alt="aws-connector" width="800" height="879"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Authorize AWS Connector for Github&lt;/strong&gt; to grant AWS access to your GitHub repository.&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%2F1q4k4u5qkt8kmz1tfwyh.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%2F1q4k4u5qkt8kmz1tfwyh.png" alt="basic-connect" width="800" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A basic GitHub user connection suffices for our deployment pipeline.&lt;/li&gt;
&lt;li&gt;If working with multiple organizations, select the specific user or organization repositories you want to use.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Connect&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Configure Source Stage
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Source Provider:&lt;/strong&gt; GitHub (via GitHub App)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connection:&lt;/strong&gt; Your GitHub connection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repository Name:&lt;/strong&gt; Select your repository&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Default Branch:&lt;/strong&gt; main&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output Artifact Format:&lt;/strong&gt; Default settings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Webhook Events:&lt;/strong&gt; Trigger the pipeline on push and pull request events.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Next&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Build Stage
&lt;/h4&gt;

&lt;p&gt;Here is where we will be the vite app using the buildspec.yml. so we will be creating a CodeBuild project to reference our file in the repo.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Create project&lt;/strong&gt;&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%2F7lrc3x5puwh93c3ofpbf.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%2F7lrc3x5puwh93c3ofpbf.png" alt="buildstage" width="800" height="787"&gt;&lt;/a&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%2F7p3jwt85d90h73x0kxzq.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%2F7p3jwt85d90h73x0kxzq.png" alt="build-stage2" width="800" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Keep the default settings.&lt;/p&gt;

&lt;p&gt;Name your project.&lt;/p&gt;

&lt;p&gt;A new service role will be create for us through AWS.&lt;/p&gt;
&lt;h5&gt;
  
  
  Buildspec
&lt;/h5&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%2Fi0wvrr9cxat0o0xbtt8u.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%2Fi0wvrr9cxat0o0xbtt8u.png" alt="build-spec" width="800" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build specifications:&lt;/strong&gt; Use a buildspec file&lt;/p&gt;

&lt;p&gt;Here is where your buildspec.yml in the repo will be referenced.&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;20&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;cd ./my-react-app&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;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;npm run build&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;my-react-app/dist/**/*'&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;appspec.yml'&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;scripts/**/*'&lt;/span&gt;
  &lt;span class="na"&gt;discard-paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;no&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(This builds your Vite app and includes the dist, appspec.yml, and scripts folder in the output artifacts.)&lt;/p&gt;

&lt;h6&gt;
  
  
  BuildSpec Configuration Explained
&lt;/h6&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cd ./my-react-app&lt;/code&gt; will move to the react app folder&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm install&lt;/code&gt; will install all our node dependencies&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm build&lt;/code&gt; will build our production static vite app into the dist folder&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;'./my-react-app/dist'&lt;/code&gt; line is to ensure all files in the build output are copied.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;'**/*'&lt;/code&gt; will include all build files in the root folder&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;appspec.yml&lt;/code&gt; and &lt;code&gt;scripts/**/*&lt;/code&gt; are explicitly listed to ensure they're included&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%2Ft9oz7vyr7lf6fkqar5i9.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%2Ft9oz7vyr7lf6fkqar5i9.png" alt="codebuild-logs" width="800" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Continue to CodePipeline&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build provider:&lt;/strong&gt; Other build providers (AWS CodeBuild)&lt;br&gt;
&lt;strong&gt;Build Type&lt;/strong&gt;: Single Type&lt;br&gt;
&lt;strong&gt;Region:&lt;/strong&gt; Select your Region&lt;br&gt;
&lt;strong&gt;Input artifacts:&lt;/strong&gt; Keep defaults&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Next&lt;/strong&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Deploy Stage
&lt;/h4&gt;

&lt;p&gt;Here is how we will deploy to our EC2 instance. We will be using the CodeDeploy application we created earlier.&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%2F6mxpao11uxy8g4paevyl.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%2F6mxpao11uxy8g4paevyl.png" alt="deploy-build" width="800" height="737"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deploy provider:&lt;/strong&gt; CodeDeploy&lt;br&gt;
&lt;strong&gt;Region:&lt;/strong&gt; Select your region&lt;br&gt;
&lt;strong&gt;Input artifacts:&lt;/strong&gt; Keep defaults&lt;br&gt;
&lt;strong&gt;Application name:&lt;/strong&gt; vite-react-deploy&lt;br&gt;
&lt;strong&gt;Deployment group:&lt;/strong&gt; vite-react-group&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Next&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Review your settings than click "Create pipeline"&lt;/p&gt;

&lt;p&gt;Your Pipeline will now run an each stage we worked on will get built.&lt;/p&gt;

&lt;p&gt;Now head on over to your instance an get the public address our site should now be deployed on the EC2 Instance.&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%2Fpsfwku0j3r3s43broct4.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%2Fpsfwku0j3r3s43broct4.png" alt="deployed-site" width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;By following these steps, you’ve implemented a fully automated CI/CD pipeline using CodePipeline as the orchestrator an AWS Codebuild and CodeDeploy, EC2, and GitHub. This setup transforms your deployment process from fragile and manual to consistent, secure, and fast. You’ve minimized human error, introduced a clear rollback mechanism, and set the stage for more advanced enhancements—like adding integration tests, canary deployments, or blue/green strategies.&lt;/p&gt;

&lt;p&gt;This is a crucial step toward embracing modern DevOps practices and ensures any team can deliver value to users with speed and confidence.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Automating Website Deployments with AWS CodePipeline and S3 No Upload</title>
      <dc:creator>David Hyppolite</dc:creator>
      <pubDate>Fri, 20 Dec 2024 14:13:25 +0000</pubDate>
      <link>https://dev.to/dhayv/automating-website-deployments-with-aws-codepipeline-and-s3-no-upload-el8</link>
      <guid>https://dev.to/dhayv/automating-website-deployments-with-aws-codepipeline-and-s3-no-upload-el8</guid>
      <description>&lt;p&gt;A step-by-step implementation of automated deployments using AWS CodePipeline, S3 static website hosting, and GitHub integration. This project demonstrates CI/CD pipeline creation, IAM security configuration, and version-controlled deployments - replacing traditional FTP uploads with a zero-touch solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Business Benefits&lt;/li&gt;
&lt;li&gt;The Problem&lt;/li&gt;
&lt;li&gt;The Solution: AWS CodePipeline&lt;/li&gt;
&lt;li&gt;Implementation Guide&lt;/li&gt;
&lt;li&gt;Making Changes and Verifying Deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Initial Architecture
&lt;/h3&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%2Fhhc8ojuzon5atmo5zuxb.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%2Fhhc8ojuzon5atmo5zuxb.png" alt="Lucid Architecture" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Business Value
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Business Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero Manual Intervention:&lt;/strong&gt; Automates the deployment process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complete Deployment History:&lt;/strong&gt; Maintains a detailed record of all deployments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quick Rollback Capability:&lt;/strong&gt; Easily revert to previous versions if needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secure Access Control:&lt;/strong&gt; Uses IAM roles to manage permissions securely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduced Human Error:&lt;/strong&gt; Minimizes mistakes associated with manual uploads.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Your retail company currently maintains its static website content on-premises, relying on manual FTP uploads for updates. This manual approach often results in incomplete or inconsistent deployments, making it difficult to maintain a reliable version history or roll back changes when issues arise. With holiday promotions driving sudden traffic spikes, the site frequently struggles under the burden of rushed, ad-hoc updates—leading to downtime, frustrated customers, and missed revenue opportunities.&lt;/p&gt;

&lt;p&gt;By introducing a fully automated CI/CD pipeline, you can eliminate the need for FTP uploads, ensure a consistent deployment history, and improve your site’s resiliency, especially during critical high-traffic periods.&lt;/p&gt;

&lt;h3&gt;
  
  
  Common Issues with FTP Deployments
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Incomplete Uploads:&lt;/strong&gt; Can break the website if files aren't fully uploaded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lack of Deployment History:&lt;/strong&gt; No records of what was deployed and when.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Change Tracking:&lt;/strong&gt; Difficult to identify who made specific changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Rollback Capability:&lt;/strong&gt; Unable to revert to previous versions easily.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Vulnerabilities:&lt;/strong&gt; FTP lacks robust security measures, exposing the site to potential threats.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Solution: AWS CodePipeline
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Key Takeaways
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automated Deployments:&lt;/strong&gt; Eliminate the risks associated with FTP.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version Control:&lt;/strong&gt; Provides a comprehensive deployment history.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IAM Roles:&lt;/strong&gt; Ensure secure access and permissions management.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S3 Static Hosting:&lt;/strong&gt; Enables reliable and scalable content delivery.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CodePipeline Automation:&lt;/strong&gt; Streamlines the entire deployment process from source to deployment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While many tutorials jump straight to CloudFront and custom domains, let's focus on the fundamental problem: automating deployments reliably.&lt;/p&gt;

&lt;p&gt;Instead of manually uploading files to S3, this guide demonstrates a true zero-touch process. We'll start with an empty S3 bucket and let CodePipeline handle the entire deployment lifecycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation Guide
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 1:&lt;/strong&gt; GitHub Repository Setup
&lt;/h3&gt;

&lt;p&gt;Create your HTML file by running the following command in your repository:&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;touch &lt;/span&gt;index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2F6cgk39ypnagsdu6sof67.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%2F6cgk39ypnagsdu6sof67.png" alt="basic-index.html" width="800" height="561"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Copy and paste the provided HTML code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Codepipeline Demo&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;H1&amp;gt;&lt;/span&gt;Deploy via AWS Codepipeline&lt;span class="nt"&gt;&amp;lt;/H1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Deployment version: &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;1&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Push your code to your repo.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step&lt;/strong&gt; 2: S3 Configuration
&lt;/h3&gt;

&lt;p&gt;First, create an S3 bucket with static website hosting:&lt;/p&gt;

&lt;p&gt;In the AWS Console Search "&lt;strong&gt;S3&lt;/strong&gt;".&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%2F298g4yt0f7rwdeii22wz.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%2F298g4yt0f7rwdeii22wz.png" alt="S3 landing page" width="800" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the "Create Bucket" button.&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%2F92axvdf7qooo8jqs5ef0.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%2F92axvdf7qooo8jqs5ef0.png" alt="bucket name" width="800" height="248"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bucket Name:&lt;/strong&gt; myawsbucket-demov1 (ensure the name is unique).&lt;/p&gt;

&lt;p&gt;Keep Default settings except:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Uncheck the box:&lt;/strong&gt; Block All Public Access.&lt;/li&gt;
&lt;li&gt;Acknowledge the previous selection to Enable Public Access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bucket Versioning:&lt;/strong&gt; Enable.&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%2Fyu1arrmeny02nxoiunkc.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%2Fyu1arrmeny02nxoiunkc.png" alt="public access" width="800" height="306"&gt;&lt;/a&gt;&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%2Fi5x0s0vu8prt352znzwz.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%2Fi5x0s0vu8prt352znzwz.png" alt="Bucket versioning" width="800" height="107"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enable Versioning (we are simulating a company its important to have versioning enabled for accidental deletes and to track history of changes)&lt;/p&gt;

&lt;p&gt;Accept the remaining defaults and create the bucket.&lt;/p&gt;

&lt;h4&gt;
  
  
  Enable Static Website Hosting
&lt;/h4&gt;

&lt;p&gt;After creating your bucket, select it and navigate to the &lt;strong&gt;Properties&lt;/strong&gt; tab. Scroll down to &lt;strong&gt;Static website hosting&lt;/strong&gt; and click &lt;strong&gt;Edit&lt;/strong&gt;.&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%2Fh3hxjng7wbdo3xrnpfsf.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%2Fh3hxjng7wbdo3xrnpfsf.png" alt="static website s3" width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Static website hosting:&lt;/strong&gt; enable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hosting type:&lt;/strong&gt; Host a static website&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Index document:&lt;/strong&gt; index.html&lt;/li&gt;
&lt;li&gt;Save Changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We Have created a bucket but we don't have the necessary permissions to access the items in the bucket.&lt;/p&gt;

&lt;h4&gt;
  
  
  Configure Bucket Policy
&lt;/h4&gt;

&lt;p&gt;Navigate to the &lt;strong&gt;Permissions&lt;/strong&gt; tab and scroll to &lt;strong&gt;Bucket Policy&lt;/strong&gt;.&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%2F50omfx72imwqjgyrudzc.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%2F50omfx72imwqjgyrudzc.png" alt="policy section" width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Edit&lt;/strong&gt; and add the following policy to grant public read access to your objects:&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;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PublicReadGetObject"&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;"Principal"&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="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="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:s3:::my-bucket/*"&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;Adapt the &lt;strong&gt;Resource&lt;/strong&gt; to match your bucket name, then save changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 3:&lt;/strong&gt; CodePipeline Setup
&lt;/h3&gt;

&lt;p&gt;In the AWS Console, search for and select &lt;strong&gt;CodePipeline&lt;/strong&gt;.&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%2Frhetwin48387j85lioli.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%2Frhetwin48387j85lioli.png" alt="code-pipeline-initial-screen" width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click Create &lt;strong&gt;Pipeline&lt;/strong&gt;.&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%2F8l6q2php3r18btk9cf0a.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%2F8l6q2php3r18btk9cf0a.png" alt="pipeline-step1" width="800" height="312"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This option allows us to build a custom pipeline to deploy from GitHub to s3.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Next&lt;/strong&gt;.&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%2Ftdcvq5e9omwz011vj5pr.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%2Ftdcvq5e9omwz011vj5pr.png" alt="pipeline-step2" width="800" height="739"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Pipeline Settings
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name the Pipeline:&lt;/strong&gt; Choose a meaningful name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service Role:&lt;/strong&gt; Select New service role to allow AWS to create a role automatically.&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%2Fexnw2d023p8ppo614088.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%2Fexnw2d023p8ppo614088.png" alt="pipeline-step2.2" width="800" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Advanced settings:&lt;/strong&gt; Use the default location to allow AWS to create an S3 bucket for CodePipeline artifacts, which will maintain deployment history.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Add Source Stage
&lt;/h4&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%2Fl22l7xvoq8ut7wh5w04t.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%2Fl22l7xvoq8ut7wh5w04t.png" alt="Image-description" width="800" height="644"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Connect to GitHub&lt;/strong&gt;.&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%2Fxjs59qnrzck9wu4esa8h.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%2Fxjs59qnrzck9wu4esa8h.png" alt="github-app-connection" width="800" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Name your connection and click &lt;strong&gt;Connect to Github&lt;/strong&gt;.&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%2F0wejg7zcchncuj8uqaj4.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%2F0wejg7zcchncuj8uqaj4.png" alt="aws-connector" width="800" height="879"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Authorize AWS Connector for Github&lt;/strong&gt; to grant AWS access to your GitHub repository.&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%2F1q4k4u5qkt8kmz1tfwyh.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%2F1q4k4u5qkt8kmz1tfwyh.png" alt="basic-connect" width="800" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A basic GitHub user connection suffices for this static website deployment pipeline.&lt;/li&gt;
&lt;li&gt;If working with multiple organizations, select the specific user or organization repositories you want to use.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Connect&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Configure Source Stage
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Source Provider:&lt;/strong&gt; GitHub (via GitHub App)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connection:&lt;/strong&gt; Your GitHub connection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repository Name:&lt;/strong&gt; Select your repository&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Default Branch:&lt;/strong&gt; main&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output Artifact Format:&lt;/strong&gt; Default settings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Webhook Events:&lt;/strong&gt; Trigger the pipeline on push and pull request events.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Next&lt;/strong&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%2Fwt69syzf54d9w2v55czh.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%2Fwt69syzf54d9w2v55czh.png" alt="build-stage" width="800" height="598"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Skip Build and Test Stages
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Since you're deploying HTML files, you can skip the build stage.&lt;/li&gt;
&lt;li&gt;Optionally, skip the test stage.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Deploy stage
&lt;/h4&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%2Fi4tzc66p806shvk3v7od.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%2Fi4tzc66p806shvk3v7od.png" alt="deploy-stage" width="800" height="736"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deploy Provider:&lt;/strong&gt; Amazon S3&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Region:&lt;/strong&gt; Your chosen AWS region&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bucket:&lt;/strong&gt; Select your static hosted S3 bucket&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S3 Object Key:&lt;/strong&gt; Leave blank&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extract file before deploy:&lt;/strong&gt; Checked&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Next&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Review and Create Pipeline
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Review all settings and click &lt;strong&gt;Create Pipeline&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step4:&lt;/strong&gt; Testing and Verification
&lt;/h3&gt;

&lt;p&gt;Pipeline automatically starts&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%2Fkh14tbz8q82qahw7nuff.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%2Fkh14tbz8q82qahw7nuff.png" alt="pipeline-queue" width="800" height="436"&gt;&lt;/a&gt;&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%2Fd7lwcw9gogstdm39014b.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%2Fd7lwcw9gogstdm39014b.png" alt="pipeline-done" width="" height=""&gt;&lt;/a&gt;&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%2Fxjmtekicwu5516ueimsp.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%2Fxjmtekicwu5516ueimsp.png" alt="pipeline-success-details" width="800" height="460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The pipeline creation process should complete quickly.&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%2Fs9j4z7623b3y2qxy2a6h.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%2Fs9j4z7623b3y2qxy2a6h.png" alt="s3-website-endpoint" width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, we've created a zero-touch solution from GitHub to S3, where files are automatically pulled from the repository without manual uploads.&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%2Fgyzj50yoxj3x5iq5rkv8.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%2Fgyzj50yoxj3x5iq5rkv8.png" alt="first-s3-pull-fromgithub" width="800" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With versioning enabled, you can track deployments:&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%2F4uav1w1bu2v2w831are0.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%2F4uav1w1bu2v2w831are0.png" alt="first-upload" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Making Changes and Verifying Deployment
&lt;/h2&gt;

&lt;p&gt;Make a change in your repository with a commit message like "Update index.html" to update the deployment version to 2.&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%2Fn43q7ajazr7as7hdroeo.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%2Fn43q7ajazr7as7hdroeo.png" alt="github-changes" width="800" height="233"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CodePipeline will automatically detect the changes:&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%2Fsbjbjsog86gxtd22n5hg.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%2Fsbjbjsog86gxtd22n5hg.png" alt="code-pipeline-changes" width="800" height="233"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;View the updated site reflecting the changes:&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%2Fa5g2reqkc09rp181rms7.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%2Fa5g2reqkc09rp181rms7.png" alt="site-changes" width="800" height="228"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;By following this guide, you've successfully set up an automated, secure, and efficient deployment pipeline using AWS CodePipeline, S3 static website hosting, and GitHub. This zero-touch solution not only streamlines your deployment process but also enhances security, provides version control, and ensures high availability during peak traffic periods.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cicd</category>
      <category>devops</category>
    </item>
    <item>
      <title>Zero-Touch AWS Deployments: Building a Modern CI/CD Pipeline with GitHub Actions and CloudFront</title>
      <dc:creator>David Hyppolite</dc:creator>
      <pubDate>Mon, 16 Dec 2024 14:35:00 +0000</pubDate>
      <link>https://dev.to/dhayv/zero-touch-aws-deployments-building-a-modern-cicd-pipeline-with-github-actions-and-cloudfront-308l</link>
      <guid>https://dev.to/dhayv/zero-touch-aws-deployments-building-a-modern-cicd-pipeline-with-github-actions-and-cloudfront-308l</guid>
      <description>&lt;h2&gt;
  
  
  Tech Stack Deep Dive
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD &amp;amp; DevOps:&lt;/strong&gt; GitHub Actions for automation, self-hosted runners for deployment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Containerization:&lt;/strong&gt; Docker for consistent backend deployments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud Infrastructure:&lt;/strong&gt; AWS S3, CloudFront, EC2, ACM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nginx:&lt;/strong&gt; Reverse proxy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; MongoDB Atlas for reliable data persistence&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security &amp;amp; Auth:&lt;/strong&gt; ACM for SSL, OIDC for AWS Role Assumption&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; React, Vite&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; Python&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/dhayv/WiseWalletWin" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/dhayv/WiseWalletWin/blob/main/.github/workflows/CI_CD.yml" rel="noopener noreferrer"&gt;Github Actions Workflow&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Wake-Up Call
&lt;/h2&gt;

&lt;p&gt;It started with a simple need: I wanted to know exactly how much money I needed to set aside before payday to cover my next two weeks. Simple enough, right? I built &lt;a href="//wisewalletwin.com"&gt;Wisewallet&lt;/a&gt; to solve this problem, but little did I know this straightforward application would become my crash course in modern cloud architecture and DevOps practices.&lt;/p&gt;

&lt;p&gt;Picture this: It's 10 PM, and I'm staring at my terminal, hands slightly shaking as I rsync changes. My EC2 had to be rebooted instance, taking my SQLite database with it into the digital void. All that data, gone. Again. My monolithic setup - a React frontend, Python backend, SQLite database, and Nginx, all crammed onto a single EC2 instance - was starting to feel like a house of cards.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Breaking Point
&lt;/h2&gt;

&lt;p&gt;Every deployment was a small adventure in anxiety. I'd SSH into my server, fingers crossed, hoping my rsync command wouldn't miss any crucial files or add files that shouldn't belong:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rsync &lt;span class="nt"&gt;-avz&lt;/span&gt; &lt;span class="nt"&gt;--exclude&lt;/span&gt; &lt;span class="s1"&gt;'node_modules'&lt;/span&gt; &lt;span class="nt"&gt;--exclude&lt;/span&gt; &lt;span class="s1"&gt;'.git'&lt;/span&gt; &lt;span class="nt"&gt;--exclude&lt;/span&gt; &lt;span class="s1"&gt;'.env'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--exclude&lt;/span&gt; &lt;span class="s1"&gt;'venv'&lt;/span&gt; &lt;span class="nt"&gt;--exclude&lt;/span&gt; &lt;span class="s1"&gt;'__pycache__'&lt;/span&gt; &lt;span class="nt"&gt;--exclude&lt;/span&gt; &lt;span class="s1"&gt;'db.sqlite3'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"ssh -i ~/.ssh/id_rsa"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;.&lt;/span&gt; ubuntu@ec2-24-127-53:~/app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then came the ritual of commands:&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;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt upgrade
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl stop frontend.service
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl stop backend-container.service
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/app/frontend
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fl9u2cyrljew13tfi791v.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%2Fl9u2cyrljew13tfi791v.png" alt="Monolithic" width="800" height="550"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each step was another chance for something to go wrong. And things did go wrong. Often. The EC2 instance would run out of storage during npm install. The build would timeout. The database would vanish with instance restarts. It was like playing DevOps roulette, and I was losing sleep over it. The app was not usable - it was a burden for months, constantly having to add the information over again.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Rumination Phase
&lt;/h2&gt;

&lt;p&gt;I spent weeks turning the problem over in my mind. The data loss issue haunted me the most. I kept thinking, "There has to be a better way to handle persistence." That's when I started researching MongoDB Atlas. The idea of a managed database service was appealing - no more data loss anxiety, automatic backups, and scaling without the headache. But I wrestled with the decision - was I overcomplicating things? Was I just adding unnecessary complexity?&lt;/p&gt;

&lt;p&gt;Then there was the frontend deployment issue. Every time I needed to update the UI, I had to run &lt;code&gt;npm run build&lt;/code&gt; on the EC2 instance. It was slow, resource-intensive, and frankly, felt wrong. The lightbulb moment came during a particularly frustrating deployment: "Why am I building static files on a server? These could live anywhere!"&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture Evolution
&lt;/h2&gt;

&lt;p&gt;Current CI/CD pipeline&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%2Fao263lnkd2aroopdcurf.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%2Fao263lnkd2aroopdcurf.png" alt="New Ci/cd" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Breaking Free from the Monolith
&lt;/h3&gt;

&lt;p&gt;The first major decision was moving to MongoDB Atlas. It wasn't just about preventing data loss - it was about peace of mind. Knowing my data would survive any EC2 mishaps was worth the migration effort. The process taught me about data modeling, replication, and the true value of managed services. It took some time to change my code over from SQL to NoSQL but it was worth it.&lt;/p&gt;

&lt;p&gt;For the frontend, S3 was an obvious choice, but the real game-changer was CloudFront. I remember the exact moment the decision clicked. I was comparing costs between running an Application Load Balancer and using CloudFront and saw how the configurations meant I could use it for the backend too!&lt;/p&gt;

&lt;p&gt;This led to an interesting architecture: two CloudFront distributions - one for the S3-hosted frontend and another for the EC2 backend. It was unconventional, perhaps, but it solved multiple problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTPS everywhere without managing Nginx certificates&lt;/li&gt;
&lt;li&gt;Caching at the edge&lt;/li&gt;
&lt;li&gt;Cost-effective compared to an ALB for my use case&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The CI/CD Epiphany
&lt;/h3&gt;

&lt;p&gt;The manual deployment process still bothered me. I wanted zero SSH access needed for routine deployments, and I wanted to essentially mimic the exact steps I did manually but automated without errors or forgetting anything. The solution came in layers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;First, containerize the backend with Docker:&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;dockerize-backend&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@v4.1.7&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;Setup Docker Buildx&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;docker/setup-buildx-action@v3.4.0&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;Login to Docker Hub&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;docker/login-action@v3.2.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;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DOCKER_USERNAME }}&lt;/span&gt;
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DOCKER_PASSWORD }}&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 and push Docker image&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;docker/build-push-action@v6.4.1&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;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./Backend/&lt;/span&gt;
          &lt;span class="na"&gt;push&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;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DOCKER_USERNAME }}/wisewallet-backend:latest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key was sending the built image to DockerHub to be held until needed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;But how to get the container onto EC2 without SSH? This stumped me for days. The breakthrough came when I discovered self-hosted GitHub Actions runners. I could install a runner on my EC2 instance, waiting to pull and run the latest Docker image. It was elegant in its simplicity:&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dockerize-backend&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-ec2&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;self-hosted&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;Pull image from Docker Hub&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;docker pull ${{ secrets.DOCKER_USERNAME }}/wisewallet-backend:latest&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 old Container&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;docker rm -f ${{ secrets.DOCKER_USERNAME }}/wisewallet-backend:latest&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;Run docker container&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;sudo systemctl restart backend.service&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then came the systemd services - one to keep the runner alive, another to manage the container lifecycle. No more manual intervention needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing a Self-Hosted Runner
&lt;/h3&gt;

&lt;p&gt;The process is straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to repo under actions tab&lt;/li&gt;
&lt;li&gt;Under management &amp;lt; runners&lt;/li&gt;
&lt;li&gt;Hit new Runners &amp;lt; new self-hosted runner&lt;/li&gt;
&lt;li&gt;Runners are versatile for Linux, Windows, macOS&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's the runner service configuration:&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="o"&gt;[&lt;/span&gt;Unit]
&lt;span class="nv"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Github actions runner
&lt;span class="nv"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;network.target

&lt;span class="o"&gt;[&lt;/span&gt;Service]
&lt;span class="nv"&gt;User&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ubuntu
&lt;span class="nv"&gt;WorkingDirectory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/ubuntu/actions-runner/
&lt;span class="nv"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/ubuntu/actions-runner/run.sh
&lt;span class="nv"&gt;Restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;always
&lt;span class="nv"&gt;RestartSec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3

&lt;span class="o"&gt;[&lt;/span&gt;Install]
&lt;span class="nv"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;multi-user.target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the backend service:&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="o"&gt;[&lt;/span&gt;Unit]
&lt;span class="nv"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Docker &lt;span class="k"&gt;for &lt;/span&gt;Backend Service
&lt;span class="nv"&gt;Requires&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;docker.service
&lt;span class="nv"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;network.target docker.service

&lt;span class="o"&gt;[&lt;/span&gt;Service]
&lt;span class="nv"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;simple
&lt;span class="nv"&gt;User&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ubuntu
&lt;span class="nv"&gt;WorkingDirectory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/ubuntu/
&lt;span class="nv"&gt;ExecStartPre&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/bin/docker pull image/backend:latest
&lt;span class="nv"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/bin/docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; backend &lt;span class="nt"&gt;-p&lt;/span&gt; 8000:8000 &lt;span class="nt"&gt;--env-file&lt;/span&gt; /etc/app.env image/backend:latest uvicorn main:app &lt;span class="nt"&gt;--host&lt;/span&gt; 0.0.0.0
&lt;span class="nv"&gt;ExecStop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/bin/docker stop backend
&lt;span class="nv"&gt;Restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;always
&lt;span class="nv"&gt;EnvironmentFile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/app.env

&lt;span class="o"&gt;[&lt;/span&gt;Install]
&lt;span class="nv"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;multi-user.target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Building the Static Files
&lt;/h3&gt;

&lt;p&gt;No more manual npm run build, npm installs, or timeouts:&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;build-frontend&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;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;   
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&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;AWS_REGION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_REGION }}&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@v4.1.7&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@v4.0.3&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="s"&gt;20.12.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;Cache Node.js modules&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/cache@v4&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;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Frontend/node_modules&lt;/span&gt;
          &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ runner.os }}-node-${{ hashFiles('**/Frontend/package-lock.json') }}&lt;/span&gt;
          &lt;span class="na"&gt;restore-keys&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;${{ runner.os }}-node-&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="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;cd ./Frontend&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 the frontend&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;VITE_APP_API_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.VITE_APP_API_URL }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;cd ./Frontend&lt;/span&gt;
          &lt;span class="s"&gt;npm run build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Security Evolution
&lt;/h3&gt;

&lt;p&gt;The final piece was security. Storing AWS credentials in GitHub secrets felt wrong. That's when I discovered AWS OpenID Connect. The ability to generate temporary credentials just when needed was exactly what I was looking for. It was a perfect example of the principle of least privilege in action. It allowed me to get my static images to S3 and invalidate my CloudFront cache instantly to apply changes - the only stored credentials are fairly 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="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@v4&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;role-to-assume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_ROLE_ARN }}&lt;/span&gt;
          &lt;span class="na"&gt;role-session-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GitHubActionsSession&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;${{ secrets.AWS_REGION }}&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 to S3&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;aws s3 sync ./Frontend/dist/ s3://${{ secrets.AWS_S3_BUCKET }} --region ${{ secrets.AWS_REGION }} --delete&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="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;aws cloudfront create-invalidation \&lt;/span&gt;
            &lt;span class="s"&gt;--distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \&lt;/span&gt;
            &lt;span class="s"&gt;--paths "/index.html"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Results
&lt;/h2&gt;

&lt;p&gt;The transformation has been dramatic. What used to be a stress-inducing manual process is now a simple git push, but with a twist - the deploy is now manual so I can send features once a month. The architecture is distributed but not overly complex. Each component does one thing well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MongoDB Atlas handles data persistence&lt;/li&gt;
&lt;li&gt;S3 and CloudFront manage static content delivery&lt;/li&gt;
&lt;li&gt;Docker provides consistent backend deployments&lt;/li&gt;
&lt;li&gt;GitHub Actions orchestrates everything&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More importantly, I sleep better at night. No more 10 PM panic sessions. No more data loss anxiety. No more deployment roulette.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking Forward
&lt;/h2&gt;

&lt;p&gt;This journey taught me that good architecture isn't about using the latest tech - it's about solving real problems. Each decision was driven by a specific pain point, not just a desire to use cool technology.&lt;/p&gt;

&lt;p&gt;Looking ahead, I'm exploring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Container orchestration with ECS (though for my scale, the current setup works well)&lt;/li&gt;
&lt;li&gt;Integration tests in the CI/CD pipeline&lt;/li&gt;
&lt;li&gt;Potentially going serverless with Lambda&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the biggest lesson? Sometimes the best solutions come from living with the pain long enough to truly understand the problem. Every sleepless night, every failed deployment, every data loss incident - they all contributed to the solution I have today.&lt;/p&gt;

&lt;p&gt;Want to discuss cloud architecture, DevOps practices, or share your own journey? Connect with me on LinkedIn. I'm always eager to learn from others' experiences!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to Fix Next.js CloudFront Permission Denied: Complete Guide to Static Export URL Issues</title>
      <dc:creator>David Hyppolite</dc:creator>
      <pubDate>Fri, 13 Dec 2024 17:25:05 +0000</pubDate>
      <link>https://dev.to/dhayv/how-to-fix-nextjs-cloudfront-permission-denied-complete-guide-to-static-export-url-issues-304k</link>
      <guid>https://dev.to/dhayv/how-to-fix-nextjs-cloudfront-permission-denied-complete-guide-to-static-export-url-issues-304k</guid>
      <description>&lt;h2&gt;
  
  
  Technical Overview
&lt;/h2&gt;

&lt;p&gt;This post demonstrates my expertise in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cloud Infrastructure&lt;/strong&gt;: AWS (CloudFront, S3, Lambda) infrastructure management with production debugging&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modern Web Development&lt;/strong&gt;: Next.js static site optimization and deployment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DevOps Practices&lt;/strong&gt;: Infrastructure as Code with Terraform, CI/CD pipeline automation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Problem Solving&lt;/strong&gt;: Complex debugging of URL routing and permissions in distributed systems&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud Security&lt;/strong&gt;: IAM roles, bucket policies, and CloudFront function implementation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring &amp;amp; Logging&lt;/strong&gt;: Setting up ETL pipeline for CloudFront logs using Lambda&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Tech Stack&lt;/strong&gt;: Next.js, AWS (CloudFront, S3, Lambda), Terraform, Python, JavaScript, CI/CD&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The Engineering Journey&lt;/li&gt;
&lt;li&gt;The Initial Setup&lt;/li&gt;
&lt;li&gt;The Problem&lt;/li&gt;
&lt;li&gt;
Setting Up CloudFront Logging

&lt;ul&gt;
&lt;li&gt;Configuring CloudFront Access&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Setting Up Amazon Lambda

&lt;ul&gt;
&lt;li&gt;Adding S3 Permissions&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Creating the CloudFront Function

&lt;ul&gt;
&lt;li&gt;Deploying the Function&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Key Takeaways &amp;amp; Troubleshooting Lessons

&lt;ul&gt;
&lt;li&gt;What Went Wrong&lt;/li&gt;
&lt;li&gt;Troubleshooting Approach&lt;/li&gt;
&lt;li&gt;Technical Lessons&lt;/li&gt;
&lt;li&gt;Prevention for Others&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Engineering Journey
&lt;/h2&gt;

&lt;p&gt;Being an engineer, you're always learning things on the fly, and when you're hosting your own services, you're always running into unexpected bugs.&lt;/p&gt;

&lt;p&gt;I hope this post helps somebody out there. Typically, issues like these are due to mismatched URL paths between what CloudFront expects and what it's getting back from your S3 bucket.&lt;/p&gt;

&lt;p&gt;It's harder to know what to expect in production, especially in situations like these where you can't locally simulate the architecture to address edge cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&amp;lt;Error&amp;gt;
  &amp;lt;Code&amp;gt;AccessDenied&amp;lt;/Code&amp;gt;
  &amp;lt;Message&amp;gt;Access Denied&amp;lt;/Message&amp;gt;
  &amp;lt;RequestId&amp;gt;3W0PC46EVXQ&amp;lt;/RequestId&amp;gt;
  &amp;lt;HostId&amp;gt;kz2tzcKKQpl+s0W8fpk+glo4QGR1p4V91ky9WRLTmHK8AgloIvPKsA1/80&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;/HostId&amp;gt;
&amp;lt;/Error&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the tricky part: when users navigate through the site using links, everything works perfectly because the links generated by Next.js include the correct file extension (e.g., /about.html). This makes the problem harder to diagnose because it only surfaces when users try to access the pages directly or refresh the page.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Initial Setup
&lt;/h2&gt;

&lt;p&gt;I was originally building my Next.js files as a static site on S3 using this code in next.config.ts and using trailingSlash to export all files as index.html:&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextConfig&lt;/span&gt; &lt;span class="p"&gt;}&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;next&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;nextConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;export&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;trailingSlash&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;nextConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything was perfect - I got the best of both worlds, but I wasn't aware of the limitations. CloudFront added an extra layer of complexity to URLs. &lt;/p&gt;

&lt;p&gt;I figure this out later but this is crucial to fix the issue, you must open next.config.ts and make sure that trailingSlash is removed or set to false:&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextConfig&lt;/span&gt; &lt;span class="p"&gt;}&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;next&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;nextConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;export&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;trailingSlash&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;nextConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I haved already saved you time.&lt;/p&gt;

&lt;p&gt;Now when I use &lt;code&gt;output: 'export'&lt;/code&gt; with &lt;code&gt;trailingSlash: false&lt;/code&gt;, my files are being built with the default /about.html format. This allows Next.js to export each blog post in its own directory containing an index.html (so it will output /about/index.html).&lt;/p&gt;

&lt;p&gt;From my experience with React/Vite I created a custom error response to for HTTP error Code 403 to customize error response200 and send to "index.html" or "/" but that just creates and infinite loop back to the homepage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up CloudFront Logging
&lt;/h2&gt;

&lt;p&gt;I wanted to understand deeply what was going on, so let's create logs to see what CloudFront is doing.&lt;/p&gt;

&lt;p&gt;First, I created an S3 bucket to add CloudFront logs, using the default settings that S3 provides.&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%2F9chxq2w4ouzkdn1mpz5k.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%2F9chxq2w4ouzkdn1mpz5k.png" alt="s3 bucket main" width="800" height="374"&gt;&lt;/a&gt;&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%2Fqz2ctedltp8xr0zlljvs.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%2Fqz2ctedltp8xr0zlljvs.png" alt="s3 bucket scroll" width="800" height="397"&gt;&lt;/a&gt;&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%2Fawajjnphn49oay5tnbj3.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%2Fawajjnphn49oay5tnbj3.png" alt="s3 bucket bottom page" width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring CloudFront Access
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to your current CloudFront distribution and navigate to the Logging tab&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%2Fqlyfg4et66jlhyhfh4xj.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%2Fqlyfg4et66jlhyhfh4xj.png" alt="Cloudfront create" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We're going to associate our S3 bucket in the Standard log destination&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%2F9agboz502pk9m2pgliig.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%2F9agboz502pk9m2pgliig.png" alt="S3 bucket" width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hit "Add" and select "Amazon S3"&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%2F0kqrylev8jasxg9wa8kw.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%2F0kqrylev8jasxg9wa8kw.png" alt="s3 bucket 2" width="800" height="225"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select the "Destination S3 bucket" to be your newly created log bucket&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Access your site, give it some time for logs to generate, and you should see new logs being created in this format:&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="s2"&gt;"distribution ID"&lt;/span&gt;.YYYY-MM-DD-HH.unique-ID.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&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="s2"&gt;"optional prefix"&lt;/span&gt;/&lt;span class="s2"&gt;"distribution ID"&lt;/span&gt;.YYYY-MM-DD-HH.unique-ID.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We cannot access the logs directly as they are zipped. This is where Lambda comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Amazon Lambda
&lt;/h2&gt;

&lt;p&gt;Why use Amazon lambda?&lt;/p&gt;

&lt;p&gt;Why use Amazon Lambda? For this moment, Lambda serves 3 purposes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Extract: Getting the gzipped logs from CloudFront/S3&lt;/li&gt;
&lt;li&gt;Transform: Unzipping/decompressing the files&lt;/li&gt;
&lt;li&gt;Load: Sending to CloudWatch&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's create a lambda function with basic configurations.&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%2Fuj3mf5duwkod7u4nhmov.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%2Fuj3mf5duwkod7u4nhmov.png" alt="Lambda main page" width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hit create function.&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%2F1j7zmdw8nw5t666y1ivo.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%2F1j7zmdw8nw5t666y1ivo.png" alt="Lambda blueprint" width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are using a blueprint to save us time. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start with the "Get S3 Object" blueprint using Python runtime&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Name your function.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new role with basic Lambda permissions (this will only include permissions for writing logs to CloudWatch but lacks permissions for accessing S3)&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%2Fp4nqevd7qto9flhmtd6l.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%2Fp4nqevd7qto9flhmtd6l.png" alt="Lambda function trigger" width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select your bucket you created logs

&lt;ul&gt;
&lt;li&gt;Under event types, use only "Put" since logs are a put event&lt;/li&gt;
&lt;li&gt;Make sure to check the "Recursive invocation" box&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now hit create function.&lt;/p&gt;

&lt;p&gt;Update the Lambda function with this code:&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;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;gzip&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Loading function&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&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;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Records&lt;/span&gt;&lt;span class="sh"&gt;'&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="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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bucket&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;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unquote_plus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Records&lt;/span&gt;&lt;span class="sh"&gt;'&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="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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;object&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;key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&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="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;compressed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;gzip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GzipFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fileobj&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BytesIO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;compressed&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;gz&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gz&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&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;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;content&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="nf"&gt;print&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: &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;span class="k"&gt;raise&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The only addition to the original code given is this will allow us to unzip the log files in the s3 bucket.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding S3 Permissions
&lt;/h3&gt;

&lt;p&gt;Now we need to add permissions for our Lambda role to access the S3 bucket:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In Lambda, go to Configuration &amp;gt; Permissions&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%2Frcvi41qa0llm98owpail.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%2Frcvi41qa0llm98owpail.png" alt="Lambda config" width="800" height="246"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select the role name under "Execution Role"&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%2Fxctw16ficfenvv0747b3.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%2Fxctw16ficfenvv0747b3.png" alt="Lambda Roles" width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the role with current basic policy.&lt;/p&gt;

&lt;p&gt;Now go to "Add permissions" than "Create Inline Policy".&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%2F229lf78s8h41afftpygj.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%2F229lf78s8h41afftpygj.png" alt="policy permission" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add an inline policy with these permissions:

&lt;ul&gt;
&lt;li&gt;GetObject: to retrieve log files&lt;/li&gt;
&lt;li&gt;ListBucket: to select the Log bucket&lt;/li&gt;
&lt;/ul&gt;
&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%2Fn4mre9p0jlqg9wf55lcp.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%2Fn4mre9p0jlqg9wf55lcp.png" alt="policy restriction" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Restrict access to your specific log bucket ARN&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once your done hit "Next"&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%2F8dwkqy0b1m83fwut4anq.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%2F8dwkqy0b1m83fwut4anq.png" alt="Name policy" width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Name the policy. Now your lambda function will have access to log bucket.&lt;/p&gt;

&lt;p&gt;Now go to your site and click and refreshed it to get some permission denied errors to generate some logs.&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%2Ffg55ws2jnan4dqk64hk0.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%2Ffg55ws2jnan4dqk64hk0.png" alt="log group" width="800" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First set of logs. Hit "Search all log streams".&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%2Fynq72ops39zaud05l99w.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%2Fynq72ops39zaud05l99w.png" alt="log stream" width="800" height="397"&gt;&lt;/a&gt;&lt;br&gt;
We can now see the access logs are being successfully decompressed.&lt;/p&gt;

&lt;p&gt;You want to go through your logs to get the exact errors and paths affected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the CloudFront Function
&lt;/h2&gt;

&lt;p&gt;After analyzing the logs, we can create a CloudFront function to rewrite the URLs properly:&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%2Fyzq3fs62w030mw6a8h5n.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%2Fyzq3fs62w030mw6a8h5n.png" alt="Cloudfront function" width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;1.Go to Cloudfront and on the left Side look for functions&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%2Fc5qiyp5hdxhf87jy9zti.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%2Fc5qiyp5hdxhf87jy9zti.png" alt="Cloudfront side" width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Give your function a name and description of what it does. Than hit "Create function".&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new function with this our your code:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uri&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;uri&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/blog&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/blog/&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="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/blog.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;request&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&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;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.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="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;request&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;Now save your changes. &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%2Fcjfvievvi9xjurc1bpiw.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%2Fcjfvievvi9xjurc1bpiw.png" alt="Cloudfront association" width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Publish the function&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%2Fzwk1b9u78091t9r1julh.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%2Fzwk1b9u78091t9r1julh.png" alt="Publish cloudfront" width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add an association to your CloudFront Distribution:

&lt;ul&gt;
&lt;li&gt;Event Type: "Viewer Request"&lt;/li&gt;
&lt;li&gt;Behavior: Use the affected path (e.g., /blog*)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Note: I changed my behavior path to "/blog*" - this ensures that both /blog and /blog/* are handled by the same cache behavior and CloudFront Function.&lt;/p&gt;

&lt;p&gt;Wait for your Distribution to redeploy, then invalidate your cache. Open your site in a new private window to test.&lt;/p&gt;

&lt;p&gt;This solution worked for my case. If it doesn't work for you, go through your set of logs to see what the errors/paths are and alter your function accordingly - that's what I did.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Learnings and Root Cause
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Understanding URL Path Behavior
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Static site generators like Next.js create HTML files for each route&lt;/li&gt;
&lt;li&gt;S3 expects full file paths (e.g., &lt;code&gt;/about.html&lt;/code&gt;) while users access clean URLs (e.g., &lt;code&gt;/about&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;CloudFront and S3 handle URL paths differently in production vs local development&lt;/li&gt;
&lt;li&gt;The URL mismatch causes permission denied errors only on direct access or page refreshes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Key Technical Insights
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Next.js Configuration Impact&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;trailingSlash&lt;/code&gt; setting significantly affects URL structure&lt;/li&gt;
&lt;li&gt;Static export behavior needs special handling with CloudFront&lt;/li&gt;
&lt;li&gt;Default configurations might work locally but fail in production&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Production Debugging Strategy&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set up proper logging infrastructure first&lt;/li&gt;
&lt;li&gt;Use Lambda to process CloudFront logs effectively&lt;/li&gt;
&lt;li&gt;Monitor actual user access patterns&lt;/li&gt;
&lt;li&gt;Test both direct access and navigation scenarios&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CloudFront Function Benefits&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Handles URL rewrites at the edge&lt;/li&gt;
&lt;li&gt;Provides clean URLs for users while maintaining proper S3 access&lt;/li&gt;
&lt;li&gt;Cost-effective solution for URL path manipulation&lt;/li&gt;
&lt;li&gt;Allows flexible routing rules based on your needs&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Prevention Tips for Others
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Start with &lt;code&gt;trailingSlash: false&lt;/code&gt; in Next.js when using CloudFront&lt;/li&gt;
&lt;li&gt;Set up comprehensive logging before deploying to production&lt;/li&gt;
&lt;li&gt;Test all URL access patterns:

&lt;ul&gt;
&lt;li&gt;Direct URL access&lt;/li&gt;
&lt;li&gt;Navigation through links&lt;/li&gt;
&lt;li&gt;Page refreshes&lt;/li&gt;
&lt;li&gt;Nested routes&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Document your CloudFront function behavior for future reference&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Remember: What seems like a simple permission issue might actually be a complex interaction between your static site generator, CDN, and storage service. Always validate your assumptions with proper logging and testing in production.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>cloudfront</category>
      <category>aws</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Building a Cloud-Native Blog Platform with Terraform</title>
      <dc:creator>David Hyppolite</dc:creator>
      <pubDate>Wed, 11 Dec 2024 12:05:30 +0000</pubDate>
      <link>https://dev.to/dhayv/building-a-cloud-native-blog-platform-with-terraform-11km</link>
      <guid>https://dev.to/dhayv/building-a-cloud-native-blog-platform-with-terraform-11km</guid>
      <description>&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Why Build Another Blog Platform?&lt;/li&gt;
&lt;li&gt;
Architectural Decisions

&lt;ul&gt;
&lt;li&gt;Core Infrastructure Components&lt;/li&gt;
&lt;li&gt;Infrastructure Management&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

The Real Challenges

&lt;ul&gt;
&lt;li&gt;Cross-Account Issues&lt;/li&gt;
&lt;li&gt;CI/CD Pipeline Setup&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Key Learnings&lt;/li&gt;

&lt;li&gt;Looking Forward&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Code Examples and Documentation
&lt;/h2&gt;

&lt;p&gt;Next.js + AWS (CloudFront, S3, Route53, ACM) + Terraform + GitHub Actions CI/CD&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/dhayv/blogsite" rel="noopener noreferrer"&gt;Github Code&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The complete Terraform configuration, GitHub Actions workflows, and detailed setup instructions are available in the repository. Feel free to use this as a reference for your own projects or to suggest improvements.&lt;/p&gt;

&lt;p&gt;Remember: Sometimes the longest path teaches you the most valuable lessons.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Build Another Blog Platform?
&lt;/h2&gt;

&lt;p&gt;"Just use WordPress," they said. "You could have it up in minutes."&lt;/p&gt;

&lt;p&gt;As an AWS Solutions Architect, I knew I could spin up a simple blog in the console within an hour. But here's the thing - I've already built production systems maintaining 99.9% uptime. I've already clicked through the console countless times. This project wasn't about the fastest path to a blog; it was about pushing my limits.&lt;/p&gt;

&lt;p&gt;I needed a platform that would:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Force me to translate my console knowledge into code&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Challenge my understanding of AWS service interactions&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Create a real-world testing ground for complex cloud architectures&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Serve as documentation for other engineers facing similar challenges&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The real value? The gap between knowing how to do something in the console and implementing it in Terraform. That's where the learning happens. That's where engineers grow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architectural Decisions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Core Infrastructure Components
&lt;/h3&gt;

&lt;p&gt;I designed the architecture with several key requirements in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High Availability&lt;/strong&gt;: Using CloudFront for edge distribution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt;: Implementing proper IAM roles and OIDC federation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability&lt;/strong&gt;: S3 for static content and versioning&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation&lt;/strong&gt;: Complete CI/CD pipeline with GitHub Actions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's how each piece fits together:&lt;/p&gt;

&lt;h4&gt;
  
  
  Content Delivery and Security
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CloudFront Distribution&lt;/strong&gt;: Handles HTTPS termination, caching, and low-latency delivery&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ACM (AWS Certificate Manager)&lt;/strong&gt;: Manages SSL certificates for secure communication&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Origin Access Identity (OAI)&lt;/strong&gt;: Restricts S3 access exclusively to CloudFront&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S3 Bucket&lt;/strong&gt;: Hosts static files with versioning enabled&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Infrastructure Management
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Private S3 Backend&lt;/strong&gt;: Maintains Terraform state files securely&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IAM Roles&lt;/strong&gt;: Enables long-term automation with least privilege&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OIDC Web Identity&lt;/strong&gt;: Secures GitHub Actions' AWS access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This setup not only adheres to AWS’s six pillars of well-architected frameworks but also taught me the intricacies of networking and IaC.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Challenges: A Sequential Journey
&lt;/h2&gt;

&lt;p&gt;What looks like a straightforward architecture on paper turned into a complex troubleshooting journey. Here's how each challenge led to the next:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Hosted Zone Puzzle
&lt;/h3&gt;

&lt;p&gt;Initially, Terraform couldn't access the hosted zone by name. While AWS CLI could find it, Terraform kept failing. This was my first hint that something deeper was wrong, though I didn't realize it yet.&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;# Initial attempt that failed`&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"zone_name"&lt;/span&gt; &lt;span class="s2"&gt;"primary"&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="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain_name&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Solution: Use zone ID directly`&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"zone_id"&lt;/span&gt; &lt;span class="s2"&gt;"primary"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;zone_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zone_id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. The Multi-Account Maze
&lt;/h3&gt;

&lt;p&gt;The hosted zone issue unveiled a complex problem: I was unknowingly trying to access resources across multiple AWS accounts without proper cross-account permissions. Here's how it manifested:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;First sign&lt;/strong&gt;: Permission errors when Terraform tried accessing resources such as hosted zone&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Second sign&lt;/strong&gt;: User not found in the expected account&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Final revelation&lt;/strong&gt;: S3 bucket 403 errors indicating cross-account access attempts&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After numerous attempts at fixing permissions, with fatigue setting in and countless Chrome tabs open, I stepped away from the computer. Sometimes the best debugging tool is a fresh perspective. When I returned, I methodically verified each environment variable, discovering they weren't properly configured.&lt;/p&gt;

&lt;p&gt;Initially, I set environment variables for my default account:&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;# Checking current identity&lt;/span&gt;
aws sts get-caller-identity

&lt;span class="c"&gt;# Setting up environment variables&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"new_access_key"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"new_secret_key"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_DEFAULT_REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;

&lt;span class="c"&gt;# Profile&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_PROFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But this created conflicts when trying to switch between accounts. The real issue? The Terraform role lacked cross-account access permissions.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Cross-Account Resource Management
&lt;/h3&gt;

&lt;p&gt;The situation became clear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hosted zone&lt;/strong&gt;: In main account but inaccessible due to resource in backup account&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S3 bucket&lt;/strong&gt;: In backup account but main role lacked cross-account access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terraform&lt;/strong&gt;: Were able to create resources becuase I gave permission from backup account but not main account.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With a fresh mind,I saw the clear picture. I took a systematic approach:&lt;/p&gt;

&lt;p&gt;Resolution required systematic cleanup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Profile Configuration&lt;/strong&gt;: I configured a profile specifically for the backup account, which I had initially removed. By setting &lt;code&gt;export AWS_PROFILE=backup&lt;/code&gt;, I aligned the Terraform operations with the correct account.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment Variable Adjustment&lt;/strong&gt;: I unset the previous environment variables that were causing conflicts and reconfigured the AWS CLI to ensure that operations were correctly authenticated and executed under the appropriate account.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verification and Cleanup&lt;/strong&gt;: I ran &lt;code&gt;aws sts get-caller-identity&lt;/code&gt; to verify that the CLI was now using the correct account. Subsequently, I used &lt;code&gt;terraform init&lt;/code&gt; to reinitialize Terraform and &lt;code&gt;terraform destroy --auto-approve&lt;/code&gt; to remove mistakenly created resources in backup.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The resolution of these issues marked a significant turning point in the project. Not only did I manage to streamline the AWS architecture by ensuring resources were correctly aligned with their respective accounts, but I also reinforced best practices in AWS management and Terraform usage. This process underscored the importance of vigilant account management and served as a valuable lesson in maintaining clarity and organization in cloud environments.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. CI/CD Pipeline Optimization
&lt;/h3&gt;

&lt;p&gt;The final challenge emerged in the CI/CD pipeline. While the YAML was syntactically correct, Terraform operations were hanging at Terraform Plan. The root cause? Environment variables needed to be passed to each Terraform command individually:&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="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;Terraform Init&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;cd ./terraform-config&lt;/span&gt;
    &lt;span class="s"&gt;terraform init&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;TF_VAR_domain_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DOMAIN_NAME }}&lt;/span&gt;
    &lt;span class="na"&gt;TF_VAR_aws_region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_REGION}}&lt;/span&gt;
    &lt;span class="na"&gt;TF_VAR_zone_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.ZONE_ID }}&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;Terraform&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;plan&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;cd ./terraform-config&lt;/span&gt;
    &lt;span class="s"&gt;terraform plan -out=tfplan&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;Terraform Apply&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;apply&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;cd ./terraform-config&lt;/span&gt;
    &lt;span class="s"&gt;terraform apply -auto-approve tfplan &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Working solution
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&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;Terraform Init&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;cd ./terraform-config&lt;/span&gt;
    &lt;span class="s"&gt;terraform init&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;TF_VAR_domain_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DOMAIN_NAME }}&lt;/span&gt;
    &lt;span class="na"&gt;TF_VAR_aws_region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_REGION}}&lt;/span&gt;
    &lt;span class="na"&gt;TF_VAR_zone_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.ZONE_ID }}&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;Terraform&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;plan&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;cd ./terraform-config&lt;/span&gt;
    &lt;span class="s"&gt;terraform plan -out=tfplan&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;TF_VAR_domain_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DOMAIN_NAME }}&lt;/span&gt;
    &lt;span class="na"&gt;TF_VAR_aws_region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_REGION}}&lt;/span&gt;
    &lt;span class="na"&gt;TF_VAR_zone_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.ZONE_ID }}&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;Terraform Apply&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;apply&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;cd ./terraform-config&lt;/span&gt;
    &lt;span class="s"&gt;terraform apply -auto-approve tfplan &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;TF_VAR_domain_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DOMAIN_NAME }}&lt;/span&gt;
    &lt;span class="na"&gt;TF_VAR_aws_region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_REGION}}&lt;/span&gt;
    &lt;span class="na"&gt;TF_VAR_zone_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.ZONE_ID }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This challenge took me two hours to solve, highlighting the importance of understanding the nuances of Terraform commands within CI/CD pipelines and reinforcing the need for meticulous configuration to ensure smooth automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Learnings
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Account Management is Critical&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- Always verify which account you're operating in
- Set up proper profile management early
- Use `aws sts get-caller-identity` frequently
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Infrastructure as Code Requires Different Thinking&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- What works in the console might need different approaches in Terraform
- Resource relationships need explicit definition
- State management is crucial
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Authentication Flow Understanding&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- OIDC federation setup requires careful configuration
- Environment variables affect different tools differently
- Multiple authentication methods need careful management
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Best Practices Emerged&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- Always verify account context before operations

&lt;ul&gt;
&lt;li&gt;Implement clear naming conventions&lt;/li&gt;
&lt;li&gt;Maintain separation of concerns between accounts&lt;/li&gt;
&lt;li&gt;Document environment variable requirements
&lt;/li&gt;
&lt;/ul&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;


Embracing the Hard Path
&lt;/h2&gt;


&lt;p&gt;One year ago, I made a choice: stop settling for the easy path. I had already built multiple AWS projects. Already maintained Linux servers. Already earned my AWS Solutions Architect certification. But Terraform? That was my next mountain to climb.&lt;/p&gt;

&lt;p&gt;The console is comfortable. It's visual. It's immediate. But code? Code demands precision. Every resource relationship must be explicit. Every permission must be defined. Every interaction must be planned.&lt;/p&gt;

&lt;p&gt;This project forced me to think differently about infrastructure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No more clicking through options&lt;/li&gt;
&lt;li&gt;No more relying on AWS's default settings&lt;/li&gt;
&lt;li&gt;No more visual confirmation of changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead, I had to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Define every resource relationship in code&lt;/li&gt;
&lt;li&gt;Understand every service interaction deeply&lt;/li&gt;
&lt;li&gt;Plan for automation from the start&lt;/li&gt;
&lt;li&gt;Think in terms of state management and drift detection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Was it harder? Absolutely.&lt;br&gt;
Did it take longer? Without question.&lt;br&gt;
Was it worth it? Every frustrating minute.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking Forward
&lt;/h2&gt;

&lt;p&gt;This platform isn't just a blog—it's a testament to the complexity and learning opportunities in modern cloud architecture. Every challenge forced me to dig deeper into AWS services, Terraform behavior, and Cloud practices.&lt;/p&gt;

&lt;p&gt;The final architecture follows AWS Well-Architected Framework principles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt;: Proper IAM roles, OIDC federation, and access controls&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reliability&lt;/strong&gt;: CloudFront distribution and S3 versioning&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance Efficiency&lt;/strong&gt;: Edge caching and optimization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost Optimization&lt;/strong&gt;: Practically Free&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operational Excellence&lt;/strong&gt;: Fully automated deployments&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>terraform</category>
      <category>webdev</category>
      <category>aws</category>
    </item>
    <item>
      <title>Resolving the Lambda S3 Trigger Error</title>
      <dc:creator>David Hyppolite</dc:creator>
      <pubDate>Tue, 10 Dec 2024 12:00:24 +0000</pubDate>
      <link>https://dev.to/dhayv/resolving-the-lambda-s3-trigger-error-k35</link>
      <guid>https://dev.to/dhayv/resolving-the-lambda-s3-trigger-error-k35</guid>
      <description>&lt;p&gt;While setting up a Lambda function to process S3 logs, I encountered an error that wasn't immediately obvious how to resolve:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your Lambda function "[function-name]" was successfully created, but an error occurred when creating the trigger: Configuration is ambiguously defined. Cannot have overlapping suffixes in two rules if the prefixes are overlapping for the same event type.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The error appeared when trying to set up the trigger through the AWS Console. My use case was straightforward - I needed to process logs as they arrived in an S3 bucket. The Lambda function was created successfully, but the S3 trigger configuration kept failing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Didn't Work
&lt;/h2&gt;

&lt;p&gt;Initially, I tried:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deleting and recreating the trigger through the AWS Console&lt;/li&gt;
&lt;li&gt;Double-checking the event types and prefix/suffix settings&lt;/li&gt;
&lt;li&gt;Verifying IAM permissions were correct&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of these steps resolved the issue, and the error message wasn't particularly helpful in pointing to the real problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding the Actual Issue
&lt;/h2&gt;

&lt;p&gt;The breakthrough came when I decided to inspect the bucket's configuration using the AWS CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3api get-bucket-notification-configuration &lt;span class="nt"&gt;--bucket&lt;/span&gt; &lt;span class="s2"&gt;"my-bucket"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This revealed something interesting - a lingering configuration that wasn't visible in the AWS Console:&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="nv"&gt;$ &lt;/span&gt;aws s3api get-bucket-notification-configuration &lt;span class="nt"&gt;--bucket&lt;/span&gt; &lt;span class="s2"&gt;"my-bucket"&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"LambdaFunctionConfigurations"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
        &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"Id"&lt;/span&gt;: &lt;span class="s2"&gt;"23410cc7-d44d-4d8a-b50d-5c41dcf80ec9"&lt;/span&gt;,
            &lt;span class="s2"&gt;"LambdaFunctionArn"&lt;/span&gt;: &lt;span class="s2"&gt;"arn:aws:lambda:us-east-1:637423581329:function:ingest_s3logs"&lt;/span&gt;,
            &lt;span class="s2"&gt;"Events"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
                &lt;span class="s2"&gt;"s3:ObjectCreated:*"&lt;/span&gt;
            &lt;span class="o"&gt;]&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;With this discovery, the fix was simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3api put-bucket-notification-configuration &lt;span class="nt"&gt;--bucket&lt;/span&gt; &lt;span class="s2"&gt;"my-bucket"&lt;/span&gt; &lt;span class="nt"&gt;--notification-configuration&lt;/span&gt; &lt;span class="s1"&gt;'{}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command cleared out the hidden configuration, allowing me to successfully create the new trigger for the log processing function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The AWS Console doesn't always show the full picture of your resource configurations&lt;/li&gt;
&lt;li&gt;When dealing with S3 event notifications, the CLI can reveal hidden settings&lt;/li&gt;
&lt;li&gt;Sometimes starting with a clean slate is the quickest path to resolution&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;p&gt;For official documentation and more details about this issue:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://repost.aws/knowledge-center/lambda-s3-event-configuration-error" rel="noopener noreferrer"&gt;Troubleshooting Lambda S3 Event Configuration Errors&lt;/a&gt; - AWS Knowledge Center article addressing this specific error&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/with-s3.html" rel="noopener noreferrer"&gt;AWS Lambda with S3 Documentation&lt;/a&gt; - Official guide for configuring Lambda functions with S3 triggers&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/notification-how-to-event-types-and-destinations.html" rel="noopener noreferrer"&gt;S3 Event Notification Configuration&lt;/a&gt; - Details on S3 event types and configuration&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
  </channel>
</rss>
