<?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: Breindel Medina</title>
    <description>The latest articles on DEV Community by Breindel Medina (@kindadailybren).</description>
    <link>https://dev.to/kindadailybren</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%2F2659328%2F45321e04-ae9e-4f19-b1a2-f3c7f6b52a86.jpeg</url>
      <title>DEV Community: Breindel Medina</title>
      <link>https://dev.to/kindadailybren</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kindadailybren"/>
    <language>en</language>
    <item>
      <title>Modern Deployment Strategies with AWS: Blue/Green and Canary with CodeDeploy</title>
      <dc:creator>Breindel Medina</dc:creator>
      <pubDate>Tue, 12 May 2026 14:02:58 +0000</pubDate>
      <link>https://dev.to/kindadailybren/modern-deployment-strategies-with-aws-bluegreen-and-canary-with-codedeploy-3ie7</link>
      <guid>https://dev.to/kindadailybren/modern-deployment-strategies-with-aws-bluegreen-and-canary-with-codedeploy-3ie7</guid>
      <description>&lt;p&gt;When deploying applications to production, the method you use to replace the old code with the new code dictates your risk level. Historically, deployments meant overwriting the live server. Today, modern architectures use load balancers to shift traffic dynamically, drastically reducing the blast radius of a bad release.&lt;/p&gt;

&lt;p&gt;Here is an in-depth look at how these deployment strategies work, complete with architectural flows, followed by a manual, CLI-driven hands-on guide to deploying a FastAPI container from Amazon ECR to ECS using AWS CodeDeploy.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Danger of "In-Place" Deployments
&lt;/h2&gt;

&lt;p&gt;Deploying without traffic shifting is known as an &lt;strong&gt;In-Place Deployment&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In practice, this means your server stops serving the current version (Version 1) and replaces it with the new version (Version 2) on the same machine or instance. There is no buffer, no parallel environment, just a direct swap.&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%2Fsb5jdlloe1s46pwmwh3x.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%2Fsb5jdlloe1s46pwmwh3x.png" alt="In-Place" width="582" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Risk:&lt;/strong&gt; During the replacement, your server is either down or running in a partially updated state. If Version 2 contains a fatal bug, every single user hitting your server experiences the failure immediately. There is no subset of users, it is all or nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Rollback:&lt;/strong&gt; You must manually re-deploy the old version, wait for it to boot, and wait for it to pass health checks. Depending on your setup, this can mean minutes of active downtime while you scramble to fix it.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  The Traffic Shifting Alternatives
&lt;/h1&gt;

&lt;p&gt;Traffic shifting decouples the &lt;em&gt;infrastructure provisioning&lt;/em&gt; from the &lt;em&gt;release&lt;/em&gt;. Instead of swapping the running server, you spin up the new version completely in the background. A &lt;strong&gt;Load Balancer&lt;/strong&gt; sits in front of your servers and acts as a traffic cop, you manipulate its routing rules to control which users see which version, without ever taking the old version offline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blue/Green Deployment
&lt;/h2&gt;

&lt;p&gt;Blue/Green provisions a completely identical, parallel server environment alongside the live one.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Blue&lt;/strong&gt; is your current live server, it is serving 100% of your users right now.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Green&lt;/strong&gt; is the new server, it is fully provisioned and running, but the load balancer is not sending anyone to it yet.&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%2Fd8hlzo5ie6l5npszcd2u.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%2Fd8hlzo5ie6l5npszcd2u.png" alt=" " width="599" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The steps are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Provision:&lt;/strong&gt; Spin up the Green server with Version 2. Users still hit Blue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test:&lt;/strong&gt; Send internal or test traffic directly to Green to verify it works correctly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shift:&lt;/strong&gt; Instruct the load balancer to swap: Blue receives 0% of traffic, Green receives 100%. The switch is instant.&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%2Fykehg7gslrr9072wqnbl.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%2Fykehg7gslrr9072wqnbl.png" alt=" " width="589" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Rollback:&lt;/strong&gt; If something goes wrong, the load balancer just points back to Blue. Blue was never torn down, so the rollback is instant with zero re-provisioning.&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%2F3y989poulcegdwb6nc0t.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%2F3y989poulcegdwb6nc0t.png" alt=" " width="597" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Canary Deployment
&lt;/h2&gt;

&lt;p&gt;Canary deployments use the same two-server setup as Blue/Green, but instead of an instant full switch, the load balancer gradually bleeds a small percentage of real user traffic to the new version while the majority stays on the old one.&lt;/p&gt;

&lt;p&gt;The name comes from the old mining practice of sending a canary into a coal mine first, if it survives, it's safe for everyone else.&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%2Fg5lbcusactyql6pgzit6.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%2Fg5lbcusactyql6pgzit6.png" alt=" " width="592" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The steps are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Provision:&lt;/strong&gt; Spin up the new server with Version 2 in the background.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shift:&lt;/strong&gt; The load balancer routes 10% of real user traffic to the new server, and 90% stays on the old one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor:&lt;/strong&gt; Watch your error rates, latency, and logs during this window. Only a small fraction of users are exposed to any potential bug.&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%2Fvn7daps9sijddtzysdcm.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%2Fvn7daps9sijddtzysdcm.png" alt=" " width="629" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Complete:&lt;/strong&gt; If the 10% remains stable for a set bake time (e.g., 5 minutes), the load balancer shifts the remaining 90%. If errors spike, traffic is shifted back to the original server before the damage spreads.&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%2Fkecla93a75rzgqhjx9ph.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%2Fkecla93a75rzgqhjx9ph.png" alt=" " width="597" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The key difference from Blue/Green is the &lt;em&gt;graduated exposure&lt;/em&gt;. Instead of betting everything on a single switch, you use real user traffic as a controlled test, with an automatic escape hatch if things go wrong.&lt;/p&gt;




&lt;h2&gt;
  
  
  AWS CodeDeploy
&lt;/h2&gt;

&lt;p&gt;These deployment strategies can easily be implemented with the use of AWS CodeDeploy, but before that, what is AWS CodeDeploy?&lt;/p&gt;

&lt;p&gt;AWS CodeDeploy is the delivery fleet. CodeDeploy does not build your code, and it does not run your tests. Its entire job is to take a finished, ready-to-go artifact (like a Docker image or a ZIP file) and place it onto your servers (EC2, ECS, or Lambda) safely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why CodeDeploy?
&lt;/h3&gt;

&lt;p&gt;Without CodeDeploy, you would have to write custom, complex scripts to tell your load balancer to shift traffic, wait 5 minutes, check for errors, and then shift more traffic. CodeDeploy abstracts all of that. You just give it an &lt;code&gt;appspec.yaml&lt;/code&gt; file, and it acts as the &lt;strong&gt;conductor&lt;/strong&gt;—coordinating the load balancers, the containers, and the health checks to execute those advanced Blue/Green and Canary strategies automatically.&lt;/p&gt;




&lt;h1&gt;
  
  
  Hands-On: ECR to ECS via CodeDeploy (CLI Workflow)
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Prerequisites:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;AWS CLI installed and configured (&lt;code&gt;aws configure&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Docker CLI installed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;jq&lt;/code&gt; installed&lt;/li&gt;
&lt;li&gt;An AWS account with permissions to create CloudFormation stacks&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Provision stack using CloudFormation Templates
&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%2Ff5n1gsjl9uc8okjvgyzh.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%2Ff5n1gsjl9uc8okjvgyzh.png" alt=" " width="315" height="102"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In CloudFormation, Press &lt;code&gt;Create Stack&lt;/code&gt; then press &lt;code&gt;With new resources (standard)&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%2Fwo6b902g1a2qe8zi067a.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%2Fwo6b902g1a2qe8zi067a.png" alt=" " width="800" height="513"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input this link &lt;a href="https://codedeploy-canary-bluegreen-template.s3.ap-southeast-1.amazonaws.com/CanaryBlueGreenWorkshopStack.template.json" rel="noopener noreferrer"&gt;&lt;code&gt;https://codedeploy-canary-bluegreen-template.s3.ap-southeast-1.amazonaws.com/CanaryBlueGreenWorkshopStack.template.json&lt;/code&gt;&lt;/a&gt; into the Amazon S3 URL input field so that it uses this template for the stack&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%2Fyfi8ei1z7nreimaiwwkv.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%2Fyfi8ei1z7nreimaiwwkv.png" alt=" " width="799" height="155"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provide a stackname, preferably &lt;code&gt;CanaryBlueGreenWorkshopStack-&amp;lt;unique number&amp;gt;&lt;/code&gt; then click next&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%2Feqrravhqqd1j831ipv66.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%2Feqrravhqqd1j831ipv66.png" alt=" " width="799" height="148"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scroll down to the very bottom under Capabilities and tick the checkbox then Next&lt;/li&gt;
&lt;li&gt;In the next page, click submit&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Understanding the Infrastructure Stack
&lt;/h2&gt;

&lt;p&gt;Before we push code, let’s look at the "piping" provisioned in your AWS account. These resources work together as a single unit to enable traffic shifting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Virtual Private Cloud (VPC):&lt;/strong&gt; The isolated network where your application lives. To keep costs low, we use Public Subnets only and by assigning each Fargate task a public IP, containers can reach ECR and Docker Hub directly without expensive NAT Gateways&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application Load Balancer (ALB):&lt;/strong&gt; The "Front Door." It receives all incoming traffic and uses &lt;strong&gt;Listener Rules&lt;/strong&gt; to decide which Target Group gets the request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Target Groups (Blue &amp;amp; Green):&lt;/strong&gt; These are logical groupings of your containers.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Blue:&lt;/strong&gt; Holds the currently running production version (v1).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Green:&lt;/strong&gt; The staging area where the new version (v2) is deployed before anyone sees it.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;ECS Cluster &amp;amp; Fargate Service:&lt;/strong&gt; The "Compute." We use &lt;strong&gt;Fargate&lt;/strong&gt;, which is serverless—you don't manage EC2 instances; you just provide the Docker image and AWS runs it.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;CodeDeploy &amp;amp; CloudWatch Alarms:&lt;/strong&gt; The "Safety Officer." CodeDeploy manages the ALB weights. It watches a &lt;strong&gt;CloudWatch Alarm&lt;/strong&gt; (monitoring 5XX errors). If the alarm triggers during the 5-minute Canary window, CodeDeploy immediately shifts traffic back to Blue.&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  Key Definitions for the Manual Workflow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Task Definition (&lt;code&gt;task-def.json&lt;/code&gt;):&lt;/strong&gt; Think of this as the &lt;strong&gt;Blueprint for the ECS&lt;/strong&gt;. It defines which Docker image to use, how much CPU/Memory it needs, and which ports are open.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AppSpec (&lt;code&gt;appspec.yaml&lt;/code&gt;):&lt;/strong&gt; This is the &lt;strong&gt;Instruction Manual&lt;/strong&gt; for CodeDeploy. It tells CodeDeploy &lt;em&gt;which&lt;/em&gt; Task Definition to move to and &lt;em&gt;which&lt;/em&gt; container inside that definition should receive the load balancer traffic.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Give Permissions to ECS to get ECR Image for upload later
&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%2F1xbtsi2f4sx3871awd4e.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%2F1xbtsi2f4sx3871awd4e.png" alt=" " width="530" height="66"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Search the Execution Role and click it&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%2Fet075jq19bcgu3pxmfo5.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%2Fet075jq19bcgu3pxmfo5.png" alt=" " width="800" height="199"&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%2F5iem0mqjcoksej81kaki.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%2F5iem0mqjcoksej81kaki.png" alt=" " width="654" height="182"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Press Attach policies and Give &lt;a href="https://us-east-1.console.aws.amazon.com/iam/home?region=ap-southeast-1#/policies/details/arn%3Aaws%3Aiam%3A%3Aaws%3Apolicy%2Fservice-role%2FAmazonECSTaskExecutionRolePolicy" rel="noopener noreferrer"&gt;&lt;code&gt;AmazonECSTaskExecutionRolePolicy&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Copy the Execution Role for later steps&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The FastAPI Application
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;To install the dependencies, spin up a virtual environment with python through &lt;code&gt;python -m venv .venv&lt;/code&gt; and install dependencies using the command &lt;code&gt;pip install fastapi uvicorn&lt;/code&gt; and freezing the dependencies to a requirements.txt using &lt;code&gt;pip freeze &amp;gt; requirements.txt&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;main.py&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nx"&gt;fastapi&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;FastAPI&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="o"&gt;---------------------------------------------------------&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;WORKSHOP&lt;/span&gt; &lt;span class="nx"&gt;INSTRUCTIONS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;For&lt;/span&gt; &lt;span class="nx"&gt;Version&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt; &lt;span class="nx"&gt;VERSION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;v1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;For&lt;/span&gt; &lt;span class="nx"&gt;Version&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt; &lt;span class="nx"&gt;VERSION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;v2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="o"&gt;---------------------------------------------------------&lt;/span&gt;
&lt;span class="nx"&gt;VERSION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;v2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_root&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;version&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;VERSION&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/crash&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;crash&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nx"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Intentional failure for workshop demo&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="nd"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/health&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;health_check&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;Keep&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt; &lt;span class="nx"&gt;Load&lt;/span&gt; &lt;span class="nx"&gt;Balancer&lt;/span&gt; &lt;span class="nx"&gt;health&lt;/span&gt; &lt;span class="nx"&gt;check&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;healthy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;version&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;VERSION&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;Dockerfile&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.11-slim&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Build and Push the Docker Image to ECR
&lt;/h2&gt;

&lt;p&gt;First, authenticate the Docker CLI to your Amazon ECR registry, build the image, and push it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click &lt;code&gt;View Push Commands&lt;/code&gt; to see how to push your image to the ECR Registry, and perform the actions in your terminal ( Needs AWS CLI and Docker CLI for the commands to work )&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%2Fj0c3y2qbubmirawkodh5.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%2Fj0c3y2qbubmirawkodh5.png" alt=" " width="800" height="664"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In this case instead of being &lt;code&gt;codedeploy-workshop:latest&lt;/code&gt; , use &lt;code&gt;codedeploy-workshop:v2&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Register the New ECS Task Definition
&lt;/h2&gt;

&lt;p&gt;ECS needs to know about the new image. You define this in a JSON file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;task-def.json&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"family"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"codedeploy-workshop-task"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"executionRoleArn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;The Execution Role from the Task Definition of ECS&amp;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;"networkMode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"awsvpc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"containerDefinitions"&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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fastapi-container"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;AWS_ACCOUNT_ID&amp;gt;.dkr.ecr.ap-southeast-1.amazonaws.com/codedeploy-workshop:v2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"portMappings"&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;"containerPort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"hostPort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"protocol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tcp"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"requiresCompatibilities"&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="s2"&gt;"FARGATE"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cpu"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"memory"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"512"&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;Register this new definition with AWS to generate a new revision number (e.g., &lt;code&gt;codedeploy-workshop-task:2&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws ecs register-task-definition &lt;span class="nt"&gt;--cli-input-json&lt;/span&gt; file://task-def.json
&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%2Fiagdlc0im38j3fvlca8j.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%2Fiagdlc0im38j3fvlca8j.png" alt=" " width="800" height="251"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You will see the task definition on the ECS Task Definitions page now&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In another terminal, call the ALB URL to see which version is being delivered and keep it on until the CodeDeploy Trigger, but first you need to get the ALB URL&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%2F5vfxxfkekktdzx47fdn3.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%2F5vfxxfkekktdzx47fdn3.png" alt=" " width="800" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To get the ALB URL, Navigate to your CloudFormation Stack and go to outputs, in there you will see the LoadBalancerUrl, paste it in the command below to call the Load balancer
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"http://&amp;lt;ALB_URL&amp;gt;/"&lt;/span&gt; 
  &lt;span class="nb"&gt;sleep &lt;/span&gt;1
&lt;span class="k"&gt;done&lt;/span&gt;
&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%2Fklc4vx7q9fg1pffdgp81.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%2Fklc4vx7q9fg1pffdgp81.png" alt=" " width="800" height="92"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Trigger the CodeDeploy Deployment
&lt;/h2&gt;

&lt;p&gt;CodeDeploy uses an &lt;code&gt;appspec.yaml&lt;/code&gt; file to understand which Task Definition to deploy and how to map it to the load balancer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding the Task Definition
&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%2Fijt6ael0kcwhb1z6dd63.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%2Fijt6ael0kcwhb1z6dd63.png" alt=" " width="241" height="234"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to ECS Dashboard and in the sidebar, Navigate to Task Definitions&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%2Fwsqwqy8m5ogpexdq10sw.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%2Fwsqwqy8m5ogpexdq10sw.png" alt=" " width="799" height="188"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;From there, click on the Task Definition with your stack name&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%2Ft9cm3fjjupku2uz53ix6.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%2Ft9cm3fjjupku2uz53ix6.png" alt=" " width="599" height="191"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;From there, you will see the different revisions, click the most recent one&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%2Foy4z78p8sv59yksdbxa4.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%2Foy4z78p8sv59yksdbxa4.png" alt=" " width="800" height="299"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Copy the ARN and paste it to the TaskDefinition in &lt;code&gt;appspec.yaml&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;appspec.yaml&lt;/code&gt;&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;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;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;TargetService&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::ECS::Service&lt;/span&gt;
      &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Update this to the new revision you just generated in Step 3&lt;/span&gt;
        &lt;span class="na"&gt;TaskDefinition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arn:aws:ecs:ap-southeast-1:&amp;lt;AWS&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ACC&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ID&amp;gt;:task-definition/codedeploy-workshop-task:1"&lt;/span&gt;
        &lt;span class="na"&gt;LoadBalancerInfo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ContainerName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fastapi-container"&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;h2&gt;
  
  
  Blue/Green Deployment
&lt;/h2&gt;

&lt;p&gt;First, Lets take a look at Blue/Green Deployment with CodeDeploy&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws deploy create-deployment &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--application-name&lt;/span&gt; AppECS-workshop-cluster-fastapi-service &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--deployment-group-name&lt;/span&gt; DgpECS-workshop-cluster-fastapi-service &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--deployment-config-name&lt;/span&gt; CodeDeployDefault.ECSAllAtOnce &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--revision&lt;/span&gt; &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;revisionType&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;AppSpecContent&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;appSpecContent&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: {&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;content&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-Rs&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; appspec.yaml&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;}}"&lt;/span&gt;
&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%2F23qf5717hjol0jy6sdiy.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%2F23qf5717hjol0jy6sdiy.png" alt=" " width="800" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You will now see the deployment being made for a Blue/Green Deployment Configuration&lt;/li&gt;
&lt;li&gt;Now check what is returning from the while loop earlier&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%2Ft6djc6e78mrdqvg7c9k5.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%2Ft6djc6e78mrdqvg7c9k5.png" alt=" " width="796" height="79"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You will now see its redirecting the other container&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%2F0x3sdquz3swei4w2y7ny.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%2F0x3sdquz3swei4w2y7ny.png" alt=" " width="800" height="279"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the CodeDeploy dashboard, it also shows the traffic shifted&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now lets go back to the original, use the original task definition for the v1 server and edit &lt;code&gt;appspec.yaml&lt;/code&gt; and wait for the rollback&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;version: 0.0
Resources:
  - TargetService:
      Type: AWS::ECS::Service
      Properties:
        &lt;span class="c"&gt;# Update this to the new revision you just generated in Step 3&lt;/span&gt;
        TaskDefinition: &lt;span class="s2"&gt;"&amp;lt;Find Task Definition named CanaryBlueGreenWorkshopStackTaskDef&amp;gt;"&lt;/span&gt;
        LoadBalancerInfo:
          ContainerName: &lt;span class="s2"&gt;"fastapi-container"&lt;/span&gt;
          ContainerPort: 80

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

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws deploy create-deployment &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--application-name&lt;/span&gt; AppECS-workshop-cluster-fastapi-service &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--deployment-group-name&lt;/span&gt; DgpECS-workshop-cluster-fastapi-service &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--deployment-config-name&lt;/span&gt; CodeDeployDefault.ECSAllAtOnce &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--revision&lt;/span&gt; &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;revisionType&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;AppSpecContent&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;appSpecContent&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: {&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;content&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-Rs&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; appspec.yaml&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;}}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Call this again to start the roll back, call the while loop again and if it shows v1, the rollback has completed&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Canary Deployment
&lt;/h2&gt;

&lt;p&gt;After rolling back to v1 again, lets now try to deploy with canary, we will also look at how CodeDeploy automatically stop traffic shift to the replacement server once there are errors being seen&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws deploy create-deployment &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--application-name&lt;/span&gt; AppECS-workshop-cluster-fastapi-service &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--deployment-group-name&lt;/span&gt; DgpECS-workshop-cluster-fastapi-service &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--deployment-config-name&lt;/span&gt; CodeDeployDefault.ECSCanary10Percent5Minutes &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--revision&lt;/span&gt; &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;revisionType&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;AppSpecContent&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;appSpecContent&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: {&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;content&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-Rs&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; appspec.yaml&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;}}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once this command executes, CodeDeploy takes over. It spins up the the replacement container, waits for the &lt;code&gt;/health&lt;/code&gt; endpoint to pass, and instructs the ALB to send 10% of traffic to the new containers. If CloudWatch Alarms remain quiet for 5 minutes, it routes the remaining 90%, gracefully terminating the old containers&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%2Fh4zz734gxs0wgze01ebl.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%2Fh4zz734gxs0wgze01ebl.png" alt=" " width="800" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  See the Deployment in action during the 5 min window
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"http://&amp;lt;ALB_URL&amp;gt;/"&lt;/span&gt; 
  &lt;span class="nb"&gt;sleep &lt;/span&gt;1
&lt;span class="k"&gt;done&lt;/span&gt;
&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%2Fqajlvc0qa2qz9xuc36iu.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%2Fqajlvc0qa2qz9xuc36iu.png" alt=" " width="800" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;As you can see, it switches occasionally to v2 since there will 10% that will be redirected to the replacement server&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Call the crash endpoint in another terminal&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"http://&amp;lt;ALB_URL&amp;gt;/crash"&lt;/span&gt;
  &lt;span class="nb"&gt;sleep &lt;/span&gt;0.5
&lt;span class="k"&gt;done&lt;/span&gt;
&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%2F1g8a3fxdwvvz3x9ifbxb.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%2F1g8a3fxdwvvz3x9ifbxb.png" alt=" " width="800" height="136"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This triggers the CloudWatch alarm for 5XX errors&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%2Fon6kd40170qvpni2qn1s.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%2Fon6kd40170qvpni2qn1s.png" alt=" " width="799" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This then triggers CodeDeploy to start the rollback and stop the full rerouting to the replacement task and stay 100% to the original&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%2Fs2vxe6o0fa7a18tin15k.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%2Fs2vxe6o0fa7a18tin15k.png" alt=" " width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;As you can see in the Deployment Details, it succeeded in the rollback upon seeing the alarm&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%2Fsioxvugl657jc2x4wvwz.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%2Fsioxvugl657jc2x4wvwz.png" alt=" " width="344" height="140"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You might wonder why its 100% to replacement when it is supposed to be 100% to the original?&lt;/li&gt;
&lt;li&gt;Its because in the rollback process, the replacement is the original instance ( the 90% ) and the original here is the one being routed 10% earlier which was the replacement&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Clean Up
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Navigate to our stack in CloudFormation and press &lt;code&gt;Delete Stack&lt;/code&gt; . This will start deleting the resources you used effectively cleaning up your environment and avoid incurring more cost.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Takeaways
&lt;/h1&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Deployment is a Spectrum of Risk&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Standard "In-Place" deployments are the riskiest because they lack a safety buffer. Blue/Green and Canary strategies move the risk from the &lt;em&gt;infrastructure&lt;/em&gt; (will it boot?) to the &lt;em&gt;traffic&lt;/em&gt; (will it work for users?), allowing for zero-downtime releases.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Health Checks vs. CloudWatch Alarms&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Modern CI/CD requires two layers of defense:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Target Group Health Checks:&lt;/strong&gt; Ensure the container is "alive" before shifting any traffic (prevents Dead-on-Arrival deployments).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudWatch Alarms:&lt;/strong&gt; Monitor the application's "behavior" during the shift (prevents runtime bugs from affecting 100% of users).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Rollbacks are just "Forward" Deployments&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;In AWS CodeDeploy, a rollback isn't a "undo" button that rewinds time—it is a new, automated deployment that treats your last stable version as the "replacement." This ensures that even reverting a change follows a monitored, safe process.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Automation Reduces "Human Error" Burnout&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;By using &lt;code&gt;appspec.yaml&lt;/code&gt; and CodeDeploy, you remove the need for manual Load Balancer manipulation. This allows developers to focus on the code while the infrastructure handles the "bake time" and safety monitoring automatically.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cicd</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Automating Serverless: A Guide to CI/CD for AWS Lambda with GitHub Actions</title>
      <dc:creator>Breindel Medina</dc:creator>
      <pubDate>Tue, 11 Nov 2025 08:00:32 +0000</pubDate>
      <link>https://dev.to/kindadailybren/automating-serverless-a-guide-to-cicd-for-aws-lambda-with-github-actions-jh1</link>
      <guid>https://dev.to/kindadailybren/automating-serverless-a-guide-to-cicd-for-aws-lambda-with-github-actions-jh1</guid>
      <description>&lt;p&gt;Deploying serverless applications to the cloud has never been easier with AWS. You just upload your files as a zip to Lambda, turn on the function URL or integrate with an API Gateway, and voila! you are able to deploy your serverless app.&lt;/p&gt;

&lt;p&gt;But, this manual process has risks. You still have to manually check if the code is right. It might be that what you deploy is faulty, which causes your application to break. &lt;/p&gt;

&lt;p&gt;Since most developers upload their code to GitHub, what if there was a way to automate all of this checking and deployment with a single push to your repository?&lt;/p&gt;

&lt;p&gt;This is where GitHub Actions comes in.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is GitHub Actions?
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;GitHub Actions is a continuous integration and continuous delivery (CI/CD) platform built directly into GitHub. It allows you to automate your software development workflows right from where your code lives.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At its core, GitHub Actions uses the following concepts:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workflows&lt;/strong&gt;: These are automated processes defined in a YAML file (e.g., .github/workflows/deploy.yml). A workflow can be triggered by an event.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Events&lt;/strong&gt;: An event is a specific activity in your repository that triggers a workflow. The most common event is a push to a branch, but it can also be a pull_request, a new issue, or even a scheduled cron job.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Jobs&lt;/strong&gt;: A workflow is made up of one or more jobs. By default, jobs run in parallel. You can also configure them to run sequentially (e.g., a deploy job that runs only if a test job succeeds).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Runners&lt;/strong&gt;: A runner is a server (a fresh virtual machine) that runs your workflow's jobs. GitHub provides runners for Linux, Windows, and macOS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Steps&lt;/strong&gt;: A job is a series of steps. A step can be a simple shell command (like pip install -r requirements.txt) or a pre-built, reusable command called an Action (like actions/checkout to check out your code).&lt;/p&gt;

&lt;p&gt;By combining these, you can create a workflow that, upon a push to your main branch, automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sets up a clean environment.&lt;/li&gt;
&lt;li&gt;Installs your dependencies.&lt;/li&gt;
&lt;li&gt;Runs your unit tests.&lt;/li&gt;
&lt;li&gt;If the tests pass, it securely deploys your code to AWS Lambda.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Demonstration
&lt;/h1&gt;

&lt;p&gt;Let's first start with a very simple code snippet which we will use in this demonstration&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="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;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;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&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;Hello dev.to article!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Code Breakdown (app.py):&lt;/p&gt;

&lt;p&gt;&lt;code&gt;def lambda_handler(event, context)&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is the main function that AWS Lambda will call. event contains data about the trigger (e.g., from an API call), and context contains runtime information.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;return { ... }&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The function returns a dictionary. For a Lambda function responding to an HTTP request (like from a Function URL or API Gateway), it must include statusCode (like 200 for "OK") and a body (which must be a string, hence json.dumps).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Prerequisite 2: Your Test File
&lt;/h2&gt;

&lt;p&gt;Let's now create our test file which our CI will use&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;test_app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_simple_lambda_handler&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Call the handler
&lt;/span&gt;    &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&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;app&lt;/span&gt;&lt;span class="p"&gt;.&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="c1"&gt;# Check the response
&lt;/span&gt;    &lt;span class="k"&gt;assert&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;statusCode&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&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="k"&gt;assert&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello dev.to article!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# We run the test function directly
&lt;/span&gt;&lt;span class="nf"&gt;test_simple_lambda_handler&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;All tests passed!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Code Breakdown (test_app.py):&lt;/p&gt;

&lt;p&gt;&lt;code&gt;import app&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This line imports our app.py file so we can access its lambda_handler function.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;response = app.lambda_handler(event, context)&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We call our Lambda handler directly, passing in empty event and context objects (since our simple function doesn't use them).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;assert response['statusCode'] == 200&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is the core of the test. assert checks if a statement is true. If it's false, the test fails and stops. Here, we check if the statusCode is 200.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;assert "..." in body['message']&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We check if the response message contains the text we expect.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;test_simple_lambda_handler()&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We call the test function to run it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;print("All tests passed!")&lt;/code&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If all the assert statements pass, this line will run, letting us know our code works as expected.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Upload Your Code to GitHub
&lt;/h2&gt;

&lt;p&gt;Before we can set up GitHub Actions, our code needs to live in a GitHub repository. We'll assume you've already:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Created a new repository on GitHub.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Committed your files (app.py, test_app.py, requirements.txt).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pushed your code to the main branch.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Create AWS IAM User &amp;amp; Get Keys
&lt;/h2&gt;

&lt;p&gt;Now, let's get the AWS credentials GitHub Actions will use. We'll assume you're familiar with the AWS IAM console.&lt;/p&gt;

&lt;p&gt;You'll need to create a new IAM User with CLI access. Give it a descriptive name like github-lambda-deployer.&lt;/p&gt;

&lt;p&gt;For permissions, attach the AWSLambda_FullAccess policy.&lt;/p&gt;

&lt;p&gt;(Note: For a real-world project, it's best practice to create a more restrictive custom policy that only allows the lambda:UpdateFunctionCode and lambda:UpdateFunctionConfiguration actions on your specific function's ARN.)&lt;/p&gt;

&lt;p&gt;After creating the user, generate an Access key and Secret access key. Copy these immediately; you will need them for the next step.&lt;/p&gt;
&lt;h2&gt;
  
  
  Add Access Keys to GitHub Secrets
&lt;/h2&gt;

&lt;p&gt;Now let's securely store those keys in GitHub.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In your GitHub repository, go to Settings.&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%2F3j9w7em91xlwp9sbe4a1.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%2F3j9w7em91xlwp9sbe4a1.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the left sidebar, navigate to Secrets and variables &amp;gt; Actions.&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%2Fd0kafq7vj7nep834wju5.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%2Fd0kafq7vj7nep834wju5.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click the New repository secret button.&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%2Fhv8osimgnm5nzp78zl1k.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%2Fhv8osimgnm5nzp78zl1k.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create two secrets, one for the Access Key, and one for the Secret Access Key:&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%2Fzoe9nukpow7tyaefop9b.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%2Fzoe9nukpow7tyaefop9b.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click Add secret.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create the second secret:&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%2Fgzrsrv3hdtod4drlhtmi.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%2Fgzrsrv3hdtod4drlhtmi.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click Add secret.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You should now have two secrets, AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY, listed under "Repository secrets." GitHub Actions can now access them using the ${{ secrets.NAME }} syntax.&lt;/p&gt;
&lt;h2&gt;
  
  
  Create Lambda Function
&lt;/h2&gt;

&lt;p&gt;Let's now create the AWS Lambda function that we will use.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create a function with name "blog-func" and Python 3.13 runtime&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%2Fvjrskm0z0ay8s465yis9.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%2Fvjrskm0z0ay8s465yis9.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After it is done creating, Edit the handler from &lt;code&gt;lambda_handler.lambda_handler&lt;/code&gt; to &lt;code&gt;app.lamba_handler&lt;/code&gt;&lt;/p&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%2F0z5kaf9gsuz793kcclmb.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%2F0z5kaf9gsuz793kcclmb.png" alt=" "&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%2F7u48yrz9wwgqmvt3qydy.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%2F7u48yrz9wwgqmvt3qydy.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Create the GitHub Actions Workflow (CI/CD)
&lt;/h2&gt;

&lt;p&gt;This is the heart of our pipeline. In your repository, create a new directory .github, and inside that, another directory workflows. Finally, create the file deploy.yml inside it. Replace the placeholders in this file before you commit.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;aws-region&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set this to the region where your Lambda function exists (e.g., us-west-2, ap-southeast-1).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;--function-name&lt;/code&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Change YOUR-LAMBDA-FUNCTION-NAME to the exact name of your function in AWS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;File Path: .github/workflows/deploy.yml&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Name of your workflow&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;CI-CD for AWS Lambda&lt;/span&gt;

&lt;span class="c1"&gt;# When this workflow is triggered&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;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="c1"&gt;# Run on pushes to the main branch&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;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="c1"&gt;# Run on pull requests targeting main&lt;/span&gt;

&lt;span class="c1"&gt;# A workflow run is made up of one or more jobs&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# The "test" job (Continuous Integration)&lt;/span&gt;
  &lt;span class="na"&gt;test&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 Unit Tests&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="c1"&gt;# Use a Linux runner&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 repository&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&lt;/span&gt; &lt;span class="c1"&gt;# Action to check out your code&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 Python&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-python@v5&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;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.11'&lt;/span&gt; &lt;span class="c1"&gt;# Use a specific Python version&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 test 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;# No dependencies needed for this simple handler&lt;/span&gt;
          &lt;span class="s"&gt;echo "No test dependencies to 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;Run tests (CI)&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;python test_app.py&lt;/span&gt;

  &lt;span class="c1"&gt;# The "deploy" job (Continuous Deployment)&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to AWS Lambda&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;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt; &lt;span class="c1"&gt;# This job will ONLY run if the "test" job succeeds&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 repository&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&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 Python&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-python@v5&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;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.11'&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create Zip File&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;zip lambda_function.zip app.py&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="c1"&gt;# Use the secrets you created in Step 3&lt;/span&gt;
          &lt;span class="na"&gt;aws-access-key-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_ACCESS_KEY_ID }}&lt;/span&gt;
          &lt;span class="na"&gt;aws-secret-access-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_SECRET_ACCESS_KEY }}&lt;/span&gt;
          &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ap-southeast-1&lt;/span&gt; &lt;span class="c1"&gt;# Change to your Lambda's 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 AWS Lambda (CD)&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 lambda update-function-code \&lt;/span&gt;
            &lt;span class="s"&gt;--function-name blog-func \ # Change to Lambda Name&lt;/span&gt;
            &lt;span class="s"&gt;--zip-file fileb://lambda_function.zip&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Workflow Breakdown
&lt;/h3&gt;

&lt;p&gt;Let's break down that YAML file.&lt;/p&gt;

&lt;p&gt;The Triggers:&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;on&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;pull_request&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;This tells GitHub when to run the workflow. It will run on any push to the main branch, and also on any pull_request that targets the main branch. This is great for checking if a new change breaks the tests before you merge it.&lt;/p&gt;
&lt;h3&gt;
  
  
  Job 1: test (The "CI" part)
&lt;/h3&gt;

&lt;p&gt;This job is our Continuous Integration check.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;name: Run Unit Tests&lt;/code&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The display name in the Actions tab.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;runs-on: ubuntu-latest&lt;/code&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It runs on a fresh Ubuntu virtual machine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;uses: actions/checkout@v4&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This action checks out your repository code onto the runner.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;uses: actions/setup-python@v5&lt;/code&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This action sets up the Python version we want.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;run: python test_app.py&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is the key CI step. It runs our test file. If test_app.py fails (e.g., an assert is false), it will exit with an error, which fails the job and stops the entire pipeline.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Job 2: deploy (The "CD" part)
&lt;/h3&gt;

&lt;p&gt;This job is our Continuous Deployment step.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;needs: test&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is the most important line for safety. It tells GitHub: Do not even start this job unless the test job finished successfully. This is what prevents you from deploying broken code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;run: zip lambda_function.zip app.py&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This bundles our app.py into the .zip file that Lambda requires.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;uses: aws-actions/configure-aws-credentials@v4&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is an official action from AWS. It uses the aws-access-key-id and aws-secret-access-key you provide to configure the AWS CLI on the runner.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;with: ... ${{ secrets.AWS_ACCESS_KEY_ID }}&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is how you securely pass your GitHub Secrets to the action. They are injected at runtime and never printed in the logs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;run: aws lambda update-function-code ...&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is the final AWS CLI command that actually performs the deployment, uploading your new lambda_function.zip to the function you specified.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Commit and Push
&lt;/h3&gt;

&lt;p&gt;Commit all your new files (specifically .github/workflows/deploy.yml) and push them to your main branch. Go to the "Actions" tab in your GitHub repository.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You will see your "CI-CD for AWS Lambda" workflow start.&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%2Fv4cu9ggnfzl024103lc2.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%2Fv4cu9ggnfzl024103lc2.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It will first run the test job. You can click to see the output, including "All tests passed!".&lt;/p&gt;

&lt;p&gt;Once the test job succeeds, the deploy job will begin. You will see it zip the code, configure credentials, and run the aws lambda update-function-code command.&lt;/p&gt;

&lt;p&gt;Check AWS: Go to your Lambda function in the AWS Console. You should see that the "Last modified" time has just updated. If you test your function in the console, it will now be running the code from your 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%2F0lr746bjj0l5xtn7qa7m.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%2F0lr746bjj0l5xtn7qa7m.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also see the result by turning on the Function URL and opening it!&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%2Fh87ejw921cxlgpetv2gl.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%2Fh87ejw921cxlgpetv2gl.png" alt=" "&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%2F69bfddgp8xh6mvu8hdk1.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%2F69bfddgp8xh6mvu8hdk1.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Conclusion and Next Steps
&lt;/h1&gt;

&lt;p&gt;You now have a robust and automated CI/CD pipeline. Any time you push a change to your main branch, your code will be automatically tested, and if the tests pass, it will be securely deployed to AWS Lambda. This directly solves the problem of "manially checking" and deploying "faulty" code.&lt;/p&gt;

&lt;p&gt;From here, you can expand this pipeline to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use different branches for staging and production environments.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Store your Lambda function name as a GitHub Secret instead of hard-coding it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add a build step that installs requirements.txt into a folder before zipping.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run more complex integration tests that actually invoke the Lambda URL.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;That is it for this demonstration! Thank you for reading this informative blog, you can check out the code for this in my github&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/kindadailybren" rel="noopener noreferrer"&gt;
        kindadailybren
      &lt;/a&gt; / &lt;a href="https://github.com/kindadailybren/CI-CD-for-AWS-Lambda-with-GitHub-Actions-Blog" rel="noopener noreferrer"&gt;
        CI-CD-for-AWS-Lambda-with-GitHub-Actions-Blog
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;





</description>
      <category>webdev</category>
      <category>aws</category>
      <category>github</category>
      <category>programming</category>
    </item>
    <item>
      <title>Build a Simple Grocery Tracker App using Vue JS and Supabase</title>
      <dc:creator>Breindel Medina</dc:creator>
      <pubDate>Tue, 11 Nov 2025 07:58:29 +0000</pubDate>
      <link>https://dev.to/up_min_sparcs/build-a-simple-grocery-tracker-app-using-vue-js-and-supabase-51g6</link>
      <guid>https://dev.to/up_min_sparcs/build-a-simple-grocery-tracker-app-using-vue-js-and-supabase-51g6</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Sometimes, the best way to learn a new technology is to start building something&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What better way to do this than to pick a stack, spin up a project, and figure things out along the way?&lt;/p&gt;

&lt;p&gt;For this project, the stack is simple but powerful: Vue for the frontend and Supabase for the backend. Vue makes it easy to create responsive, dynamic interfaces, while Supabase handles the database, API, and even real-time functionality — all without needing to write a full backend from scratch.&lt;/p&gt;

&lt;p&gt;This article walks through the entire process — from setting things up to building core features like adding, updating, and deleting data, and finally exploring what could be improved or added next. Along the way, you’ll find practical examples, key takeaways, and code snippets that break down how each component works, helping you build a solid grasp of these technologies. By the end, you’ll (hopefully) feel confident enough to take what you’ve learned and start building your own projects using Vue and Supabase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack Breakdown
&lt;/h2&gt;

&lt;p&gt;This project is built using two main technologies: Vue and Supabase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vue&lt;/strong&gt; is a progressive JavaScript framework that is great for building reactive user interfaces. It’s lightweight, easy to learn, and provides a clean syntax that makes frontend development feel intuitive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supabase&lt;/strong&gt; is an open-source Firebase alternative that provides a full backend out of the box — including a PostgreSQL database, authentication, file storage, and auto-generated APIs. It’s developer-friendly, easy to set up, and integrates smoothly with frontend frameworks like Vue.&lt;/p&gt;

&lt;p&gt;Together, Vue and Supabase make a solid stack for quickly building modern web apps — without needing to spin up an entire backend from scratch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vue, Supabase, and Why We Chose Them
&lt;/h2&gt;

&lt;p&gt;When trying to dive into something new, we knew we wanted a stack that was both easy to use and powerful enough to build something real. Vue and Supabase ended up being the perfect combo for that.&lt;/p&gt;

&lt;p&gt;We picked Vue because of how intuitive it is — the syntax is clean, straightforward, and just makes sense. It didn’t feel overwhelming to dive into, and building dynamic UIs felt smooth from the start.&lt;/p&gt;

&lt;p&gt;For the backend, we went with Supabase. It’s open source and super easy to set up — no complicated config or boilerplate. We got a fully functional backend out of the box, complete with a PostgreSQL database, authentication, file storage, and even real-time updates.&lt;/p&gt;

&lt;p&gt;Together, these two tools let us focus on actually building the project instead of getting stuck on setup. They gave us a fast way to go from an idea to a working app, while learning a lot in the process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the Frontend
&lt;/h2&gt;

&lt;p&gt;Alright, for this project, we’re keeping things light and fast by using Vite. &lt;/p&gt;

&lt;p&gt;What is &lt;strong&gt;Vite&lt;/strong&gt;? Think of it as a modern build tool that makes setting up and running your frontend super quick. &lt;/p&gt;

&lt;p&gt;Difference between Vite and Vue? &lt;br&gt;
Vite is just the tool that runs your Vue app behind the scenes. Vue handles the UI stuff, Vite handles the dev server and bundling.&lt;/p&gt;

&lt;p&gt;We’ll be using npm as our package manager, so make sure that’s installed. To get started, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm create vite@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Follow the prompts:&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%2Fa0ce9drot5qvjk6w2vtp.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%2Fa0ce9drot5qvjk6w2vtp.png" alt=" "&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%2Fltph0dq85nxlf7e1jb5s.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%2Fltph0dq85nxlf7e1jb5s.png" alt=" "&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%2Fw5fwly0az8ff2lp78vf5.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%2Fw5fwly0az8ff2lp78vf5.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next up, we’ll set up Pinia for state management (a fancy way of saying “shared data between components”):&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install pinia
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Once that's installed, use this code in the main.ts:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { createPinia } from 'pinia'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This code sets up Pinia, which is a tool that helps your app share data between different components. First, it brings in Pinia, then creates a "store" (like a central place to keep your app's data). After that, it starts your Vue app and tells it to use that store so every part of the app can access and update shared data easily.&lt;/p&gt;

&lt;p&gt;After that, feel free to do a little cleaning. Delete any of the default files you’re not using. And that's it. Your frontend is ready to roll!&lt;br&gt;
Next up, backend!&lt;/p&gt;
&lt;h2&gt;
  
  
  Setting up Supabase
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Create a Supabase Account&lt;/strong&gt;&lt;br&gt;
First, you'll need a Supabase account. Just sign up, it's quick and they are offering a free tier. Once you're in, create a new project from the dashboard. For this project, we are going to use just the Supabase dashboard to configure our database. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configure the Database&lt;/strong&gt;&lt;br&gt;
Head over to the "Table Editor" section. Here you can create new tables. Make a table called “product”. Add a few attributes or columns: id (UUID), name (varchar), quantity (int4), and price (numeric).&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Column Name     | Type
id          | UUID
name        | varchar
quantity    | int4
price       | numeric
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's all we need for this app. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connect Supabase to Vue JS&lt;/strong&gt;&lt;br&gt;
Next, in your frontend project, make a directory called supabase and inside it, create a file named supabaseClient.ts. And, drop the following code:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { createClient } from "@supabase/supabase-js";
const supabase_url = import.meta.env.VITE_SUPABASE_URL;
const supabase_key = import.meta.env.VITE_SUPABASE_KEY;
export const supabase = createClient(supabase_url, supabase_key);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;What this does is it sets up a connection between your app and your Supabase backend by creating a client using the project URL and API Key (p.s. store the url and key in your .env file for security). With that client, you are now connected to your hosted Supabase backend.&lt;/p&gt;
&lt;h2&gt;
  
  
  Components Structure
&lt;/h2&gt;

&lt;p&gt;In this section, you will see how the main components of the web app are implemented. Each code snippet is followed by an explanation to help you understand how the components work together to build interactive UI features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Button Component:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script lang="ts" setup&amp;gt;
defineProps&amp;lt;{ msg?: string }&amp;gt;()
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
    &amp;lt;button class="border-[1px] border-dashed px-4 py text-2xl cursor-pointer hover:bg-gray-300 transition-all duration-150"&amp;gt;{{ msg }}&amp;lt;/button&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;This is a reusable button component. It accepts an optional msg prop to display text on the button. The button is styled with a dashed border, padding, large text, and a hover effect that changes the background color. It is used across different components for consistent styling and interactivity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Forms Component:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;section&amp;gt;
    &amp;lt;div&amp;gt;
      &amp;lt;div class="flex flex-col gap-4"&amp;gt;
        &amp;lt;div class="flex justify-between text-2xl xl:text-3xl items-center"&amp;gt;
          &amp;lt;p&amp;gt;{{ nameRef }}&amp;lt;/p&amp;gt;
          &amp;lt;div class="flex gap-10"&amp;gt;
            &amp;lt;div class="flex gap-2 xl:gap-4"&amp;gt;
              &amp;lt;p&amp;gt;&amp;amp;#x20B1 {{ priceRef }}&amp;lt;/p&amp;gt;
              &amp;lt;p&amp;gt;x&amp;lt;/p&amp;gt;
              &amp;lt;p&amp;gt;{{ quantityRef }}&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div&amp;gt;
              &amp;lt;button class="hover:cursor-pointer" @click="isEditing = !isEditing"&amp;gt;
                Edit
              &amp;lt;/button&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/section&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;This part of the Forms component displays the product's name, price, and quantity in a well-structured layout. There’s an "Edit" button that toggles the editing mode by changing the isEditing value.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!-- edit state --&amp;gt;
&amp;lt;div v-if="isEditing" class="flex flex-col gap-2"&amp;gt;
  &amp;lt;div class="flex items-center justify-between text-2xl gap-4"&amp;gt;
    &amp;lt;input v-model="nameRef" type="text" :placeholder="product?.name" class="w-full border-b border-dashed px-4" @keydown.enter="update" /&amp;gt;
    &amp;lt;div class="flex items-center justify-center gap-1"&amp;gt;
      &amp;lt;Button @click="decrement" msg="-" /&amp;gt;
      &amp;lt;Button @click="quantityRef++" msg="+" class="hover:bg-green-400" /&amp;gt;
      &amp;lt;button @click="deleteProd" class="border-[1px] border-dashed px-4 py-1 text-2xl cursor-pointer hover:bg-red-400 transition-all duration-150"&amp;gt;
        &amp;lt;Trash class="w-6 h-6" /&amp;gt;
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;Button @click="update" msg="Save" class="hover:bg-green-400" /&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;When editing is enabled, the user can change the product name, increase or decrease the quantity, or delete the product. It uses the Button component for interactivity and consistent styling.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script setup lang="ts"&amp;gt;
import { ref, computed } from 'vue'
import { updateProduct, deleteProduct } from '../utils/actions.ts'
import Button from './Button.vue'
import Trash from './Trash.vue'
import { useAllProductsStore } from '../stores/allProducts.ts'

const productStore = useAllProductsStore()

const props = defineProps({
  id: { type: String, required: true },
});

const product = computed(() =&amp;gt;
  productStore.products.find((p) =&amp;gt; p.id === props.id)
)

const nameRef = ref(product.value?.name ?? "")
const priceRef = ref(product.value?.price ?? 0)
const quantityRef = ref(product.value?.quantity ?? 0)

const update = async () =&amp;gt; {
  isEditing.value = !isEditing.value
  if (!product.value) return;
  await updateProduct(product.value.id, nameRef.value, quantityRef.value, priceRef.value)
  isEditing.value = false
}

const deleteProd = async () =&amp;gt; {
  await deleteProduct(props.id);
};

const decrement = () =&amp;gt; {
  if (quantityRef.value &amp;gt; 1) quantityRef.value--
}
const isEditing = ref(false)
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;This script defines the reactive variables and functions used in the component. It connects with the Pinia store to find and update products. It also handles product deletion and quantity adjustment, toggled by the edit state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Music Player Component:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class="flex gap-10"&amp;gt;
    &amp;lt;div&amp;gt;
      &amp;lt;button v-if="isPlaying" @click="() =&amp;gt; { play(); isPlaying = false; }" class="bg-green-400 px-8 py-2 hover:bg-green-500 cursor-pointer"&amp;gt;PLAY&amp;lt;/button&amp;gt;
      &amp;lt;button v-else @click="() =&amp;gt; { stop(); isPlaying = true; }" class="bg-red-400 px-8 py-2 hover:bg-red-500 cursor-pointer"&amp;gt;STOP&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;
      &amp;lt;button v-if="isPlaying2" @click="() =&amp;gt; { play2(); isPlaying2 = false; }" class="bg-yellow-400 px-8 py-2 hover:bg-orange-500 cursor-pointer"&amp;gt;PLAY&amp;lt;/button&amp;gt;
      &amp;lt;div v-else class="flex flex-col justify-center"&amp;gt;
        &amp;lt;button @click="() =&amp;gt; { pause(); isPlaying2 = true; }" class="bg-red-400 px-8 py-2 hover:bg-red-500 cursor-pointer"&amp;gt;PAUSE&amp;lt;/button&amp;gt;
        &amp;lt;div class="flex-col text-center text-white hidden"&amp;gt;
          &amp;lt;p class="text-2xl"&amp;gt;Playing Naaalala Ka&amp;lt;/p&amp;gt;
          &amp;lt;p&amp;gt;by&amp;lt;/p&amp;gt;
          &amp;lt;p class="text-2xl"&amp;gt;Rey Valera&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;This component provides buttons to control two separate audio tracks. One plays background music, and the other plays a specific song. Buttons toggle between play and stop/pause modes
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script lang="ts" setup&amp;gt;
import { ref } from 'vue';
import { useSound } from '@vueuse/sound';
import bgMusic from '../assets/sound/bg-music-grocery-store.mp3'
import reyValera from '../assets/sound/naaalala-ka-rey-valera.mp3'

const isPlaying = ref(true);
const isPlaying2 = ref(true);

const { play, stop } = useSound(bgMusic, {
  volume: 0.5,
  loop: true,
  autoplay: true,
})

const { play: play2, pause } = useSound(reyValera, {
  volume: 0.2,
  loop: false,
  autoplay: true,
})
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;This music player provides play/pause functionality for two audio tracks. It uses the @vueuse/sound library to control audio playback and manages playback states with Vue's reactivity system.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  View, Add, Update, Delete Functionality
&lt;/h2&gt;

&lt;p&gt;In this section of the article, you will see how the CRUD functionalities of the web app are implemented with the following code. The code may look intimidating, but this section will also explain key terms and syntax for your understanding.&lt;/p&gt;

&lt;p&gt;An important concept and syntax that needs to be understood to fully grasp the code is async and await.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;async&lt;/code&gt; is a keyword used to declare a function as asynchronous. It allows the use of await inside it and ensures the function returns a promise.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;await&lt;/code&gt; is used inside an async function to pause the execution of the function until the awaited promise is resolved or rejected. This helps write asynchronous code in a cleaner, more readable way, as if it were synchronous.&lt;/p&gt;

&lt;p&gt;Now that you know the basics of async and await, let's proceed with the code implementation of the CRUD.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;View All Items:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async fetchProducts() {
  this.loading = true;
  const { data, error } = await supabase.from("product").select("*");
  if (error) {
    console.error("Error fetching products:", error);
    this.loading = false;
    return;
  }
  this.products = data as Product[];
  this.loading = false;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;This function retrieves all items from the product table using Supabase. It sets a loading flag to true while fetching and resets it afterward. If there's an error during the fetch, it logs the error; otherwise, it stores the result in the products variable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Add Items:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const addItem = async (
  name: string,
  price: number,
  quantity: number,
) =&amp;gt; {
  const { data, error } = await supabase
    .from("product")
    .insert([{ name, price, quantity }]);
  if (error) console.error("Insert failed", error);
  else console.log("Inserted item", data);
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;This function inserts a new product into the &lt;code&gt;product&lt;/code&gt; table. It accepts the item's name, price, and quantity as arguments. If the insertion fails, it logs an error; otherwise, it confirms the item was successfully inserted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Update Items:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const updateProduct = async (
  id: string,
  productName: string,
  quantity: number,
  price: number,
) =&amp;gt; {
  const { data, error } = await supabase
    .from("product")
    .update({
      name: productName,
      quantity: quantity,
      price: price,
    })
    .eq("id", id);

  if (error) {
    console.error("Update error:", error.message);
  } else {
    console.log("Updated successfully:", data);
  }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;This function updates an existing product in the table. It uses the product's id to find the specific item and updates its name, quantity, and price. An error message is shown if the update fails, otherwise it logs the updated data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Delete Items:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const deleteProduct = async (id: string) =&amp;gt; {
  const { data, error } = await supabase
    .from("product") // your table name
    .delete()

    .eq("id", id);

  if (error) {
    console.error("Delete error:", error.message);
  } else {
    console.log("Deleted row:", data);
  }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;This function deletes a product from the table using its id. It filters the product to delete using the .eq("id", id) method. If an error occurs, it logs the error message; otherwise, it confirms the deletion.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Now that you've seen the essential features of this app, here are some ideas you can implement on your own to go even further. They're great opportunities to deepen your knowledge of Vue and Supabase:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Authentication System (Auth)&lt;/strong&gt;: Try adding user login and registration. It’s a great way to learn about route protection and Supabase auth integration.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Advanced Filtering and Sorting&lt;/strong&gt;: Add features like search bars or dropdowns to filter and sort items by name, price, or quantity.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Different Lists for Different Users&lt;/strong&gt;: Modify your database structure to include a column that assigns each item to a specific user. Then filter data based on who's logged in so each user sees only their own list.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for checking out our article! We hope this gave you not just insight into building with Vue and Supabase, but also ideas you’re excited to try out.&lt;/p&gt;

&lt;p&gt;If you're curious to explore the actual code or want to see the app in action, feel free to check out our GitHub repository and live demo:&lt;/p&gt;

&lt;p&gt;GitHub Repo: &lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/kindadailybren" rel="noopener noreferrer"&gt;
        kindadailybren
      &lt;/a&gt; / &lt;a href="https://github.com/kindadailybren/groceryTracker_SPARCS" rel="noopener noreferrer"&gt;
        groceryTracker_SPARCS
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;🛒 groceryTracker_SPARCS&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A simple and efficient grocery tracking app built with Vue 3, TypeScript, and Vite. This project is designed to help users manage grocery items through a clean interface, with fast performance powered by Vite and strong type safety from TypeScript.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🚀 Features&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;✅ Add and remove grocery items
📦 View list of tracked items&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;📦 Tech Stack&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Framework: Vue 3&lt;/p&gt;
&lt;p&gt;Build Tool: Vite&lt;/p&gt;
&lt;p&gt;Language: TypeScript&lt;/p&gt;
&lt;p&gt;State Management: Pinia (planned/included)&lt;/p&gt;
&lt;p&gt;Backend: Supabase (optional, based on context)&lt;/p&gt;
&lt;p&gt;🛠️ Project Setup&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;# Clone the repository
git clone https://github.com/kindadailybren/groceryTracker_SPARCS.git
cd groceryTracker_SPARCS

# Install dependencies
npm install

# Start development server
npm run dev
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🧪 Scripts&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Command   Description
&lt;code&gt;npm run dev&lt;/code&gt;    Start local dev server
&lt;code&gt;npm run build&lt;/code&gt;  Build for production
&lt;code&gt;npm run lint&lt;/code&gt;   Lint the code&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;👨‍💻 Author&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Errol Minguez&lt;br&gt;
UP Mindanao · BS Computer Science&lt;br&gt;
Breindel Medina&lt;br&gt;
UP Mindanao · BS Computer Science&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;📄 License&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;This project is licensed under the…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/kindadailybren/groceryTracker_SPARCS" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Live Demo: &lt;br&gt;
&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://grocery-tracker-sparcs.vercel.app/" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;grocery-tracker-sparcs.vercel.app&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Explore the codebase, try running it locally, or even fork it and start building your own version!&lt;/p&gt;

</description>
      <category>vue</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
