<?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.us-east-2.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>OverTheWire Wargames : Natas - LOTS OF DOCS, LOTS OF VULNS</title>
      <dc:creator>Breindel Medina</dc:creator>
      <pubDate>Fri, 26 Jun 2026 16:00:17 +0000</pubDate>
      <link>https://dev.to/kindadailybren/overthewire-wargames-natas-lots-of-docs-lots-of-vulns-51gc</link>
      <guid>https://dev.to/kindadailybren/overthewire-wargames-natas-lots-of-docs-lots-of-vulns-51gc</guid>
      <description>&lt;p&gt;I recently tackled the Natas Wargames by OverTheWire, following my completion of the Bandit series. While Bandit focused on the fundamentals of Linux and system administration, Natas shifted its focus toward Web Application Security. This transition provided deep insight into how modern web servers and applications function under the hood. Throughout this challenge, I explored fundamental server-side vulnerabilities, HTTP request manipulation, and gained an appreciation for secure coding practices. I would like to share my journey, the concepts I mastered, and the highlights and challenges I encountered along the way.&lt;/p&gt;

&lt;h1&gt;
  
  
  Highlights
&lt;/h1&gt;

&lt;p&gt;In the process of trying to solve the problems, going over resources on web vulnerabilities, and reading documentation and write ups on certain levels, I frequently saw &lt;strong&gt;Burp Suite&lt;/strong&gt; being used as an essential tool for analysis and for intercepting or manipulating network traffic and requests. Seeing that some of these levels utilized Burp Suite, I had to learn how to navigate the software, and I found it genuinely enjoyable to master its features. Getting hands-on experience with intercepting traffic and applying concepts like &lt;strong&gt;URL encoding&lt;/strong&gt; to bypass client-side input restrictions was a fun and highly educational process.&lt;/p&gt;

&lt;p&gt;I was also able to learn a lot of concepts regarding web vulnerabilities and strategies to exploit these vulnerabilities such as the &lt;code&gt;robots.txt&lt;/code&gt; used in websites to avoid &lt;strong&gt;web crawling&lt;/strong&gt;, &lt;strong&gt;Session Injections&lt;/strong&gt;, &lt;strong&gt;Command Injections&lt;/strong&gt;, &lt;strong&gt;SQL Injections&lt;/strong&gt; as well as &lt;strong&gt;Blind SQL Injections&lt;/strong&gt;, &lt;strong&gt;Brute Forcing&lt;/strong&gt;, &lt;strong&gt;Deserialization&lt;/strong&gt;, and a little bit of &lt;strong&gt;Cryptography&lt;/strong&gt; which was used in some levels.&lt;/p&gt;

&lt;p&gt;As I progressed toward the later levels, the challenges shifted to the Perl programming language, which forced me to learn its specific syntax and functions. Through this, I realized that programming languages are functionally similar in many ways. The vulnerabilities I encountered in levels that used PHP were often present in Perl as well, simply manifesting through different mechanisms. This series also taught me the vital importance of reading official documentation, as many solutions hinged on understanding the specific security implications of a language's syntax and how it can be misused.&lt;/p&gt;

&lt;h1&gt;
  
  
  Lowlights
&lt;/h1&gt;

&lt;p&gt;I found my self being stuck on a lot of these levels without really knowing what to do next apart from what I already knew. So, I found myself relying on solutions and write ups made by people who tried and solve these problems as well. What I realized was that reading how people approached these problems gave a lot more insight than AI ever could. It showed their very unique thought processes and showed technique that differed from one another which also made me realize another thing, &lt;em&gt;Problems can be approached a whole lot of different ways&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;On the note of being stuck on many different levels, I realized that I really still lacked a lot of knowledge on how web servers worked which made me wake up to the technical level that I am currently at. That fact gives me a lot of hope, it signifies that I still have a lot to learn and a lot ahead of me in terms of exploration and discovery.&lt;/p&gt;

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

&lt;p&gt;Finishing Natas really changed how I look at websites and, more importantly, how I approach creating them. I stopped seeing them as just static pages to click through and started seeing them as interconnected puzzles where every single input field is a potential vulnerability.&lt;/p&gt;

&lt;p&gt;Ultimately, this challenge made me a better developer by forcing me to learn the ins and outs of security. Understanding these vulnerabilities from the inside out has changed how I write code, I’m no longer just building for functionality, but also keeping in the back of my mind that it could be penetrated maliciously. Being aware of these common pitfalls and knowing how to prevent them allows me to design more secure applications.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>cybersecurity</category>
      <category>learning</category>
      <category>security</category>
    </item>
    <item>
      <title>OverTheWire Wargames : Bandit - LOTS OF UNIX AND NETWORKING GOODNESS</title>
      <dc:creator>Breindel Medina</dc:creator>
      <pubDate>Tue, 16 Jun 2026 13:07:52 +0000</pubDate>
      <link>https://dev.to/kindadailybren/overthewire-wargames-bandit-lots-of-unix-and-networking-goodness-504b</link>
      <guid>https://dev.to/kindadailybren/overthewire-wargames-bandit-lots-of-unix-and-networking-goodness-504b</guid>
      <description>&lt;p&gt;I recently took the challenge of one of the OverTheWire Wargames which is &lt;strong&gt;Bandit&lt;/strong&gt;. It tackled a lot of concepts on UNIX and Networking which could help in actually being able to play the other Wargames that they have. In the process of trying to progress through the levels in Bandit, I have learned a lot on the basics of UNIX/Linux and the Networking on these systems (and a little bit of Git as well) and I would like to share the concepts I have learned throughout the challenge&lt;/p&gt;

&lt;h1&gt;
  
  
  ssh
&lt;/h1&gt;

&lt;p&gt;The first concept that was apparent throughout the levels to progress is &lt;code&gt;ssh&lt;/code&gt;. The whole game was located on their SSH server, which players needed to access through their CLIs using the &lt;code&gt;ssh&lt;/code&gt; command. Every level transition essentially boiled down to connecting to the next user with a password obtained from the previous level, so getting comfortable with the syntax &lt;code&gt;ssh bandit0@bandit.labs.overthewire.org -p 2220&lt;/code&gt; became second nature fairly quickly.&lt;/p&gt;

&lt;p&gt;As the levels progressed, password-based authentication wasn't always the way in. Some levels handed over an RSA private key instead of a plaintext password, which meant using the &lt;code&gt;-i&lt;/code&gt; flag to point &lt;code&gt;ssh&lt;/code&gt; to that identity file (e.g. &lt;code&gt;ssh -i bandit_key bandit14@bandit.labs.overthewire.org -p 2220&lt;/code&gt;). This tied directly into the broader concept of key-based authentication, where the private key proves identity in place of typing a password.&lt;/p&gt;

&lt;p&gt;There were also levels where the goal wasn't just to log in, but to execute a specific command or drop into a different shell on the remote end as part of the solution. This is where the &lt;code&gt;-t&lt;/code&gt; flag came in, forcing pseudo-terminal allocation so that interactive programs (like another shell) would behave correctly when invoked through &lt;code&gt;ssh&lt;/code&gt; rather than just running non-interactively and returning.&lt;/p&gt;




&lt;h1&gt;
  
  
  Unix/Linux Commands, Features and Their Intricacies
&lt;/h1&gt;

&lt;p&gt;This is where I had the most of the fun stuff and most of my learning happened when trying to perform these commands. These are some of the core commands and features I picked up when trying to progress through the levels and how it actually was used in some of the levels:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;cat&lt;/code&gt;, &lt;code&gt;ls&lt;/code&gt;, &lt;code&gt;more&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;These commands are the bread and butter for viewing files and directory contents, including handling tricky filenames (spaces, dashes, hidden files) which was present on the levels. One of the most notable parts of the game in which I had to use this command was at Level 2 -&amp;gt; Level 3. The level contained a file named &lt;code&gt;--spaces in this filename--&lt;/code&gt;, the problem of a hyphen on the filename appears which typically means that there will be a flag to be passed to the command which causes it to wait an extra flag input. There are also spaces on the file name so the way to solve those problems is to utilize &lt;code&gt;./&lt;/code&gt; when calling the file and also use &lt;code&gt;\&lt;/code&gt; before the spaces to indicate that there are spaces on the file name like so:&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%2Faz4nt99w0dl73mr7wudw.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%2Faz4nt99w0dl73mr7wudw.png" alt=" " width="490" height="39"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These types of problems appear on the levels which really helped me master these concepts especially on the commands I have mentioned.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;find&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This command is for locating files based on size, permissions, type, and timestamps, which was crucial for levels that hid passwords deep in directory trees or filtered by file attributes. Level 5 -&amp;gt; Level 6 highlighted the power of this command when trying to find a certain file. That is because in this level, the file that needs to be found should be searched from many directories with many different files inside as well. With the given limits to where the password could be, the power of the find command was shown in solving that particular problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;grep&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This command searching file contents for patterns, especially useful when a password was buried among thousands of words and characters. &lt;code&gt;grep&lt;/code&gt; was used in some of the problems by piping the &lt;code&gt;cat&lt;/code&gt; command's output into grep like so:&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%2F0xgbpae5fdr7ty6rmpu9.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%2F0xgbpae5fdr7ty6rmpu9.png" alt=" " width="416" height="40"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pipe commands
&lt;/h3&gt;

&lt;p&gt;As said earlier, piping chains commands together (cmd1 | cmd2 | cmd3) to filter and transform output instead of relying on a single command to do everything. The grep example from earlier is a good illustration of this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0xgbpae5fdr7ty6rmpu9.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%2F0xgbpae5fdr7ty6rmpu9.png" alt=" " width="416" height="40"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, &lt;code&gt;cat data.txt&lt;/code&gt; reads and outputs the entire contents of the file, and instead of dumping all of that to the terminal, the pipe (|) takes that output and feeds it directly into grep as input. &lt;code&gt;grep "millionth"&lt;/code&gt; then filters through every line it receives and only prints the lines that contain the word "millionth", discarding everything else. This meant that instead of scrolling through potentially thousands of lines of junk data, the pipe let two simple commands work together to instantly narrow down the output to just the relevant line, which was often where the password for the next level was hiding.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;sort&lt;/code&gt; and &lt;code&gt;uniq&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;These commands together is used to isolate unique lines, which came up in levels where the password was the one line in a file that didn't repeat. One level's data.txt contained thousands of duplicated lines with only a single line appearing once, so the approach was to first sort the file (since uniq only detects duplicates that are adjacent to each other) and then pipe that sorted output into &lt;code&gt;uniq -u&lt;/code&gt;, which prints only the lines that have no duplicates:&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%2Fse9i2sh2z0l0i2at5kvb.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%2Fse9i2sh2z0l0i2at5kvb.png" alt=" " width="381" height="37"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;strings&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This command is used to extract readable text from binary files, useful when the password was embedded in something not meant to be read directly. One level's data.txt contained a mix of binary data and human-readable text, which meant &lt;code&gt;cat&lt;/code&gt; would just spit out a mess of unreadable characters. Running strings on the file filtered out the unreadable contents and printed only the human-readable strings, but the output was still cluttered with other text fragments unrelated to the password. To narrow it down further, the output was piped into &lt;code&gt;grep&lt;/code&gt; to search specifically for lines containing an equals sign, since the password in this file appeared preceded by several rows of =:&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%2Ftliec06vpq0sd2sw84jj.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%2Ftliec06vpq0sd2sw84jj.png" alt=" " width="421" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;base64&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This command was used to decode data, since some levels layered multiple encodings on top of each other. One level's data.txt contained a string that, when viewed with cat, was base64-encoded. Running it through &lt;code&gt;base64 -d&lt;/code&gt; to decode it revealed the readable line, giving the password needed to progress to the next level:&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%2Fdn7c7j4ruzi2xqupwnvc.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%2Fdn7c7j4ruzi2xqupwnvc.png" alt=" " width="619" height="78"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;chmod&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This command is used in manipulating file permissions. It was used both for solving levels and for understanding why certain access was blocked or allowed. &lt;code&gt;chmod&lt;/code&gt; came up in a few different contexts throughout the game:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;giving execute permissions to scripts or files that needed to be run&lt;/li&gt;
&lt;li&gt;restricting the permissions on private key files (since ssh refuses to use a key that's readable by others, requiring something like &lt;code&gt;chmod 600 key_file&lt;/code&gt; before it would work)&lt;/li&gt;
&lt;li&gt;adjusting permissions on output files to make sure they could actually be written to or read from when a level's solution involved redirecting output to a file.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;tr&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Used to translating or substituting characters. I used this in a level where the password was hidden using a ROT13-style letter rotation. Viewing data.txt directly with cat showed scrambled, unreadable text. Piping that output into tr with the mapping 'A-Za-z' 'N-ZA-Mn-za-m' shifted every letter by 13 places, decoding it back into plain English:&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%2Fusfwgaxfmk162im5i9m3.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%2Fusfwgaxfmk162im5i9m3.png" alt=" " width="551" height="70"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;diff&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Used in comparing two files to spot the difference between them, useful when a level asked to compare two large, mostly-identical files. This level provided two password files, passwords.old and passwords.new, each containing thousands of lines. Running diff between them isolated the exact line that had changed:&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%2Fau7w6foupqjj96geert4.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%2Fau7w6foupqjj96geert4.png" alt=" " width="691" height="127"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Shells
&lt;/h3&gt;

&lt;p&gt;Getting comfortable with how different shells behave, and at times needing to specify or switch shells to get a command to execute properly. Linux systems support multiple shell types (bash, sh, rbash, etc.), each with different behaviors, capabilities, and restrictions. One level specifically required logging into a different, more restricted shell than the default in order to interact with the level correctly.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;setuid&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Understanding how the setuid bit allows a program to run with the file owner's privileges rather than the executing user's, which was central to several privilege-escalation-style levels. Normally, when a program is executed, it runs with the permissions of the user who launched it. A binary with the &lt;code&gt;setuid&lt;/code&gt; bit set, however, runs with the permissions of the file's owner instead, regardless of who actually executes it. This was the mechanism that made several levels solvable: the current user didn't have direct read access to the next level's password file, but a setuid binary owned by the next-level user could be executed to read or output that password on the user's behalf, effectively borrowing the owner's privileges just long enough to retrieve the credential needed to progress.&lt;/p&gt;

&lt;h3&gt;
  
  
  CRON/scripting
&lt;/h3&gt;

&lt;p&gt;This concept is important in reading and understanding scheduled jobs and the scripts they run, including spotting vulnerabilities in how those scripts handled permissions or input. One of the most satisfying levels in the whole game involved a &lt;code&gt;cron&lt;/code&gt; job that was set to automatically run on a fixed schedule. The task here wasn't just to find a password sitting in a file, but to actually read and analyze the script that cron was executing in order to understand what it did, what permissions it ran with, and where it pulled its input from. Once it became clear that the script would execute anything placed in a certain location with the privileges of the cron job's owner, the solution was to write a custom script designed to take advantage of that behavior, drop it where the cron job would pick it up, and let the next scheduled run execute it automatically. Waiting for cron to fire and seeing the payload execute with elevated privileges, handing over the next password, was one of the more rewarding moments for me.&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%2F7zmoxbcz7n4vrwjfa2zq.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%2F7zmoxbcz7n4vrwjfa2zq.png" alt=" " width="799" height="144"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Networking and Communication
&lt;/h1&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;scp&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Securely copying files to and from the remote server, which was necessary for levels requiring local processing of a file. One level provided an RSA private key for authentication, which first needed its permissions locked down with &lt;code&gt;chmod 600 sshkey.private&lt;/code&gt; before it could be used. From there, &lt;code&gt;scp&lt;/code&gt; was used with the &lt;code&gt;-i&lt;/code&gt; flag to authenticate with that key and pull a password file directly off the remote server's filesystem down to the local machine:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7jtwaayfjekg0zipewlo.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%2F7jtwaayfjekg0zipewlo.png" alt=" " width="749" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the file landed locally, a simple &lt;code&gt;cat&lt;/code&gt; on the transferred file through &lt;code&gt;scp&lt;/code&gt; revealed the password.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;nmap&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Used for scanning for open ports and identifying services running on a target machine. One level only stated that the next password was being served somewhere on a port within a specific range, with no further details on which exact port. Running &lt;code&gt;nmap&lt;/code&gt; against that range on localhost scanned through the possibilities and returned the open ports:&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%2Fugmiyy0ourlmol30pdec.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%2Fugmiyy0ourlmol30pdec.png" alt=" " width="548" height="184"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This narrowed things down from a thousand possible ports to just five open ones, each running an unknown service. From there, it was a matter of connecting to each open port individually to inspect what was being served and figure out which one actually held the password.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;netcat&lt;/code&gt; or &lt;code&gt;nc&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Both of these commands is a way to connect to arbitrary ports/services and as a lightweight way to send and receive raw data. One level was one of the most interesting to me out of the whole game. It involved a setuid binary, suconnect, that would connect locally to a given port, read a password from whoever was listening there, and if that password matched the current level's password, it would send back the next level's password in response. The challenge was that this required something to actually be listening on a port to feed suconnect the correct password in the first place.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftp2zw0dc1lv67wpim49x.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%2Ftp2zw0dc1lv67wpim49x.png" alt=" " width="798" height="168"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To pull this off, I used tmux to split my terminal so I could run multiple things simultaneously: in one pane, &lt;code&gt;nc -lp 1112&lt;/code&gt; was set up to listen on a port and send out the current password using &lt;code&gt;echo -n "password" | nc -lp 1112 &amp;amp;&lt;/code&gt;, running it in the background so the terminal stayed free. In the other pane, &lt;code&gt;./suconnect 1112&lt;/code&gt; was run to connect to that same port, read the password being served, confirm the match, and print "Password matches, sending next password," followed by the actual password for the next level. Seeing both sides of that handshake play out across split panes made this one of the more hands-on, satisfying levels to work through, since it wasn't just about reading a file, it was about actively setting up the communication myself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Brute forcing
&lt;/h3&gt;

&lt;p&gt;Brute forcing is a method of solving a problem by systematically trying every possible combination or option until the correct one is found, rather than deducing or calculating the answer directly. This level required a known password plus a 4-digit pincode to authenticate over a port, but the pincode itself wasn't given, only that it was somewhere within the 0000-9999 range. Manually guessing wasn't feasible, so the approach was to script the brute force instead. A small bash script was written to loop through every possible 4-digit combination, appending the known password plus each candidate pincode to a file:&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%2Fgonjhce7h0em2accad9u.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%2Fgonjhce7h0em2accad9u.png" alt=" " width="652" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After making the script executable with chmod 700 scriptBRUTE.sh and running it, the resulting possibilities.txt contained every possible password-pincode combination. That file was then piped directly into nc to send each line to the listening service on port 30002. It eventually responded with "Correct!" once it hit the right pincode, revealing the password for the next level.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;openssl&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Used to communicate over encrypted SSL/TLS connections, since some levels served their next password over a port that expected SSL/TLS rather than plain text, making plain &lt;code&gt;nc&lt;/code&gt; insufficient. One level required sending the current password to a service listening on port 30001, but that port specifically expected an SSL connection. The &lt;code&gt;openssl s_client&lt;/code&gt; command handled establishing that encrypted connection, connecting to the target with &lt;code&gt;-connect localhost:30001&lt;/code&gt;, using &lt;code&gt;-ign_eof&lt;/code&gt; to keep the connection open after sending input instead of closing immediately, and redirecting the password file as input:&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%2Fyf9n70sd0xji06pwk75n.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%2Fyf9n70sd0xji06pwk75n.png" alt=" " width="800" height="39"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Git
&lt;/h1&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;git clone&lt;/code&gt; (SSH)
&lt;/h3&gt;

&lt;p&gt;Cloning repositories over SSH rather than HTTPS, tying back into the SSH concepts learned earlier. Several of the later levels each provided a Git repository accessible over SSH on a specific port, and the password for that level was hidden somewhere within the repository's history rather than sitting in a plain file. Cloning it locally was the first step before any of the repository's contents or history could actually be inspected.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;git diff&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Inspecting changes between commits or branches to find where a password may have been added and later removed. In one level, the current state of the repository didn't contain the password at all, but comparing two commits with &lt;code&gt;git diff &amp;lt;commit1&amp;gt; &amp;lt;commit2&amp;gt;&lt;/code&gt; revealed that a line containing the password had been added in an earlier commit and then deleted in a later one, exposing it despite no longer being present in the latest version of the files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Git branching
&lt;/h3&gt;

&lt;p&gt;Understanding that information can exist on branches other than the one currently checked out. Not every branch in a repository is necessarily the default one checked out after cloning, and one level required listing all branches with &lt;code&gt;git branch -a&lt;/code&gt; and switching into a non-default branch to find a password that simply wasn't present anywhere on the main branch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Git tagging
&lt;/h3&gt;

&lt;p&gt;Recognizing that tags can point to commits containing information not visible in the default branch history. Tags can reference commits that aren't part of any branch's regular history at all, so a level here meant running &lt;code&gt;git tag&lt;/code&gt; to list available tags and then checking out or inspecting the commit a particular tag pointed to, where the password had been stored.&lt;/p&gt;

&lt;h3&gt;
  
  
  Git history
&lt;/h3&gt;

&lt;p&gt;Digging through past commits, since deleted or modified content often still exists somewhere in the log. Using &lt;code&gt;git log&lt;/code&gt; to walk back through the full commit history made it possible to find commits that had been effectively "hidden" by later changes, reinforcing the idea that removing content from the latest version of a file doesn't erase it from the repository's history.&lt;/p&gt;

&lt;h3&gt;
  
  
  Git Pushing (with &lt;code&gt;.gitignore&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Understanding how &lt;code&gt;.gitignore&lt;/code&gt; controls what does and doesn't get tracked, and how that can be exploited or worked around when searching for hidden information. One level involved a &lt;code&gt;.gitignore&lt;/code&gt; file configured to exclude the very file that contained the password, meaning a plain &lt;code&gt;git add .&lt;/code&gt; and commit wouldn't track it. Getting around this meant removing the ignored file from the &lt;code&gt;.gitignore&lt;/code&gt; so it could be committed and pushed, revealing that &lt;code&gt;.gitignore&lt;/code&gt; only prevents automatic tracking and doesn't actually make a file's content inaccessible if you choose to override it.&lt;/p&gt;




&lt;h1&gt;
  
  
  Closing Thoughts
&lt;/h1&gt;

&lt;p&gt;Working through Bandit gave me a much more hands-on understanding of UNIX fundamentals and networking basics than reading about them ever could. More importantly, it built the muscle memory and problem-solving instincts needed to approach the other OverTheWire Wargames and also system tinkering in general with more confidence.&lt;/p&gt;




&lt;blockquote&gt;
&lt;h3&gt;
  
  
  Visit OverTheWire Wargames : Bandit to try it out for yourself!
&lt;/h3&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://overthewire.org/wargames/bandit/" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;overthewire.org&lt;/span&gt;
          

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

&lt;/blockquote&gt;

</description>
      <category>linux</category>
      <category>cybersecurity</category>
      <category>networking</category>
      <category>writeup</category>
    </item>
    <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%2F88fugllf6neq2xiq0d8h.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%2F88fugllf6neq2xiq0d8h.png" alt=" " width="800" height="517"&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%2Fzxjg3rik1mgjww0g988l.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%2Fzxjg3rik1mgjww0g988l.png" alt=" " width="800" height="480"&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%2Fn8ogp4wiqmci6clzjzo2.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%2Fn8ogp4wiqmci6clzjzo2.png" alt=" " width="800" height="480"&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%2Feykf2x0qoeq6qqf8da4o.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%2Feykf2x0qoeq6qqf8da4o.png" alt=" " width="800" height="487"&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%2Fvqm3aikasyv2s9g9dd3r.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%2Fvqm3aikasyv2s9g9dd3r.png" alt=" " width="800" height="480"&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%2F3vil6sxbo1u4y93cn1yc.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%2F3vil6sxbo1u4y93cn1yc.png" alt=" " width="800" height="482"&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%2Fit3al244l5czda686dl1.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%2Fit3al244l5czda686dl1.png" alt=" " width="799" height="245"&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%2Fzfdg033jb4pbbtg0w1bd.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%2Fzfdg033jb4pbbtg0w1bd.png" alt=" " width="612" height="130"&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%2Fxzi40b67nupj1zwz5y0j.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%2Fxzi40b67nupj1zwz5y0j.png" alt=" " width="800" height="252"&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;&amp;lt;ARN&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;recently&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;copied&amp;gt;"&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, then change the ARN again to the task definition named &lt;code&gt;codedeploy-workshop-task&lt;/code&gt;
&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;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>
