<?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: Samir Khanal</title>
    <description>The latest articles on DEV Community by Samir Khanal (@shawmeer22).</description>
    <link>https://dev.to/shawmeer22</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2935490%2Fb0941ff8-67d2-4fc3-ab02-910238d726e1.jpg</url>
      <title>DEV Community: Samir Khanal</title>
      <link>https://dev.to/shawmeer22</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shawmeer22"/>
    <language>en</language>
    <item>
      <title>Building a Serverless URL Shortener with AWS</title>
      <dc:creator>Samir Khanal</dc:creator>
      <pubDate>Thu, 16 Apr 2026 05:33:35 +0000</pubDate>
      <link>https://dev.to/shawmeer22/building-a-serverless-url-shortener-with-aws-2llf</link>
      <guid>https://dev.to/shawmeer22/building-a-serverless-url-shortener-with-aws-2llf</guid>
      <description>&lt;p&gt;Hey there, fellow developers and cloud enthusiasts! If you're like me, you've probably wondered how those sleek, short links on social media work behind the scenes. Today, I'm excited to dive into a project I built: a serverless URL shortener that combines DevOps automation, real-time analytics, and scalable architecture. It's not just a fun tool; it's a blueprint for modern, event-driven applications on AWS. Let's break it down step by step, from concept to deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;In the fast-paced world of web development, sharing links efficiently is crucial. Long URLs can be cumbersome, especially on mobile devices or character-limited platforms. Enter our serverless URL shortener: a robust system that takes any URL and generates a short, memorable code (e.g., &lt;code&gt;https://mydomain.com/ABC123&lt;/code&gt;) that redirects users seamlessly.&lt;/p&gt;

&lt;p&gt;Built entirely on AWS, this project showcases serverless best practices. It handles URL shortening, click tracking, analytics processing via queues, and even integrates with CI/CD pipelines for automated deployments. I designed this to demonstrate how serverless can simplify complex workflows while keeping costs low and scalability high. Think of it as a digital post office: you drop off a package (long URL), get a tracking number (short code), and the system handles delivery (redirects) with full visibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  Goals
&lt;/h2&gt;

&lt;p&gt;The primary goals were straightforward yet ambitious:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simplicity and Scalability&lt;/strong&gt;: Create a URL shortener that scales automatically without server management.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analytics Insights&lt;/strong&gt;: Track clicks and events in real-time for user behavior analysis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DevOps Integration&lt;/strong&gt;: Automate deployments via CI/CD, with notifications for workflow triggers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Community Value&lt;/strong&gt;: Provide an open-source example for AWS builders, highlighting services like Lambda, Step Functions, and SQS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end, I wanted a production-ready app that could handle thousands of requests with minimal overhead—perfect for startups, marketers, or anyone needing link management.&lt;/p&gt;

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

&lt;p&gt;At its core, this is a fully serverless architecture leveraging AWS's event-driven model. Here's a high-level view:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GitLab / API Trigger
        ↓
   API Gateway
        ↓
Lambda (url-shortener)
     ↓        ↓        ↓
DynamoDB   SQS Queue   Step Functions (url_workflow)
     ↓        ↓
Analytics Lambda   Slack Notifications
&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%2Fvxlbgtf6wfb48l8cudp0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvxlbgtf6wfb48l8cudp0.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API Gateway&lt;/strong&gt;: Acts as the entry point for shortening and redirecting URLs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lambda Functions&lt;/strong&gt;: Handle logic—url-shortener for CRUD operations, analytics for processing queued events.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DynamoDB&lt;/strong&gt;: NoSQL database storing URLs, short codes, and click data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQS&lt;/strong&gt;: Decouples analytics processing, ensuring reliability during traffic spikes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step Functions&lt;/strong&gt;: Orchestrates workflows for notifications (e.g., Slack alerts on URL creation).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudWatch&lt;/strong&gt;: Monitors logs and metrics; integrates with GitLab for CI/CD.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Development Process
&lt;/h2&gt;

&lt;p&gt;Development followed an iterative, Terraform-driven approach. I started with Terraform for infrastructure as code, defining resources in &lt;code&gt;main.tf&lt;/code&gt;. Here's a snippet of the API Gateway setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_api_gateway_rest_api"&lt;/span&gt; &lt;span class="s2"&gt;"url_shortener_api"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-url-api"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"URL Shortener API"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_api_gateway_method"&lt;/span&gt; &lt;span class="s2"&gt;"shorten_method"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;rest_api_id&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_api_gateway_rest_api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url_shortener_api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;resource_id&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_api_gateway_resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shorten_resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;http_method&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"POST"&lt;/span&gt;
  &lt;span class="nx"&gt;authorization&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"NONE"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, I wrote Lambda functions in Node.js. The url-shortener Lambda handles shortening and redirects, using DynamoDB for storage. Check out this key function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;shortenUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shortCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateShortCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;dynamo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;TableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TABLE_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;shortCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;originalUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;clicks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Trigger Step Functions for notifications&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;stepfunctions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startExecution&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;stateMachineArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;STATE_MACHINE_ARN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;url_created&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;shortCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;originalUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;promise&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="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;shortCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;shortUrl&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;Testing involved local simulations with AWS SAM or Docker, followed by deployments via GitLab pipelines. Each push triggered the DevOps workflow, sending Slack notifications and queuing messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;URL Shortening &amp;amp; Redirects&lt;/strong&gt;: Generate codes and handle redirects with click tracking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-Time Analytics&lt;/strong&gt;: Queue events to SQS; process via Lambda for insights.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slack Integration&lt;/strong&gt;: Instant notifications via Step Functions (e.g., "New URL created!").&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitLab CI/CD&lt;/strong&gt;: Automated builds and deployments with webhook triggers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring&lt;/strong&gt;: CloudWatch dashboards for metrics—[Screenshot 2: CloudWatch graph showing Lambda invocations].&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These features make it user-friendly: Developers can integrate via APIs, while stakeholders get alerts and reports.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;p&gt;Serverless isn't without hurdles. Cold starts in Lambda caused initial latency—mitigated with provisioned concurrency. SQS message visibility timeouts need tuning to avoid duplicates during high loads. Debugging Step Functions required tracing executions in the console. Security was key: I used IAM roles and environment variables to protect sensitive data like Slack webhooks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;This project reinforced serverless principles: Embrace events over servers. Use queues for decoupling to build resilient systems. Community tools like Terraform and GitLab sped up development. Sharing code snippets and docs helped others—open-source wins!&lt;/p&gt;

&lt;h2&gt;
  
  
  Future Plans
&lt;/h2&gt;

&lt;p&gt;Next, I'll add A/B testing for short codes, integrate with Kinesis for advanced analytics, and explore multi-region deployments. If the community engages, I might add a UI dashboard. Contributions welcome—check the GitHub repo!&lt;/p&gt;

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

&lt;p&gt;Building this URL shortener was a journey in serverless excellence, proving AWS can power scalable apps with ease. Whether you're a developer experimenting with Lambda or a stakeholder seeking efficient tools, this project shows the power of event-driven architecture. What serverless project are you tackling? Drop your thoughts in the comments—let's build together!&lt;/p&gt;

&lt;p&gt;Github link: &lt;a href="https://github.com/Shawmeer/aws-serverless-url-shortener" rel="noopener noreferrer"&gt;https://github.com/Shawmeer/aws-serverless-url-shortener&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Check more blogs at: &lt;a href="https://khanalsamir.com" rel="noopener noreferrer"&gt;https://khanalsamir.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>lambda</category>
      <category>community</category>
    </item>
    <item>
      <title>Building a PostgreSQL Observability Stack with Docker, Grafana, and Prometheus</title>
      <dc:creator>Samir Khanal</dc:creator>
      <pubDate>Mon, 30 Mar 2026 09:18:37 +0000</pubDate>
      <link>https://dev.to/shawmeer22/building-a-postgresql-observability-stack-with-docker-grafana-and-prometheus-1jlo</link>
      <guid>https://dev.to/shawmeer22/building-a-postgresql-observability-stack-with-docker-grafana-and-prometheus-1jlo</guid>
      <description>&lt;p&gt;&lt;em&gt;How to monitor your PostgreSQL database with metrics collection, log aggregation, and beautiful dashboards&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;If you're running PostgreSQL in production, you need to know what's happening under the hood. Slow queries, lock contention, vacuum issues – these can quietly kill your application's performance. But setting up proper observability doesn't have to be complicated.&lt;br&gt;
In this post, I'll show you how to build a complete PostgreSQL observability stack using Docker Compose, Grafana Alloy, Prometheus, Loki, and Grafana. All running locally, all open-source.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;p&gt;Here's what we're building:&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%2Fejuls7m5uukyba93mum1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fejuls7m5uukyba93mum1.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL&lt;/strong&gt; - Our database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grafana Alloy&lt;/strong&gt; - Collects metrics from PostgreSQL and ships logs to Loki&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prometheus&lt;/strong&gt; - Stores time-series metrics&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loki&lt;/strong&gt; - Log aggregation (like Prometheus, but for logs)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grafana&lt;/strong&gt; - Dashboards for everything&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;You need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;li&gt;Docker Compose
That's it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The Code
&lt;/h2&gt;

&lt;p&gt;Let's look at the key files. First, our Docker Compose setup:&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:18.1&lt;/span&gt;
&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="s"&gt;- ./postgres.conf:/etc/postgresql/postgresql.conf&lt;/span&gt;
&lt;span class="s"&gt;- ./init-exporter.sh:/docker-entrypoint-initdb.d/init-exporter.sh&lt;/span&gt;
&lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER=${POSTGRES_USER}&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=${POSTGRES_PASSWORD}&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB=${POSTGRES_DB}&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_EXPORTER_PASSWORD=${POSTGRES_EXPORTER_PASSWORD}&lt;/span&gt;
&lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgres"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-c"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;config_file=/etc/postgresql/postgresql.conf"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;grafana-alloy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grafana/alloy:v1.11.3&lt;/span&gt;
&lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;12345:12345"&lt;/span&gt;
&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./config.alloy:/etc/alloy/config.alloy:ro"&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./secrets/postgres_db_url.txt:/var/secrets/postgres_db_url.txt:ro"&lt;/span&gt;
&lt;span class="na"&gt;prometheus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prom/prometheus:v3.9.1&lt;/span&gt;
&lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9090:9090"&lt;/span&gt;
&lt;span class="na"&gt;grafana&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grafana/grafana:11.6&lt;/span&gt;
&lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The init script enables &lt;code&gt;pg_stat_statements&lt;/code&gt; and creates the exporter user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;psql &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;EOSQL&lt;/span&gt;&lt;span class="sh"&gt;
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
CREATE ROLE postgres_exporter WITH LOGIN PASSWORD '&lt;/span&gt;&lt;span class="nv"&gt;$POSTGRES_EXPORTER_PASSWORD&lt;/span&gt;&lt;span class="sh"&gt;';
GRANT pg_monitor TO postgres_exporter;
GRANT SELECT ON pg_stat_statements TO postgres_exporter;
&lt;/span&gt;&lt;span class="no"&gt;EOSQL
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Alloy configuration collects PostgreSQL metrics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;prometheus.exporter.postgres "postgres_db" {
data_source_names = [local.file.postgres_db_url.content]
enabled_collectors = [
"database",
"locks",
"stat_bgwriter",
"stat_statements", // Query performance metrics
"stat_activity_autovacuum",
"long_running_transactions",
]
}
prometheus.scrape "postgres_db" {
job_name = "postgres_db_metrics"
scrape_interval = "15s"
forward_to = [prometheus.remote_write.send_to_prometheus.receiver]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Clone the repository&lt;/li&gt;
&lt;li&gt;Copy &lt;code&gt;.env.example&lt;/code&gt; to &lt;code&gt;.env&lt;/code&gt; and set your passwords&lt;/li&gt;
&lt;li&gt;Run:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Access Grafana at &lt;code&gt;http://localhost:3000&lt;/code&gt; (admin/admin)
## What Metrics Do You Get?
Once running, you can query things like:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Top 10 most called queries
topk(10, rate(pg_stat_statements_calls_total[5m]))
# Slowest queries
topk(10, rate(pg_stat_statements_total_time_seconds_total[5m]))
# Database connections over time
pg_stat_database_numbackends
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You also get lock contention metrics, vacuum progress, buffer statistics - everything you need to debug performance issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Dashboard
&lt;/h2&gt;

&lt;p&gt;In Grafana, create a new dashboard and add panels using your PostgreSQL metrics. You'll see query performance, database statistics, and can correlate with logs from Loki.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Setup?
&lt;/h2&gt;

&lt;p&gt;A few reasons this works well:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Alloy instead of postgres_exporter&lt;/strong&gt; - Grafana Alloy is newer and more modern. It handles both metrics and log collection in one agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pg_stat_statements&lt;/strong&gt; - This extension is gold for understanding query performance. Enable it, and you can see exactly which queries are slowest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loki for logs&lt;/strong&gt; - Instead of grepping through files, you get structured logs in Grafana that you can filter and query.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything in Docker&lt;/strong&gt; - No system packages to install. Spin it up, tear it down, reproduce issues.
## What's Next?
This is a practice project, so here's where you could extend it:&lt;/li&gt;
&lt;li&gt;Add alerts for slow queries or long-running transactions&lt;/li&gt;
&lt;li&gt;Set up retention policies in Prometheus&lt;/li&gt;
&lt;li&gt;Add more PostgreSQL exporters for connection pooling metrics&lt;/li&gt;
&lt;li&gt;Deploy to Kubernetes with proper secrets management
## Conclusion
Observability doesn't have to be complex. With Docker and open-source tools, you can have a production-grade monitoring setup running locally in minutes.
The code is on GitHub - clone it, play with it, break it. That's the best way to learn.
 - -&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;GitHub link: &lt;a href="https://github.com/Shawmeer/postgres-observability-stack" rel="noopener noreferrer"&gt;https://github.com/Shawmeer/postgres-observability-stack&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Check more blogs at: &lt;a href="https://khanalsamir.com" rel="noopener noreferrer"&gt;https://khanalsamir.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have questions or ran into issues? Drop a comment below.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>postgres</category>
      <category>grafana</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Deploying a Production-Ready React App on AWS using Terraform Module, S3, CloudFront &amp; GitHub Actions</title>
      <dc:creator>Samir Khanal</dc:creator>
      <pubDate>Sun, 22 Mar 2026 17:24:10 +0000</pubDate>
      <link>https://dev.to/shawmeer22/deploying-a-production-ready-react-app-on-aws-using-terraform-module-s3-cloudfront-github-ikd</link>
      <guid>https://dev.to/shawmeer22/deploying-a-production-ready-react-app-on-aws-using-terraform-module-s3-cloudfront-github-ikd</guid>
      <description>&lt;p&gt;Modern frontend apps need fast, scalable, and cost-efficient hosting. &lt;br&gt;
While AWS S3 is great for static hosting, making it production-ready &lt;br&gt;
requires CloudFront, proper security, and automation.&lt;/p&gt;

&lt;p&gt;In this blog, I’ll show how I built a fully automated pipeline. &lt;br&gt;
to deploy a React app using Terraform and GitHub Actions.&lt;/p&gt;
&lt;h2&gt;
  
  
  📌 Architecture Overview
&lt;/h2&gt;

&lt;p&gt;Here’s what we’re building:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;React app → built into static files&lt;/li&gt;
&lt;li&gt;Terraform provisions:

&lt;ul&gt;
&lt;li&gt;S3 bucket (hosting)&lt;/li&gt;
&lt;li&gt;CloudFront distribution (CDN)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;GitHub Actions:

&lt;ul&gt;
&lt;li&gt;Build React app&lt;/li&gt;
&lt;li&gt;Upload to S3&lt;/li&gt;
&lt;li&gt;Invalidate CloudFront cache&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsyakprqqr7dxz8kd1mmb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsyakprqqr7dxz8kd1mmb.png" alt=" " width="313" height="595"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  ⚙️ Step 1: Terraform Module Design
&lt;/h2&gt;

&lt;p&gt;Instead of writing everything in one file, I used modular Terraform.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Example modules:&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;🔗 How Modules Are Used (Root Configuration)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This snippet shows how the root module calls the S3 and CloudFront &lt;br&gt;
modules to provision infrastructure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"s3_static_site"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./modules/s3-static-site"&lt;/span&gt;
  &lt;span class="nx"&gt;bucket_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bucket_name&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"cloudfront"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./modules/cloudfront-distribution"&lt;/span&gt;
  &lt;span class="nx"&gt;bucket_domain_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3_static_site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bucket_domain_name&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  📦 S3 Module (Static Hosting)
&lt;/h2&gt;

&lt;p&gt;This module creates and configures the S3 bucket for static hosting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bucket Creation&lt;/strong&gt;&lt;br&gt;
This snippet creates the S3 bucket that will host the React static site.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"skr_bucket"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bucket_name&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;Website Configuration&lt;/strong&gt;&lt;br&gt;
This snippet configures the S3 bucket for static website hosting and sets the index document.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket_website_configuration"&lt;/span&gt; &lt;span class="s2"&gt;"skr_bucket"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;skr_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;index_document&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;suffix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"index.html"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Bucket Policy (for CloudFront)&lt;/strong&gt;&lt;br&gt;
This snippet sets a bucket policy allowing CloudFront (and other services if needed) to read objects from the S3 bucket.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket_policy"&lt;/span&gt; &lt;span class="s2"&gt;"allow_cloudfront"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;skr_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;Statement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="nx"&gt;Effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
      &lt;span class="nx"&gt;Principal&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"*"&lt;/span&gt;
      &lt;span class="nx"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;
      &lt;span class="nx"&gt;Resource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;skr_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/*"&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🌍 CloudFront Module
&lt;/h2&gt;

&lt;p&gt;This module sets up a CDN in front of S3.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Distribution&lt;/strong&gt;&lt;br&gt;
This snippet creates the CloudFront distribution that serves your S3 bucket globally with HTTPS support.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_cloudfront_distribution"&lt;/span&gt; &lt;span class="s2"&gt;"cdn"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;enabled&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;default_root_object&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"index.html"&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;React Routing Fix (IMPORTANT)&lt;/strong&gt;&lt;br&gt;
This snippet ensures single-page React routes return index.html instead of 404 errors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;custom_error_response&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;error_code&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;
  &lt;span class="nx"&gt;response_code&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
  &lt;span class="nx"&gt;response_page_path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/index.html"&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;Cache Invalidation&lt;/strong&gt;&lt;br&gt;
This snippet invalidates the CloudFront cache automatically after deployment, so new changes appear immediately.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"null_resource"&lt;/span&gt; &lt;span class="s2"&gt;"cache"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;provisioner&lt;/span&gt; &lt;span class="s2"&gt;"local-exec"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aws cloudfront create-invalidation --distribution-id &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;aws_cloudfront_distribution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cdn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; --paths '/*'"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This Terraform module-based architecture makes it easy to reuse infrastructure across multiple environments like dev, staging, and production.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Reusable&lt;/li&gt;
&lt;li&gt;Clean architecture&lt;/li&gt;
&lt;li&gt;Easier debugging&lt;/li&gt;
&lt;li&gt;Production-ready structure&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ☁️ Step 2: S3 Static Website Hosting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The S3 module:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creates a bucket&lt;/li&gt;
&lt;li&gt;Enables static hosting&lt;/li&gt;
&lt;li&gt;Configures public access (Via OAC)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Key features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Versioning enabled&lt;/li&gt;
&lt;li&gt;Proper bucket policy&lt;/li&gt;
&lt;li&gt;Index + error documents&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🌍 Step 3: CloudFront CDN Setup
&lt;/h2&gt;

&lt;p&gt;CloudFront sits in front of S3 to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Improve performance (low latency)&lt;/li&gt;
&lt;li&gt;Add HTTPS support&lt;/li&gt;
&lt;li&gt;Cache content globally&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Configuration highlights:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Origin: S3 bucket&lt;/li&gt;
&lt;li&gt;Viewer protocol: Redirect HTTP → HTTPS&lt;/li&gt;
&lt;li&gt;Cache behavior optimized for static assets&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ⚡ Step 4: GitHub Actions CI/CD
&lt;/h2&gt;

&lt;p&gt;This is where things get powerful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workflow does:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install dependencies&lt;/li&gt;
&lt;li&gt;Build React app&lt;/li&gt;
&lt;li&gt;Sync build folder to S3&lt;/li&gt;
&lt;li&gt;Invalidate CloudFront cache&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Every push to main → automatic deployment 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  🔄 CI/CD Workflow Example
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to CloudFront&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="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;AWS_REGION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ap-south-1&lt;/span&gt;
  &lt;span class="na"&gt;S3_BUCKET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;samir-module-s3-bucket-hosting&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@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;Setup Node.js&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;20'&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;npm'&lt;/span&gt;
          &lt;span class="na"&gt;cache-dependency-path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;static-site-react/package-lock.json&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./static-site-react&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build React app&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./static-site-react&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run build&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure AWS credentials&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;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;${{ env.AWS_REGION }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload to S3&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;aws s3 sync ./static-site-react/dist/ s3://${{ env.S3_BUCKET }} --delete&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get CloudFront distribution ID&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloudfront&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;DISTRIBUTION_ID=$(aws cloudfront list-distributions --query "DistributionList.Items[?Origins.Items[0].DomainName=='${{ env.S3_BUCKET }}.s3.${{ env.AWS_REGION }}.amazonaws.com'].Id" --output text)&lt;/span&gt;
          &lt;span class="s"&gt;echo "distribution_id=$DISTRIBUTION_ID" &amp;gt;&amp;gt; $GITHUB_OUTPUT&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Invalidate CloudFront cache&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;aws cloudfront create-invalidation --distribution-id ${{ steps.cloudfront.outputs.distribution_id }} --paths "/*"&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 success message&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;echo "✅ Deployment completed successfully!"&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why Use Terraform Modules?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Without modules:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Messy code&lt;/li&gt;
&lt;li&gt;Hard to reuse&lt;/li&gt;
&lt;li&gt;Difficult scaling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;With modules:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clean separation&lt;/li&gt;
&lt;li&gt;Reusable across projects&lt;/li&gt;
&lt;li&gt;Easier team collaboration&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  📊 Key Advantages of This Setup
&lt;/h2&gt;

&lt;p&gt;✅ Fully automated deployments&lt;br&gt;
✅ Global CDN performance&lt;br&gt;
✅ Infrastructure as Code (IaC)&lt;br&gt;
✅ Scalable &amp;amp; production-ready&lt;br&gt;
✅ Low cost (S3 + CloudFront)&lt;/p&gt;
&lt;h2&gt;
  
  
  How to Run This Project
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Initialize Terraform&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform init
&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%2Fkndv10jzdnierit9vbyl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkndv10jzdnierit9vbyl.png" alt=" " width="800" height="241"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Apply Infrastructure&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform apply &lt;span class="nt"&gt;-auto-approve&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%2Fd4n4x37lmy9ki9x87ypn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd4n4x37lmy9ki9x87ypn.png" alt=" " width="800" height="233"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deploy React App&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Push code to GitHub → CI/CD handles the rest&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%2Fp7lta3vhszxqeiuat26g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp7lta3vhszxqeiuat26g.png" alt=" " width="800" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🔐 Best Practices
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use IAM roles instead of access keys&lt;/li&gt;
&lt;li&gt;Store secrets in GitHub Secrets&lt;/li&gt;
&lt;li&gt;Enable CloudFront caching strategies&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  💡 Improvements You Can Add
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Custom domain with Route 53&lt;/li&gt;
&lt;li&gt;HTTPS with ACM&lt;/li&gt;
&lt;li&gt;WAF for security&lt;/li&gt;
&lt;li&gt;Multi-environment Terraform setup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🏁 Conclusion&lt;/p&gt;

&lt;p&gt;This project demonstrates how to build a modern frontend deployment pipeline using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terraform Modules&lt;/li&gt;
&lt;li&gt;AWS S3 + CloudFront&lt;/li&gt;
&lt;li&gt;GitHub Actions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔗 Final Output
&lt;/h2&gt;

&lt;p&gt;After deployment, your app will be available via:&lt;/p&gt;

&lt;p&gt;👉 CloudFront URL (fast, secure, global)&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%2Fqkfm2xp88vncgd4qr96u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqkfm2xp88vncgd4qr96u.png" alt=" " width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  📂 Explore the Code
&lt;/h2&gt;

&lt;p&gt;The full project with Terraform modules, S3, CloudFront setup, and GitHub Actions workflow is available here:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/Shawmeer/s3-website-hosting-using-terraform-modules" rel="noopener noreferrer"&gt;Check it out on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🚀 With this setup, you now have a fully automated, scalable, and production-ready React deployment pipeline. Start building modern frontends with confidence!Happy Building!!&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect with me
&lt;/h2&gt;

&lt;p&gt;• Portfolio: &lt;a href="https://khanalsamir.com" rel="noopener noreferrer"&gt;https://khanalsamir.com&lt;/a&gt;&lt;br&gt;&lt;br&gt;
• GitHub: &lt;a href="https://github.com/Shawmeer" rel="noopener noreferrer"&gt;https://github.com/Shawmeer&lt;/a&gt;&lt;br&gt;&lt;br&gt;
• LinkedIn: &lt;a href="https://linkedin.com/in/samir-khanal" rel="noopener noreferrer"&gt;https://linkedin.com/in/samir-khanal&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;If you're working on similar DevOps or AWS projects, feel free to connect. I regularly share practical cloud and CI/CD content.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>aws</category>
      <category>github</category>
      <category>cicd</category>
    </item>
    <item>
      <title>AWS App Runner: Deploy Containerized Apps Without the Infrastructure Headache</title>
      <dc:creator>Samir Khanal</dc:creator>
      <pubDate>Tue, 30 Dec 2025 15:24:51 +0000</pubDate>
      <link>https://dev.to/shawmeer22/aws-app-runner-deploy-containerized-apps-without-the-infrastructure-headache-2oo8</link>
      <guid>https://dev.to/shawmeer22/aws-app-runner-deploy-containerized-apps-without-the-infrastructure-headache-2oo8</guid>
      <description>&lt;p&gt;Deploying a web application into the &lt;a href="https://aws.amazon.com/" rel="noopener noreferrer"&gt;AWS&lt;/a&gt; platform should not require mastering in cloud architecture. However, it is hard considering how many different aspects of AWS's VPCs, Load Balancers, Security Groups, and the like that need to be dealt with to create just one simple containerized application, none of which impact the actual code behind the application. &lt;/p&gt;

&lt;p&gt;AWS App Runner has addressed this by offering developers a simple way to deploy their containerized Web Applications without dealing with any underlying infrastructure services for the deployment of the Web Application.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is &lt;a href="https://aws.amazon.com/apprunner/" rel="noopener noreferrer"&gt;AWS App Runner&lt;/a&gt;?
&lt;/h2&gt;

&lt;p&gt;App Runner by AWS offers a complete management solution for hosting and deploying web applications in containers hosted by AWS (Amazon Web Services). Point App Runner toward an image stored in Amazon ECR or source code on GitHub; App Runner will automatically deploy your application, scale your application, balance the load across multiple copies of your application, and serve your web application over HTTPS.&lt;br&gt;
You won’t have to manage any servers, configure any clusters, or set up any infrastructure. Just your application running!&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Fully Managed Infrastructure
&lt;/h3&gt;

&lt;p&gt;App Runner simplifies infrastructure management as all aspects are abstracted. Configuring VPCs, choosing instance types, and managing servers are not necessary for you to do because Amazon will take care of everything for you behind the scenes.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Automatic Scaling
&lt;/h3&gt;

&lt;p&gt;When an application experiences an increase in traffic, App Runner automatically adds instances (i.e., scales up) to meet traffic demands. Conversely, when an application experiences a decrease in traffic, App Runner will scale down by releasing nodes back into available resources (including removing all nodes from use when configured to do so).&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Built-in HTTPS and Load Balancing
&lt;/h3&gt;

&lt;p&gt;All App Runner services also have HTTPS endpoints automatically created for them, and for you. You do not need to worry about managing or renewing TLS certificates; all of this is handled automatically by Amazon for all App Runner services. Additionally, AWS will automatically load balance your services without any additional effort and expense on your part.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Flexible Deployment Options
&lt;/h3&gt;

&lt;p&gt;In addition to deploying directly from Amazon Elastic Container Registry (ECR) or other container registries, you can deploy an Application directly from a GitHub repository connection to App Runner. When deploying an application with a source code method (from GitHub) App Runner will automatically create the container image to support the following programming languages (Python, Node.js and Java).&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Integrated Observability
&lt;/h3&gt;

&lt;p&gt;The built-in integrations with Amazon CloudWatch will automatically send your logs and metrics from the App Runner to your Amazon CloudWatch account. This gives you an easy view into your application deployments, the status of health checks and application performance without any additional work required by you.&lt;/p&gt;

&lt;h2&gt;
  
  
  How AWS App Runner Enhances Application Deployment
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Eliminates Infrastructure Complexity
&lt;/h3&gt;

&lt;p&gt;Developers can upload applications to production without having to learn all of the complicated AWS networks or computing setups. As a result, development teams across the organization have more options when it comes to deploying their work.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Reduces Time to Production
&lt;/h3&gt;

&lt;p&gt;The simplified deployment process allows developers to go from writing code to being live in minutes rather than hours/days. In turn, development teams can make more iterations in a shorter amount of time, enabling them to quickly adapt to the changing needs of the organization.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Optimizes Costs
&lt;/h3&gt;

&lt;p&gt;Developers only pay for the compute and memory that are utilized by their applications. Developers don't need to pay for development environments when they are scaling down to zero. Also, there is no additional cost associated with load balancers, etc., as these are all built into the pricing structure.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Simplifies Maintenance
&lt;/h3&gt;

&lt;p&gt;AWS is responsible for performing platform-related tasks like platform upgrades, security patches, and infrastructure. As a result, you can focus on developing application code, while AWS manages the rest (like operational overhead).&lt;/p&gt;

&lt;h2&gt;
  
  
  Where App Runner Fits in the AWS
&lt;/h2&gt;

&lt;h3&gt;
  
  
  In Comparison with Amazon ECS/EKS
&lt;/h3&gt;

&lt;p&gt;Amazon ECS and EKS provide significant control over a cluster's networking, task orchestration, and cluster management. On the other hand, Amazon App Runner sacrifices this control in favour of ease-of-use (there's no need to manage the underlying infrastructure).&lt;/p&gt;

&lt;h3&gt;
  
  
  In Comparison with Elastic Beanstalk
&lt;/h3&gt;

&lt;p&gt;Both Amazon App Runner and Elastic Beanstalk are Platform-as-a-Service (PaaS) solutions, however, App Runner is newer and focuses on containers rather than on a broader application platform. App Runner also has more rigid guidelines, reducing your number of decisions and speeding up your time-to-deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  In Comparison with AWS Lambda
&lt;/h3&gt;

&lt;p&gt;While AWS Lambda is designed to execute event-driven functions that have short execution times, Amazon App Runner is designed for long-executing, continuously running web applications and APIs that require persistent container images and the ability to communicate with users via HTTP on an ongoing basis.&lt;/p&gt;

&lt;p&gt;Therefore, use Amazon App Runner if you are building a web application or API and would like to make the simplest deployment possible without having to worry about managing the underlying infrastructure. Otherwise, you should consider other AWS services if you require sophisticated networking configurations or custom compute requirements or workloads that extend beyond HTTP/HTTPS. &lt;/p&gt;

&lt;h2&gt;
  
  
  Common Use Cases
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Web Apps:&lt;/strong&gt; Build and launch web-based software for customers, employees, or administrators (using) Cloud Resources with no provisioning or setup required.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RESTful APIs:&lt;/strong&gt; Create RESTful web services and automatically scale them based upon the number of requests received.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MicroServices:&lt;/strong&gt; Build independent Components representing individual Business Feature Sets with distinct Endpoints and Scale Independently of each other based upon Business Demand.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SaaS Backend:&lt;/strong&gt; SaaS Applications enable the construction of multi-tenant Applications whereby each Tenant may require their own Service Instances...
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prototypes:&lt;/strong&gt; Build applications by prototyping or only those Features that validate your Idea without wasting the time spent on configuring the Software Infrastructure for an Application.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When Not to Use App Runner
&lt;/h2&gt;

&lt;h3&gt;
  
  
  App Runner is Not Designed for the Following:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Background Jobs / Scheduled Tasks
&lt;/li&gt;
&lt;li&gt;Batch Processing Workloads
&lt;/li&gt;
&lt;li&gt;Applications Requiring Complex VPC Configurations / Custom Networking
&lt;/li&gt;
&lt;li&gt;GPU Workloads / Specialized Compute Requirements
&lt;/li&gt;
&lt;li&gt;Multi-Region, Active-Active Deployments with Advanced Failover Strategies
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In these Situations, Consider ECS, Lambda, or EC2.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started with AWS App Runner
&lt;/h2&gt;

&lt;p&gt;To start using AWS App Runner:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Log in to the &lt;a href="https://console.aws.amazon.com/console/home" rel="noopener noreferrer"&gt;AWS Console&lt;/a&gt;:&lt;/strong&gt; Log in to your AWS account via the AWS Management Console.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Navigate to App Runner:&lt;/strong&gt; Go to Compute Services and look for App Runner.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create a Service:&lt;/strong&gt; Select your Source Type - Container Image hosted on ECR or Source Code in GitHub.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configure the Settings:&lt;/strong&gt; Set your Deployment Configuration (Instance Size, Scaling Parameters, etc.) and enter any Environment Variables you need for the Application.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy:&lt;/strong&gt; App Runner will Build (in the case of Source Code), Deploy your Application, and Return an HTTPS Endpoint.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor:&lt;/strong&gt; Use the Integrated CloudWatch to monitor Logs and Metrics generated by your application.
&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%2F4un13f8n0xtv5r6ch4al.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4un13f8n0xtv5r6ch4al.png" alt="App Runner" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;AWS's App Runner is designed to change the way we deploy our cloud applications to the cloud. App Runner allows developers to not worry about the complexities of managing their infrastructure so they can simply focus on writing their application.&lt;/p&gt;

&lt;p&gt;If you are deploying web applications or APIs using containers, and you want the easiest way to go from your code to production, App Runner provides you with that option. Although App Runner may not be as versatile as ECS or EKS when it comes to managing multiple services across different regions and environments, App Runner is usually a better option for more simple web application deployments.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Do you want to learn more about how AWS App Runner can improve your application's deployment process? Please leave comments below!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>containers</category>
      <category>community</category>
      <category>development</category>
    </item>
    <item>
      <title>Optimizing Costs for Container Workloads on AWS EKS and ECS</title>
      <dc:creator>Samir Khanal</dc:creator>
      <pubDate>Wed, 24 Dec 2025 16:46:51 +0000</pubDate>
      <link>https://dev.to/shawmeer22/optimizing-costs-for-container-workloads-on-aws-eks-and-ecs-37dd</link>
      <guid>https://dev.to/shawmeer22/optimizing-costs-for-container-workloads-on-aws-eks-and-ecs-37dd</guid>
      <description>&lt;p&gt;Hey Everyone! let's talk about something that I know all of us care about. That is saving money on our cloud bills. I recently dived deep into optimizing our container costs on AWS, and to be honest, I wish I knew all of these insights earlier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Container Cost Optimization Matters
&lt;/h2&gt;

&lt;p&gt;The thing is containers are a huge win on the scaling and deployment side, but they can quietly carry away at your budget if you’re not keeping an eye on it. The beast? The beauty is AWS offers us a whole bunch of ways to make the cost magic disappear without impacting performance or even improving it, quite often.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spot Instances: Your Secret Weapon
&lt;/h2&gt;

&lt;p&gt;This is in all probability, the biggest win that AWS has provided to me. You can save up to 90% in spot instances over on-demand instances. Yes, 90%! These are ideal for fault-tolerant applications that are able to withstand intermittent disruptions.&lt;/p&gt;

&lt;p&gt;EKS makes the process of using managed node groups with spot instances relatively easy. You can even use both spot instances and regular instances,within the same EKS cluster. In this way, you could use regular instances to power critical applications, while another application could use spot instances.&lt;/p&gt;

&lt;p&gt;To begin, moving our batch-processing workloads and our CI/CD pipelines to Spot instances worked beautifully for us. These workloads are inherently interruptible, so the cost benefit is instantaneous. The key, though, is to ensure your applications can shut down gracefully, and you're good to go.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fargate vs EC2: Choosing Wisely
&lt;/h2&gt;

&lt;p&gt;But I did have questions about when to pick Fargate over EC2 and when to pick EC2 over Fargate. Fargate costs more per compute unit, but it does away with operational costs.&lt;/p&gt;

&lt;p&gt;I learned the following: Fargate is great for workloads that have unpredictable traffic or a smaller size, or if you want no infrastructure management. The service charges are for what you use down to the second with no wasted capacity.&lt;/p&gt;

&lt;p&gt;EC2 would be more appropriate for production workloads where right-sizing and maintaining high resource utilization are possible. For large application deployments that exhibit predictable resource behaviour, EC2 using Reserved Instances and/or Savings Plans tends to be cheaper.&lt;/p&gt;

&lt;p&gt;As for my current approach,&lt;br&gt;
I am using Fargate for dev environments and occasional workloads. Production workloads that receive steady traffic run on well-optimized instances of EC2.&lt;br&gt;
It’s essentially about using the right tool for the right job.&lt;/p&gt;
&lt;h2&gt;
  
  
  Autoscaling: The Dynamic Duo
&lt;/h2&gt;

&lt;p&gt;Two things that changed the way I think about resource allocation are Cluster Autoscaler and Horizontal Pod Autoscaler (HPA).&lt;/p&gt;

&lt;p&gt;The Cluster Autoscaler automatically scales the number of nodes in your cluster based on how many pending pods there are. No more spending money on nodes just sitting there, doing nothing.&lt;/p&gt;

&lt;p&gt;HPA scales your pods at an application level based on CPU, memory, or your customized metrics. All three are a magnificent symphony of efficiency. Your cluster scales up as your traffic boosts and down when your traffic diminishes. Doing so by itself saved us 30% because we stopped over-provisioning just in case.&lt;/p&gt;

&lt;p&gt;Setting up HPA is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;autoscaling/v2&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HorizontalPodAutoscaler&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app-hpa&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;scaleTargetRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
  &lt;span class="na"&gt;minReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;maxReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
  &lt;span class="na"&gt;metrics&lt;/span&gt;&lt;span class="pi"&gt;:&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;Resource&lt;/span&gt;
    &lt;span class="na"&gt;resource&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;cpu&lt;/span&gt;
      &lt;span class="na"&gt;target&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;Utilization&lt;/span&gt;
        &lt;span class="na"&gt;averageUtilization&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;70&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Rightsizing: Stop Wasting Resources
&lt;/h2&gt;

&lt;p&gt;I was guilty of this too: setting pod resource requests much too high as a precaution. But no, I was actually paying for resources we didn't use.&lt;/p&gt;

&lt;p&gt;Start with understanding your actual resource consumption. Evaluate what your pods actually consume resource-wise and not what you believe they consume. You can use the Kubernetes metrics server to achieve this.&lt;/p&gt;

&lt;p&gt;Next, adjust your resource requests and limits based on that. For example, if your pod is using 100MB of memory but you have a resource request of 512MB, you are wasting money. Be practical about your application resource requirements, and remember that resource limits are important too.&lt;/p&gt;

&lt;p&gt;Practice tip: Begin conservatively, watching for a week or two, and then optimize. It may seem slow, but the cost savings really add up over dozens or hundreds of pods.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kubecost: Your Financial Visibility Partner
&lt;/h2&gt;

&lt;p&gt;His tool was nothing short of revolutionary for me. Kubecost is a cost visibility tool for Kubernetes workloads that delivers real-time cost visibility. What it does is show you exactly where your dollars are being spent – down to the namespace level or down to pods.&lt;/p&gt;

&lt;p&gt;What I like best about Kubecost is the way it provides cost data broken down by teams, apps, or environments. Now you can see that the actual cost of the staging environment is up to the cost of the production environment oops, or that one microservice is consuming 40% of the compute cost.&lt;/p&gt;

&lt;p&gt;The community edition offers a lot and is ideal for beginners. Once you install it on your cluster, you get information regarding cost allocation, optimization, and even alerts for spending above certain limits. It’s almost like having your own financial analyst for all of your Kubernetes clusters.&lt;/p&gt;

&lt;h2&gt;
  
  
  ECR Lifecycle Policies: Clean Up and Save
&lt;/h2&gt;

&lt;p&gt;This is something to easily forget: those images inside those container repositories in your Amazon ECR? They're going to start costing money to store. Just because those versions are ancient and sitting around unused means they're effectively burning money.&lt;/p&gt;

&lt;p&gt;ECR lifecycle policies allow you to automatically clean up your images by age or number. One of the first things I did was establish a basic policy of retaining the last 10 images in a repository and removing anything over 30 days if not pulled.&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;"rules"&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;"rulePriority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Keep last 10 images"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"selection"&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;"tagStatus"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"any"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"countType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"imageCountMoreThan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"countNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"expire"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s a small thing, but if you’re working with several repositories, the disk space savings can add up.&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%2Fy1inklzgtvbd4gseucn1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy1inklzgtvbd4gseucn1.png" alt="Cost Optimizition" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bringing It All Together
&lt;/h2&gt;

&lt;p&gt;Cost optimization, however, doesn't fall under the category of something that's done in one go. My take on this would be to begin with the quick wins. The.quick wins include autoscaling, Spot instances, and Kubecost.&lt;/p&gt;

&lt;p&gt;Next, begin to focus on rightsizing, cleanup of old images, and making educated decisions between Fargate and EC2. Monitor progress and rejoice at the wins along the way. We were able to lower our costs in containers by close to 45% in three months.&lt;br&gt;
The Next Areas to Tackle: Infrastructure and IRSA&lt;/p&gt;

&lt;p&gt;Also, remember that every dollar you save is a dollar you can then invest in building better features or improving your infrastructure in different ways.&lt;/p&gt;

&lt;p&gt;What cost optimization methods have you tried with success? I'd love to hear your experiences so I don't miss any tips. Thanks in advance, and let's keep learning from each other!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Share your “best tip on cutting costs” in the comment section below to help each other stay on top of those cloud bills!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>eks</category>
      <category>containers</category>
      <category>community</category>
    </item>
    <item>
      <title>My Journey with Amazon EKS: Simplifying Kubernetes on AWS</title>
      <dc:creator>Samir Khanal</dc:creator>
      <pubDate>Wed, 24 Dec 2025 15:45:35 +0000</pubDate>
      <link>https://dev.to/shawmeer22/my-journey-with-amazon-eks-simplifying-kubernetes-on-aws-4g0b</link>
      <guid>https://dev.to/shawmeer22/my-journey-with-amazon-eks-simplifying-kubernetes-on-aws-4g0b</guid>
      <description>&lt;p&gt;Hey everyone, I wanted to share with you all something I've been exploring within the cloud services infrastructure that I am truly passionate about Amazon EKS, also known as Amazon Elastic Kubernetes Service. If you have been wanting to leverage Kubernetes within AWS but have been deterred by the complexity of setup, this article is written for you &lt;/p&gt;

&lt;h2&gt;
  
  
  What is Amazon &lt;a href="https://aws.amazon.com/eks/" rel="noopener noreferrer"&gt;EKS&lt;/a&gt;?
&lt;/h2&gt;

&lt;p&gt;To break it down into simpler terms: the Amazon EKS service is essentially the managed Kubernetes service offered by AWS. It is almost as if AWS is handling the whole heavy lifting of Kubernetes itself so that you are left with the liberty of concentrating on application management.&lt;/p&gt;

&lt;p&gt;EKS is essentially a system that makes it easier to manage your applications running in containers hosted across multiple machines. The interesting part about this is that, while creating your own EKS clusters is not very easy, EKS is exactly what you need. EKS takes care of things like master node setup, high availability, security patches, and upgrade tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes EKS Special?
&lt;/h2&gt;

&lt;p&gt;My exploration of EKS has revealed the following salient features:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fully Managed Control Plane&lt;/strong&gt;: The control plane process in Kubernetes is automatically handled by AWS across various availability zones. This enhances reliability with less effort on your part to design this yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Smooth Integration with AWS&lt;/strong&gt;: EKS integrates seamlessly well with other AWS services like IAM for auth, VPC for networking, and ELB for balancing loads. Imagine how easily you can work while building cloud native applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security Baked In&lt;/strong&gt;: When you think about it, security is essentially baked in here because of automatic encryption, its tie to IAM, and VPCs. You also get automatic security patches for your control plane.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flexibility with Compute Options&lt;/strong&gt;: Instance options for computing, AWS Fargate with serverless containers, and EKS Anywhere, aimed at extending services to premises, give you the opportunity to run your compute workloads anywhere. This is an important factor, given your needs may vary.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Helps Us as Developers and DevOps Engineers
&lt;/h2&gt;

&lt;p&gt;It’s at this stage that things become more practical. As someone keen on efficiency, I appreciate the way EKS resolves some of the key pain points for people like me:&lt;/p&gt;

&lt;p&gt;You have fewer activities related to infrastructure maintenance and more related to application development. The learning curve here isn’t as steep since if you know Kubernetes already, you’ll pretty much need no learning. You will not be locked into AWS-specific services either since you can use standard Kubernetes services and configurations.&lt;/p&gt;

&lt;p&gt;Another advantage is that EKS is scalable. Your applications will be able to scale from a few users to thousands without you having to redesign your architecture. EKS scales the control plane automatically.&lt;/p&gt;

&lt;p&gt;In terms of cost, you will be responsible for paying for the EKS control plane only and the compute resources that you actually consume. This does not require overprovisioning, and this can result in some cost savings.&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%2Fayuqhf7v4sjrswxl74zx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fayuqhf7v4sjrswxl74zx.png" alt="AWS EKS" width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started with EKS
&lt;/h2&gt;

&lt;p&gt;Are you ready to give EKS a test drive? Here's the simplified step-by-step plan to get started&lt;/p&gt;

&lt;p&gt;First, ensure that you have both AWS CLI and kubectl installed on your machine. Additionally, you should install &lt;code&gt;eksctl&lt;/code&gt;, which is a command line utility specifically built to make creating an EKS cluster simpler.&lt;/p&gt;

&lt;p&gt;Creating your first cluster can be as simple as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eksctl create cluster --name my-first-cluster --region us-east-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This single command launches a whole EKS cluster with worker nodes. It is almost magical to see the amount of complexity abstracted away.&lt;/p&gt;

&lt;p&gt;Once your cluster is up, you can then deploy applications by using standard Kubernetes manifests. This is a very familiar experience if you're used to running applications via Kubernetes, but without all of the management overhead.&lt;/p&gt;

&lt;p&gt;The AWS documentation is comprehensive, and there is an active and supportive community for EKS. Do not be afraid to dive into the official tutorials and guides because they really are helpful resources.&lt;/p&gt;

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

&lt;p&gt;Finding about Amazon EKS has been an eye-opening experience for me. It is an excellent platform that provides you the ability and flexibility of Kubernetes, taking away all the operational hassles from it.&lt;/p&gt;

&lt;p&gt;Whether you are running microservices, batch jobs, or machine learning workloads, EKS gets you off to a strong start. And the best part? It scales alongside your maturing demands.&lt;/p&gt;

&lt;p&gt;If you’ve been looking at Kubernetes for your workloads that run on AWS, I would say you should definitely give EKS a whirl. Just start with a tiny application and see what it feels like. The hurdle is not that high.&lt;/p&gt;

&lt;p&gt;Would love to hear about your experience with EKS or questions you have. Feel free to provide your thoughts below via comments. Happy cloud building!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What is your favorite part about EKS, or what might be keeping you from trying EKS? Let's discuss!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>eks</category>
      <category>aws</category>
      <category>kubernetes</category>
      <category>community</category>
    </item>
    <item>
      <title>Understanding Serverless Containers on AWS</title>
      <dc:creator>Samir Khanal</dc:creator>
      <pubDate>Mon, 22 Dec 2025 08:21:31 +0000</pubDate>
      <link>https://dev.to/shawmeer22/understanding-serverless-containers-on-aws-41il</link>
      <guid>https://dev.to/shawmeer22/understanding-serverless-containers-on-aws-41il</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.tourl"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;I recently heard about the term ‘serverless containers’. I was already working with cloud and DevOps, Docker, and container orchestration, so I had a question for myself: “What exactly is a serverless container, and how can containers be serverless when they run on servers?”&lt;br&gt;
With that curiosity, I explored serverless containers on AWS, which I found was a powerful approach that blends the flexibility of containers with the simplicity of serverless computing&lt;/p&gt;

&lt;p&gt;In this post, I’ll discuss what serverless containers really mean, how AWS IMPLEMENTS THEM, and where they make sense in real-world environments&lt;/p&gt;

&lt;h2&gt;
  
  
  Serverless Containers:
&lt;/h2&gt;

&lt;p&gt;Serverless containers are containerized workloads that run the app without managing the underlying servers. Serverless does not mean that there are no servers. Servers exist, but we don't need to manage them. AWS takes full responsibility for them.&lt;/p&gt;

&lt;p&gt;For the traditional container deployments, you have to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choose the EC2 types&lt;/li&gt;
&lt;li&gt;Provision and manage clusters&lt;/li&gt;
&lt;li&gt;Patch operating systems&lt;/li&gt;
&lt;li&gt;Plan for scaling and capacity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But with serverless, AWS handles all of that. You simply need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build your container image&lt;/li&gt;
&lt;li&gt;Define CPU and memory needs&lt;/li&gt;
&lt;li&gt;Deploy the workloads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AWS manages the provisioning of compute resources, launching containers, dynamically scaling them as needed, and managing the infrastructure lifecycle.&lt;/p&gt;

&lt;p&gt;So, preserving all of the benefits of container-based applications portability, consistency, and flexibility but significantly reducing operational overhead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Serverless Containers on AWS
&lt;/h2&gt;

&lt;p&gt;AWS provides two options for deploying serverless containers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://aws.amazon.com/fargate/" rel="noopener noreferrer"&gt;AWS Fargate&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Serverless computing for containers (Fargate) is offered by the AWS Cloud for Amazon ECS (Elastic Container Service) or Amazon EKS (Elastic Kubernetes Service) without requiring customers to manage any EC2 instances or worker nodes. Instead, you define your application's task or pod specifications, and AWS takes care of running those specifications for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://aws.amazon.com/pm/lambda/?trk=da204135-1455-430c-a587-0b38a41624b4&amp;amp;sc_channel=ps&amp;amp;ef_id=CjwKCAiA9aPKBhBhEiwAyz82J0nDJ53LcWDhJvy1MGMJ1COu-aA0fq5p7QtzWyFdubJk-HkNPH9uURoCBWkQAvD_BwE:G:s&amp;amp;s_kwcid=AL!4422!3!785483253781!e!!g!!aws%20lambda!23300619076!189486859415&amp;amp;gad_campaignid=23300619076&amp;amp;gbraid=0AAAAADjHtp9EzsG08sLAE1vmd6jsxkW9D&amp;amp;gclid=CjwKCAiA9aPKBhBhEiwAyz82J0nDJ53LcWDhJvy1MGMJ1COu-aA0fq5p7QtzWyFdubJk-HkNPH9uURoCBWkQAvD_BwE" rel="noopener noreferrer"&gt;AWS Lambda&lt;/a&gt; with Container Images&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AWS Lambda also allows you to package your function as a container image with a maximum size of 10 GB instead of a traditional ZIP file. Package your function in a container format when you require a custom runtime environment, and you utilise native dependencies or want to follow a Docker-based build process. When you deploy your container image to AWS Lambda, you receive all of the benefits associated with AWS Lambda's Event-Driven Model, Auto Scaling, and Pay-As-You-Go pricing, along with complete compatibility of your container image(s) with AWS Lambda.&lt;/p&gt;

&lt;p&gt;Both services are classified as serverless because they allow customers to describe how to run their applications; AWS takes care of provisioning, scaling, and maintaining hardware resources on behalf of the customer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Serverless Container Key Features
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;No Server Management&lt;/strong&gt;&lt;br&gt;
  No EC2 Instances to Size, Patch, or Monitor. AWS fully manages the          Compute Layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automatic Scaling&lt;/strong&gt;&lt;br&gt;
   The Compute Layer Automatically Scales Up when Demand increases&lt;br&gt;&lt;br&gt;
    and down when Demand decreases, without requiring any human work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pay As You Go Pricing&lt;/strong&gt;&lt;br&gt;
  You only pay for CPU, Memory, and Time that your containers&lt;br&gt;&lt;br&gt;
  actually consume. And No Idle Capacity Costs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Native AWARE (AWS) Integration&lt;/strong&gt;&lt;br&gt;
  AWS is Fully Integrated with IAM and VPC, as well as ALB for &lt;br&gt;
  networking, Security, and Observability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Container Flexibility&lt;/strong&gt;&lt;br&gt;
   Use Docker Images and Existing CI/CD Pipelines to Ease in Move of &lt;br&gt;
   Containers to the Cloud.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lambda Containers vs AWS Fargate
&lt;/h2&gt;

&lt;p&gt;Lambda and Fargate provide a serverless container option, but each supports applications with varying workload types. &lt;/p&gt;

&lt;p&gt;Lambda has many use cases for short-lived tasks that respond to events (S3, API Gateway, Amazon SQS, EventBridge) that can run for up to 15 minutes and handle inconsistent and unexpected spikes in traffic with lightweight APIs or background jobs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Examples of event-driven workloads&lt;/strong&gt; are an image resize service or file processing, webhooks, or cron jobs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fargate is intended for long-term applications,&lt;/strong&gt; like microservices that must keep persistent connections, such as long-running services or REST APIs with multiple connections, or microservices that must maintain their states or persist between requests. In addition, Fargate offers users more options for controlling how their architecture scales, as they can deploy their workloads using ECS or Kubernetes.&lt;/p&gt;

&lt;p&gt;Examples of applications that can be deployed using Amazon Fargate include (but are not limited to) internal tools, streaming processors, back-end services, and REST APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Example of Video Processing Pipeline
&lt;/h2&gt;

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

&lt;p&gt;Let's say you're creating a video processing application that generates thumbnail images every time someone uploads a video file. Upload traffic is unknown; it may have periods of low volume and sudden influxes (also known as "spikes"). &lt;/p&gt;

&lt;p&gt;Using Containers As Part Of A Lambda Computing Platform&lt;/p&gt;

&lt;p&gt;When a user uploads a video, the system first saves it to Amazon S3 Storage. Next, an event trigger in Amazon S3 initiates a Lambda function. Inside the Lambda function, you may call a container image to create thumbnails utilizing applications such as FFmpeg. Lastly, the Lambda function saves the generated thumbnails back to Amazon S3 Storage for users to view.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here's how it works:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There won't be any idle servers.&lt;/li&gt;
&lt;li&gt;You have scalable Lambda computing capabilities when there is high increase in traffic.&lt;/li&gt;
&lt;li&gt;You pay for each time you process; therefore, you do not need to maintain and manage a large EC2 fleet of instances that only process occasionally; you still use complex &amp;amp; powerful container-based software development tools.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Where AWS Fargate Fits Better&lt;/strong&gt;&lt;br&gt;
Imagine a microservice architecture that supports a mobile or web application.&lt;br&gt;
An example of this type of microservice architecture might have multiple APIs (user, order, payment, inventory) and must be up continuously. Load balancing and service discovery are also necessary for this type of architecture to work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When using ECS on Fargate:&lt;/strong&gt;&lt;br&gt;
Each of your microservice tasks runs a container task.&lt;/p&gt;

&lt;p&gt;The Application Load Balancer routes traffic.&lt;/p&gt;

&lt;p&gt;Your services scale based on either CPU/memory or request count.&lt;/p&gt;

&lt;p&gt;There will be no need to manage an EC2 cluster.&lt;/p&gt;

&lt;p&gt;Additionally, it provides a nice solution for DevOps teams that require a way to orchestrate containers without having to manage nodes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started with Serverless Containers on AWS
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Steps: Set Up Serverless Containers on AWS&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Determine which service you want to use, based on what you're trying to achieve&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;AWS Lambda is a good option when your service will be short-lived (only active while processing an event) and your application is event-driven.&lt;br&gt;
 AWS Fargate is a better option when you need to set up a long-running service that requires containerized applications.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create Your Application in a Docker Container&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Create a Dockerfile with best-practice guidelines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep base images slim&lt;/li&gt;
&lt;li&gt;Use multiple stages to build&lt;/li&gt;
&lt;li&gt;Minimize the number of layers in your image&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Upload Your Image to Amazon Elastic Container Registry (ECR)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;ECR is AWS's completely Managed, scalable image storage/management for storing, managing and delivering Docker container images.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deploy Your Container Based Applications&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most people use the AWS Console, the AWS CLI, Terraform or CI/CD pipelines to define AWS Lambda tasks or Amazon Elastic Container Services (ECS) tasks. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Improve your system's throughput and cost management by keeping track of performance metrics using AWS CloudWatch logs, metrics and alarms.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Container management infrastructure is removed by serverless, but the large degree of container flexibility remains.&lt;/li&gt;
&lt;li&gt;Two serverless options on AWS: Lambda Container and Fargate.&lt;/li&gt;
&lt;li&gt;You should choose Choose based on runtime duration, architecture, or scalability needs&lt;/li&gt;
&lt;li&gt;Serverless containers offer faster deployments, simplify operational processes, and provide greater cost-effectiveness.&lt;/li&gt;
&lt;li&gt;They are best for modern cloud-native DevOps workflows.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Serverless containers fill the gap between traditional containers and serverless computing; it allow you to continue using your existing Docker workflow without the hassles of managing servers, clusters, or even performing capacity planning.&lt;/p&gt;

&lt;p&gt;AWS allows you to develop and deploy event-driven functions in AWS Lambda as well as scalable microservices using AWS Fargate. This means that not only can you deploy applications faster but also run them more efficiently than before, thanks to the extensive capabilities of AWS.&lt;/p&gt;

&lt;p&gt;Feel free to reach out in the comments if you have questions or want to share your experiences with serverless containers!&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>containers</category>
      <category>aws</category>
      <category>devops</category>
    </item>
    <item>
      <title>Automating Container Security on AWS with CI/CD and Fargate using GitHub Actions</title>
      <dc:creator>Samir Khanal</dc:creator>
      <pubDate>Tue, 16 Dec 2025 08:33:45 +0000</pubDate>
      <link>https://dev.to/shawmeer22/automating-container-security-on-aws-with-cicd-and-fargate-using-github-actions-1fml</link>
      <guid>https://dev.to/shawmeer22/automating-container-security-on-aws-with-cicd-and-fargate-using-github-actions-1fml</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;Introduction: Why I Built This&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A few months ago, I was responsible for managing a rapidly growing microservices architecture supported by AWS. There was five different services running in separate containers, each with its own dependencies, and I was continually worried about whether any of them had been updated with a vulnerability during image deployment.&lt;/p&gt;

&lt;p&gt;The real problem wasn't just the scanning of images; it was about how to automate and consistently maintain security across the entire deployment pipeline.&lt;br&gt;
Manual security checks do not scale, and we all know they are often skipped when a deadline approaches.&lt;/p&gt;

&lt;p&gt;I built an automated security pipeline to scan for vulnerabilities in container images using AWS Fargate and GitHub Actions. It eliminated all the manual scanning of images, as well as the constant fear of whether the last base image had critical CVEs and whether we were deploying insecure code to production.&lt;/p&gt;

&lt;p&gt;In this article, I will provide a detailed overview of how I set this up with example configurations and command lines I ran, as well as some of the lessons I learned from implementing it in a production environment..&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What We Will Discuss:&lt;/strong&gt;&lt;br&gt;
How to set up ECR with automated vulnerability scanning.&lt;/p&gt;

&lt;p&gt;How to create GitHub Actions workflows that will do deployments based on security findings.&lt;/p&gt;

&lt;p&gt;What Least Privilege IAM Roles and Network Isolation on Fargate mean.&lt;/p&gt;

&lt;p&gt;How to continuously monitor and track compliance.&lt;/p&gt;

&lt;p&gt;An example step-by-step for your projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture Overview: The Big Picture&lt;/strong&gt;&lt;br&gt;
Now that we have an overview of what we will be doing, here's an overview of the architecture that supports the automated security pipeline you built:&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%2F9b65myctmwzx62y3b6gz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9b65myctmwzx62y3b6gz.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this configuration, you will have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automated security scanning&lt;/strong&gt; of all container images before reaching production.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network Isolation&lt;/strong&gt; for Private Subnets and Security Groups.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Least Privilege Access&lt;/strong&gt; with Separate IAM Roles for Execution and Task Roles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complete Audit Trail&lt;/strong&gt; via CloudWatch Logs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime Protection&lt;/strong&gt; via Fargate Isolation Model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now Let's Get into How Each Component Functions!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Container Image Security&lt;/strong&gt;&lt;br&gt;
It is necessary not to assume that any images of containers are trustworthy. Even official images used by developers can contain vulnerabilities and/or have dependencies that could create security problems.&lt;br&gt;
I locked down the image containers by:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Automatic image scanning ECR (Scan on push)&lt;/strong&gt;&lt;br&gt;
For each repository in ECR, I enabled image scanning on push. When I push an image into ECR, AWS automatically scans that image against the CVE database. To enable image scan on push for a repository, follow these steps:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enabling scan-on-push for a repository:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws ecr put-image-scanning-configuration &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--repository-name&lt;/span&gt; production-api &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--image-scanning-configuration&lt;/span&gt; &lt;span class="nv"&gt;scanOnPush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Configuring enhanced scanning with Inspector:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws ecr put-registry-scanning-configuration &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--scan-type&lt;/span&gt; ENHANCED &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--rules&lt;/span&gt; &lt;span class="s1"&gt;'[{"repositoryFilters":[{"filter":"*","filterType":"WILDCARD"}],"scanFrequency":"CONTINUOUS_SCAN"}]'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With improved scanning you will discover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Operating system vulnerabilities&lt;/li&gt;
&lt;li&gt;Vulnerabilities in programming-language packages (Python, Node.JS,
&lt;/li&gt;
&lt;li&gt; Java etc.)&lt;/li&gt;
&lt;li&gt;Continuous monitoring of your environment not just at the time    you 'push' your container image.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. IAM Roles - Separation is Key&lt;/strong&gt;&lt;br&gt;
I learnt the hard way - do not ever use the same IAM Role across everything. Here is how I distinguish between:&lt;br&gt;
Task Execution Role - this is the role that ECS will require in order to launch the container you have created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ecr:GetAuthorizationToken"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ecr:BatchCheckLayerAvailability"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ecr:GetDownloadUrlForLayer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ecr:BatchGetImage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"logs:CreateLogStream"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"logs:PutLogEvents"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;*&lt;em&gt;Task Role *&lt;/em&gt;(what your application needs):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"dynamodb:GetItem"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"dynamodb:PutItem"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"dynamodb:Query"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:dynamodb:us-east-1:745812456855:table/users-table"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::my-app-assets/*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The service’s requirement for executing a task is identical for all services; however, each service's specific need defines the task role.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Base Image Selection &amp;amp; Multi-Stage Builds &lt;/li&gt;
&lt;li&gt;I no longer use large operating system images; instead, I use small base images as a foundation. Below is an example comparing both types of base images.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Base Image Size and Vulnerabilities (my tests show the below data).&lt;/p&gt;

&lt;p&gt;node:18 - full image: 1.1 GB 247 CVEs&lt;br&gt;
node:18-slim - small image: 243 MB 89 CVEs&lt;br&gt;
node:18-alpine - container images: 178 MB 12 CVEs&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My Dockerfile Strategy&lt;/strong&gt; Uses Multi-Stage Builds:&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="c"&gt;# Build stage - includes dev dependencies&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:18-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&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; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="c"&gt;# Production stage - minimal runtime&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18-alpine&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Create non-root user&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;-g&lt;/span&gt; 1001 appuser &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    adduser &lt;span class="nt"&gt;-D&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; 1001 &lt;span class="nt"&gt;-G&lt;/span&gt; appuser appuser

&lt;span class="c"&gt;# Copy only production artifacts&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder --chown=appuser:appuser /app/dist ./dist&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder --chown=appuser:appuser /app/node_modules ./node_modules&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder --chown=appuser:appuser /app/package.json ./&lt;/span&gt;

&lt;span class="c"&gt;# Switch to non-root user&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; appuser&lt;/span&gt;

&lt;span class="c"&gt;# Run the application&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "dist/index.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Security Practices in the Dockerfile:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Utilizes an Alpine image as a base&lt;/li&gt;
&lt;li&gt;Implements a multi-stage build; production image has no build tools&lt;/li&gt;
&lt;li&gt;Runs the image as a non-root user (following the principle of least privilege)&lt;/li&gt;
&lt;li&gt;Only production dependencies included in the image&lt;/li&gt;
&lt;li&gt;Related dependencies are pinned to a specific version.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Vulnerability Thresholds&lt;/strong&gt;&lt;br&gt;
I created a policy that states: No Critical or High-Risk vulnerabilities exist in production -- No Exceptions.&lt;br&gt;
The following process allows me to check the programmatic results of the scans:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Get the latest scan findings&lt;/span&gt;
aws ecr describe-image-scan-findings &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--repository-name&lt;/span&gt; production-api &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--image-id&lt;/span&gt; &lt;span class="nv"&gt;imageTag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;v1.2.3 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'imageScanFindings.findingSeverityCounts'&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Output:&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;"CRITICAL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"HIGH"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"MEDIUM"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"LOW"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"INFORMATIONAL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&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;A build will fail automatically when CRITICAL or HIGH vulnerabilities are detected in GitHub Actions CI/CD using our automated security process (in the following section).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automating Security in GitHub Actions CI/CD&lt;/strong&gt;&lt;br&gt;
This is where things become interesting: every time new code is pushed into the master branch, an automated process is created, which builds the application, scans it for vulnerabilities, and only then deploys (if everything was successful).&lt;/p&gt;

&lt;p&gt;GitHub Actions Full Workflow&lt;br&gt;
Here is the workflow document I have.(.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="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build, Scan, and Deploy to Fargate&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="nv"&gt;main&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;AWS_REGION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;us-east-1&lt;/span&gt;
  &lt;span class="na"&gt;ECR_REPOSITORY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production-api&lt;/span&gt;
  &lt;span class="na"&gt;ECS_CLUSTER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production-cluster&lt;/span&gt;
  &lt;span class="na"&gt;ECS_SERVICE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api-service&lt;/span&gt;
  &lt;span class="na"&gt;CONTAINER_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api-container&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-and-scan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and Security Scan&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;outputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.build-image.outputs.image }}&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;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@v2&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;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;${{ env.AWS_REGION }}&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Login to Amazon ECR&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;login-ecr&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/amazon-ecr-login@v1&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build Docker image&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build-image&lt;/span&gt;
      &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ECR_REGISTRY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.login-ecr.outputs.registry }}&lt;/span&gt;
        &lt;span class="na"&gt;IMAGE_TAG&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.sha }}&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .&lt;/span&gt;
        &lt;span class="s"&gt;docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest&lt;/span&gt;
        &lt;span class="s"&gt;echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" &amp;gt;&amp;gt; $GITHUB_OUTPUT&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;Push image to Amazon ECR&lt;/span&gt;
      &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ECR_REGISTRY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.login-ecr.outputs.registry }}&lt;/span&gt;
        &lt;span class="na"&gt;IMAGE_TAG&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.sha }}&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG&lt;/span&gt;
        &lt;span class="s"&gt;docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Wait for ECR scan to complete&lt;/span&gt;
      &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;IMAGE_TAG&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.sha }}&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;echo "Waiting for image scan to complete..."&lt;/span&gt;
        &lt;span class="s"&gt;sleep 30&lt;/span&gt;

        &lt;span class="s"&gt;for i in {1..20}; do&lt;/span&gt;
          &lt;span class="s"&gt;SCAN_STATUS=$(aws ecr describe-image-scan-findings \&lt;/span&gt;
            &lt;span class="s"&gt;--repository-name $ECR_REPOSITORY \&lt;/span&gt;
            &lt;span class="s"&gt;--image-id imageTag=$IMAGE_TAG \&lt;/span&gt;
            &lt;span class="s"&gt;--region $AWS_REGION \&lt;/span&gt;
            &lt;span class="s"&gt;--query 'imageScanStatus.status' \&lt;/span&gt;
            &lt;span class="s"&gt;--output text 2&amp;gt;/dev/null || echo "PENDING")&lt;/span&gt;

          &lt;span class="s"&gt;echo "Scan status: $SCAN_STATUS"&lt;/span&gt;

          &lt;span class="s"&gt;if [ "$SCAN_STATUS" = "COMPLETE" ]; then&lt;/span&gt;
            &lt;span class="s"&gt;echo "Scan completed successfully"&lt;/span&gt;
            &lt;span class="s"&gt;break&lt;/span&gt;
          &lt;span class="s"&gt;elif [ "$SCAN_STATUS" = "FAILED" ]; then&lt;/span&gt;
            &lt;span class="s"&gt;echo "Scan failed!"&lt;/span&gt;
            &lt;span class="s"&gt;exit 1&lt;/span&gt;
          &lt;span class="s"&gt;fi&lt;/span&gt;

          &lt;span class="s"&gt;sleep 15&lt;/span&gt;
        &lt;span class="s"&gt;done&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;Check for vulnerabilities&lt;/span&gt;
      &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;IMAGE_TAG&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.sha }}&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;echo "Checking for vulnerabilities..."&lt;/span&gt;

        &lt;span class="s"&gt;FINDINGS=$(aws ecr describe-image-scan-findings \&lt;/span&gt;
          &lt;span class="s"&gt;--repository-name $ECR_REPOSITORY \&lt;/span&gt;
          &lt;span class="s"&gt;--image-id imageTag=$IMAGE_TAG \&lt;/span&gt;
          &lt;span class="s"&gt;--region $AWS_REGION)&lt;/span&gt;

        &lt;span class="s"&gt;echo "$FINDINGS" | jq '.imageScanFindings.findingSeverityCounts'&lt;/span&gt;

        &lt;span class="s"&gt;CRITICAL=$(echo "$FINDINGS" | jq -r '.imageScanFindings.findingSeverityCounts.CRITICAL // 0')&lt;/span&gt;
        &lt;span class="s"&gt;HIGH=$(echo "$FINDINGS" | jq -r '.imageScanFindings.findingSeverityCounts.HIGH // 0')&lt;/span&gt;
        &lt;span class="s"&gt;MEDIUM=$(echo "$FINDINGS" | jq -r '.imageScanFindings.findingSeverityCounts.MEDIUM // 0')&lt;/span&gt;

        &lt;span class="s"&gt;echo "Critical vulnerabilities: $CRITICAL"&lt;/span&gt;
        &lt;span class="s"&gt;echo "High vulnerabilities: $HIGH"&lt;/span&gt;
        &lt;span class="s"&gt;echo "Medium vulnerabilities: $MEDIUM"&lt;/span&gt;

        &lt;span class="s"&gt;if [ "$CRITICAL" -gt 0 ] || [ "$HIGH" -gt 0 ]; then&lt;/span&gt;
          &lt;span class="s"&gt;echo "❌ CRITICAL or HIGH vulnerabilities found! Deployment blocked."&lt;/span&gt;
          &lt;span class="s"&gt;echo "Please fix vulnerabilities before deploying."&lt;/span&gt;
          &lt;span class="s"&gt;exit 1&lt;/span&gt;
        &lt;span class="s"&gt;fi&lt;/span&gt;

        &lt;span class="s"&gt;if [ "$MEDIUM" -gt 5 ]; then&lt;/span&gt;
          &lt;span class="s"&gt;echo "⚠️  Warning: More than 5 MEDIUM vulnerabilities found."&lt;/span&gt;
          &lt;span class="s"&gt;echo "Consider addressing these vulnerabilities."&lt;/span&gt;
        &lt;span class="s"&gt;fi&lt;/span&gt;

        &lt;span class="s"&gt;echo "✅ Security scan passed!"&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 Trivy vulnerability scanner&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;aquasecurity/trivy-action@master&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;image-ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.build-image.outputs.image }}&lt;/span&gt;
        &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sarif'&lt;/span&gt;
        &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;trivy-results.sarif'&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;Upload Trivy results to GitHub Security&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;github/codeql-action/upload-sarif@v2&lt;/span&gt;
      &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;sarif_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;trivy-results.sarif'&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 Fargate&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;build-and-scan&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;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.ref == 'refs/heads/main' &amp;amp;&amp;amp; github.event_name == 'push'&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;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@v2&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;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;${{ env.AWS_REGION }}&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Download task definition&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 ecs describe-task-definition \&lt;/span&gt;
          &lt;span class="s"&gt;--task-definition api-service-task \&lt;/span&gt;
          &lt;span class="s"&gt;--query taskDefinition &amp;gt; task-definition.json&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;Fill in the new image ID in the task definition&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;task-def&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/amazon-ecs-render-task-definition@v1&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;task-definition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;task-definition.json&lt;/span&gt;
        &lt;span class="na"&gt;container-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.CONTAINER_NAME }}&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ needs.build-and-scan.outputs.image }}&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 Amazon ECS&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/amazon-ecs-deploy-task-definition@v1&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;task-definition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.task-def.outputs.task-definition }}&lt;/span&gt;
        &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.ECS_SERVICE }}&lt;/span&gt;
        &lt;span class="na"&gt;cluster&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.ECS_CLUSTER }}&lt;/span&gt;
        &lt;span class="na"&gt;wait-for-service-stability&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Notify deployment success&lt;/span&gt;
      &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;success()&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;echo "🚀 Deployment successful!"&lt;/span&gt;
        &lt;span class="s"&gt;echo "Image: ${{ needs.build-and-scan.outputs.image }}"&lt;/span&gt;
        &lt;span class="s"&gt;echo "Service: ${{ env.ECS_SERVICE }}"&lt;/span&gt;
        &lt;span class="s"&gt;echo "Cluster: ${{ env.ECS_CLUSTER }}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Below is a list of the actions taken in this workflow, listed sequentially:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build:&lt;/li&gt;
&lt;li&gt;Retrieve code from the repository&lt;/li&gt;
&lt;li&gt;Create a Docker image&lt;/li&gt;
&lt;li&gt;Tag the image with the SHA for historical records&lt;/li&gt;
&lt;li&gt;Scan:&lt;/li&gt;
&lt;li&gt;Send the image to Amazon ECR (which starts an automatic scan)&lt;/li&gt;
&lt;li&gt;Check for completion of the scan (with timeout)&lt;/li&gt;
&lt;li&gt;Download the results of the scan for identified vulnerabilities&lt;/li&gt;
&lt;li&gt;Deployments that have Critical or High vulnerabilities are prohibited from proceeding&lt;/li&gt;
&lt;li&gt;Run another scan using Trivy&lt;/li&gt;
&lt;li&gt;Upload the results of the scan to the Security Tab in GitHub&lt;/li&gt;
&lt;li&gt;Deploy: This will only take place when a successful scan occurs.&lt;/li&gt;
&lt;li&gt;Pull current task definition file(s)&lt;/li&gt;
&lt;li&gt;Replace current image reference with new image&lt;/li&gt;
&lt;li&gt;Deploy the task to Fargate&lt;/li&gt;
&lt;li&gt;Observe until the service has stabilized&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The Secret Sauce&lt;/strong&gt; consists of Deployment Gate Checks, which essentially checks for any vulnerabilities present during the deployment process.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CRITICAL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HIGH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;" CRITICAL or HIGH vulnerabilities found! Deployment blocked."&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If any vulnerabilities are found during this deployment gate check, the workflow will fail and the user will not be able to deploy that artifact regardless of the reason. This process has saved us from deploying known CVEs on several occasions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Secrets Required to Configure GitHub&lt;/strong&gt;&lt;br&gt;
You will need to add the following secret keys to your repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWS_ACCESS_KEY_ID (ex: AKIAIOSFODNN7EXAMPLE)
 AWS_SECRET_ACCESS_KEY (ex: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pro tip: The best practice is to use an IAM user that has the least privileges necessary:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ecr:&lt;/strong&gt;* To your repositories&lt;br&gt;
&lt;strong&gt;ecs:DescribeTaskDefinition&lt;/strong&gt;, &lt;strong&gt;ecs:RegisterTaskDefinition&lt;/strong&gt;, &lt;strong&gt;ecs:UpdateService&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;iam:PassRole&lt;/strong&gt; to the task execution role&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security and Runtime Management of Your Tasks Running within Fargate&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are many built-in Security features available with Fargate, including isolated processors and no-host management capabilities; however, it is still necessary to properly configure Fargate to obtain maximum benefits from these built-in Security capabilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Network Segmentation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When using Fargate to deploy my services, I created an environment with Private SUBNETS That Do Not Have Direct Access To The Internet. To leverage Outbound Connectivity (i.e., For ECR Image Pulling and API Calls), I used NAT Gateways (server-side Network Address Translation Gateway). &lt;/p&gt;

&lt;p&gt;I Created A VPC According To This Configuration Through Terraform.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc"&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_block&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.0.0/16"&lt;/span&gt;
  &lt;span class="nx"&gt;enable_dns_hostnames&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;enable_dns_support&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"production-vpc"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"private_a"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_block&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.1.0/24"&lt;/span&gt;
  &lt;span class="nx"&gt;availability_zone&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1a"&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"production-private-subnet-a"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"private_b"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_block&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.2.0/24"&lt;/span&gt;
  &lt;span class="nx"&gt;availability_zone&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1b"&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"production-private-subnet-b"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_security_group"&lt;/span&gt; &lt;span class="s2"&gt;"ecs_tasks"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ecs-tasks-sg"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Security group for ECS tasks"&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="c1"&gt;# Allow outbound to internet (via NAT Gateway)&lt;/span&gt;
  &lt;span class="nx"&gt;egress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"-1"&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Allow inbound from ALB only&lt;/span&gt;
  &lt;span class="nx"&gt;ingress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;security_groups&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;description&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow traffic from ALB"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ecs-tasks-security-group"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;A sample task definition (Terraform) for Fargate Task Definitions that adheres to secure practices:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ecs_task_definition"&lt;/span&gt; &lt;span class="s2"&gt;"api_service"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;family&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"api-service-task"&lt;/span&gt;
  &lt;span class="nx"&gt;network_mode&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"awsvpc"&lt;/span&gt;
  &lt;span class="nx"&gt;requires_compatibilities&lt;/span&gt; &lt;span class="p"&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="nx"&gt;cpu&lt;/span&gt;                      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"512"&lt;/span&gt;
  &lt;span class="nx"&gt;memory&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1024"&lt;/span&gt;
  &lt;span class="nx"&gt;execution_role_arn&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ecs_execution_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;task_role_arn&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api_task_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;

  &lt;span class="nx"&gt;container_definitions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"api-container"&lt;/span&gt;
      &lt;span class="nx"&gt;image&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"745812456855.dkr.ecr.us-east-1.amazonaws.com/production-api:latest"&lt;/span&gt;
      &lt;span class="nx"&gt;essential&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

      &lt;span class="nx"&gt;portMappings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;containerPort&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;
          &lt;span class="nx"&gt;protocol&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;

      &lt;span class="nx"&gt;logConfiguration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;logDriver&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"awslogs"&lt;/span&gt;
        &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"awslogs-group"&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/ecs/production-cluster/api-service"&lt;/span&gt;
          &lt;span class="s2"&gt;"awslogs-region"&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
          &lt;span class="s2"&gt;"awslogs-stream-prefix"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ecs"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;winston&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'winston'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;winston&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createLogger&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'info'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;winston&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;winston&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nx"&gt;winston&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;defaultMeta&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'api-service'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'production'&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;transports&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;winston&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Usage&lt;/span&gt;
&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'User login successful'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'user-123'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nx"&gt;ipAddress&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'203.0.113.42'&lt;/span&gt; 
&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Database connection failed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt; 
&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;

          &lt;span class="nx"&gt;name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"NODE_ENV"&lt;/span&gt;
          &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"production"&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"PORT"&lt;/span&gt;
          &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"3000"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;

      &lt;span class="nx"&gt;secrets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;name&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"DATABASE_URL"&lt;/span&gt;
          &lt;span class="nx"&gt;valueFrom&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:secretsmanager:us-east-1:745812456855:secret:prod/database-url-AbCdEf"&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;name&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"API_KEY"&lt;/span&gt;
          &lt;span class="nx"&gt;valueFrom&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:secretsmanager:us-east-1:745812456855:secret:prod/api-key-XyZ123"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;

      &lt;span class="c1"&gt;# Security configurations&lt;/span&gt;
      &lt;span class="nx"&gt;readonlyRootFilesystem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

      &lt;span class="nx"&gt;linuxParameters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;capabilities&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;drop&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ALL"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
          &lt;span class="nx"&gt;add&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"NET_BIND_SERVICE"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nx"&gt;healthCheck&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;command&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"CMD-SHELL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"curl -f http://localhost:3000/health || exit 1"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nx"&gt;interval&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
        &lt;span class="nx"&gt;timeout&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
        &lt;span class="nx"&gt;retries&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
        &lt;span class="nx"&gt;startPeriod&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"production"&lt;/span&gt;
    &lt;span class="nx"&gt;Service&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"api"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The task definition contains the following key features related to security:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;readonlyRootFilesystem&lt;/strong&gt; option restricts write access to the local file system of the container so that it cannot be modified by external entities.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;linuxParameters.capabilities&lt;/strong&gt; allow the user to drop all capabilities by default, and only grant the minimal required access.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;secrets&lt;/strong&gt; parameter should be set to use AWS Secrets Manager instead of environment variables.&lt;/li&gt;
&lt;li&gt;The execution role and the task role should be separate.&lt;/li&gt;
&lt;li&gt;It is recommended that a health check be included as part of the overall reliability of the application.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Encryption in Transit:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ALB still uses HTTPS using ACM certificates.&lt;/li&gt;
&lt;li&gt;All internal service-to-service communications use HTTPS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Encryption at Rest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ECR images will be encrypted using AWS KMS.&lt;/li&gt;
&lt;li&gt;CloudWatch logs will be encrypted.&lt;/li&gt;
&lt;li&gt;EFS volume (if used) will be encrypted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example: Creating encrypted log group:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws logs create-log-group &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--log-group-name&lt;/span&gt; /ecs/production-cluster/api-service &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--kms-key-id&lt;/span&gt; arn:aws:kms:us-east-1:745812456855:key/12345678-4562-4568-5645-745812456855 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Least-Privilege IAM Policies&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the actual task role I used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role"&lt;/span&gt; &lt;span class="s2"&gt;"api_task_role"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"apiServiceTaskRole"&lt;/span&gt;

  &lt;span class="nx"&gt;assume_role_policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&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="s2"&gt;"2012-10-17"&lt;/span&gt;
    &lt;span class="nx"&gt;Statement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sts:AssumeRole"&lt;/span&gt;
        &lt;span class="nx"&gt;Effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
        &lt;span class="nx"&gt;Principal&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;Service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ecs-tasks.amazonaws.com"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role_policy"&lt;/span&gt; &lt;span class="s2"&gt;"api_task_policy"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"apiServiceTaskPolicy"&lt;/span&gt;
  &lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api_task_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&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="s2"&gt;"2012-10-17"&lt;/span&gt;
    &lt;span class="nx"&gt;Statement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;Effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
        &lt;span class="nx"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="s2"&gt;"dynamodb:GetItem"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"dynamodb:PutItem"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"dynamodb:Query"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"dynamodb:UpdateItem"&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nx"&gt;Resource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="s2"&gt;"arn:aws:dynamodb:us-east-1:745812456855:table/users-table"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"arn:aws:dynamodb:us-east-1:745812456855:table/users-table/index/*"&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;Effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
        &lt;span class="nx"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"s3:PutObject"&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nx"&gt;Resource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:s3:::my-app-assets/*"&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;Effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
        &lt;span class="nx"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="s2"&gt;"secretsmanager:GetSecretValue"&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nx"&gt;Resource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="s2"&gt;"arn:aws:secretsmanager:us-east-1:745812456855:secret:prod/database-url-*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"arn:aws:secretsmanager:us-east-1:745812456855:secret:prod/api-key-*"&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a clearer account of what I'm storing in DynamoDB &lt;br&gt;
(the only DynamoDB tables required by this service) and which S3 actions can be performed (only GetObject and PutObject allowed - deletes not included). The other sensitive data I store (secrets) must be secured.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compliance &amp;amp; Monitoring&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Monitoring isn't a one-time event; it requires consistent monitoring so that we can maintain audit trails.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;CloudWatch Logging
Log files from all containers are collected in CloudWatch. The way I configure structured logging is detailed below:
Creating the log group:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws logs create-log-group &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--log-group-name&lt;/span&gt; /ecs/production-cluster/api-service &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--retention-in-days&lt;/span&gt; 30 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Application logging (Node.js example):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;winston&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;winston&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;winston&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createLogger&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;info&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;winston&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;winston&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nx"&gt;winston&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;defaultMeta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;api-service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;transports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;winston&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Usage&lt;/span&gt;
&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User login successful&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="na"&gt;ipAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;203.0.113.42&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; 
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Database connection failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt; 
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;CloudWatch Alarms
I set up alarms for critical metrics:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# High CPU usage alarm&lt;/span&gt;
aws cloudwatch put-metric-alarm &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--alarm-name&lt;/span&gt; api-service-high-cpu &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--alarm-description&lt;/span&gt; &lt;span class="s2"&gt;"Alert when API service CPU &amp;gt; 80%"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--metric-name&lt;/span&gt; CPUUtilization &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--namespace&lt;/span&gt; AWS/ECS &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--statistic&lt;/span&gt; Average &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--period&lt;/span&gt; 300 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--threshold&lt;/span&gt; 80 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--comparison-operator&lt;/span&gt; GreaterThanThreshold &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--evaluation-periods&lt;/span&gt; 2 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--dimensions&lt;/span&gt; &lt;span class="nv"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ServiceName,Value&lt;span class="o"&gt;=&lt;/span&gt;api-service &lt;span class="nv"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ClusterName,Value&lt;span class="o"&gt;=&lt;/span&gt;production-cluster &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--alarm-actions&lt;/span&gt; arn:aws:sns:us-east-1:745812456855:ops-alerts

&lt;span class="c"&gt;# Failed task alarm&lt;/span&gt;
aws cloudwatch put-metric-alarm &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--alarm-name&lt;/span&gt; api-service-task-failures &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--alarm-description&lt;/span&gt; &lt;span class="s2"&gt;"Alert on task failures"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--metric-name&lt;/span&gt; TaskFailures &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--namespace&lt;/span&gt; ECS/ContainerInsights &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--statistic&lt;/span&gt; Sum &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--period&lt;/span&gt; 60 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--threshold&lt;/span&gt; 1 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--comparison-operator&lt;/span&gt; GreaterThanThreshold &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--evaluation-periods&lt;/span&gt; 1 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--dimensions&lt;/span&gt; &lt;span class="nv"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ServiceName,Value&lt;span class="o"&gt;=&lt;/span&gt;api-service &lt;span class="nv"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ClusterName,Value&lt;span class="o"&gt;=&lt;/span&gt;production-cluster &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--alarm-actions&lt;/span&gt; arn:aws:sns:us-east-1:745812456855:ops-alerts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Amazon Inspector Integration&lt;/strong&gt;&lt;br&gt;
Inspector provides continuous runtime vulnerability scanning. Here's how I enabled it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Enable Inspector for ECR and EC2&lt;/span&gt;
aws inspector2 &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--resource-types&lt;/span&gt; ECR EC2 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1

&lt;span class="c"&gt;# Check Inspector findings&lt;/span&gt;
aws inspector2 list-findings &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--filter-criteria&lt;/span&gt; &lt;span class="s1"&gt;'{
      "ecrImageRepositoryName": [{"comparison": "EQUALS", "value": "production-api"}],
      "severity": [{"comparison": "EQUALS", "value": "CRITICAL"}]
    }'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The inspector provides you with the following benefits: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Continuous scanning of all of your running containers;.&lt;/li&gt;
&lt;li&gt;detection of CVEs in both your operating system packages and your application dependencies.&lt;/li&gt;
&lt;li&gt;Risk scores to prioritize your scan and find the most urgent risks; integration with AWS Security Hub.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Log Query Analysis
I use CloudWatch Insights to monitor security-relevant events, and I have created a query that identifies all failed authentication attempts.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fields @timestamp, userId, ipAddress, @message
| filter @message like /authentication failed/
| sort @timestamp desc
| limit 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The query for the error rate by service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fields @timestamp, service, @message
| filter level = "error"
| stats count() by service
| sort count() desc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Compliance Reporting
For compliance requirements (SOC 2, ISO 27001), I export logs to S3 for long-term retention:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws logs create-export-task &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--log-group-name&lt;/span&gt; /ecs/production-cluster/api-service &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--from&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'30 days ago'&lt;/span&gt; +%s&lt;span class="si"&gt;)&lt;/span&gt;000 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--to&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s&lt;span class="si"&gt;)&lt;/span&gt;000 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--destination&lt;/span&gt; s3-compliance-logs-bucket &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--destination-prefix&lt;/span&gt; ecs-logs/api-service/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example of Steps: Complete Process&lt;/p&gt;

&lt;p&gt;I want to demonstrate how to deploy (create) a completely new microservice while implementing all security features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Create the Microservice&lt;/strong&gt;&lt;br&gt;
Project Layout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;notification-service/
├── src/
│   ├── index.js
│   ├── handlers/
│   └── utils/
├── Dockerfile
├── package.json
└── .github/
    └── workflows/
        └── deploy.yml

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

&lt;/div&gt;



&lt;p&gt;Example Docker file (There is a link to the Docker file that is similar to notification-service/dockerfile on GitHub)&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="c"&gt;# Build stage&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:18-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Copy package files&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;

&lt;span class="c"&gt;# Install dependencies&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production

&lt;span class="c"&gt;# Copy application code&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="c"&gt;# Build if needed (TypeScript, etc.)&lt;/span&gt;
&lt;span class="c"&gt;# RUN npm run build&lt;/span&gt;

&lt;span class="c"&gt;# Production stage&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18-alpine&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Install security updates&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apk upgrade &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; dumb-init &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/cache/apk/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="c"&gt;# Create non-root user&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;-g&lt;/span&gt; 1001 &lt;span class="nt"&gt;-S&lt;/span&gt; appgroup &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    adduser &lt;span class="nt"&gt;-u&lt;/span&gt; 1001 &lt;span class="nt"&gt;-S&lt;/span&gt; appuser &lt;span class="nt"&gt;-G&lt;/span&gt; appgroup &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; appuser:appgroup /app

&lt;span class="c"&gt;# Copy from builder&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder --chown=appuser:appgroup /app/src ./src&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder --chown=appuser:appgroup /app/package.json ./&lt;/span&gt;

&lt;span class="c"&gt;# Switch to non-root user&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; appuser&lt;/span&gt;

&lt;span class="c"&gt;# Use dumb-init to handle signals properly&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["dumb-init", "--"]&lt;/span&gt;

&lt;span class="c"&gt;# Health check&lt;/span&gt;
&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=30s --timeout=3s --start-period=40s --retries=3 \&lt;/span&gt;
    CMD node -e "require('http').get('http://localhost:3000/health', (r) =&amp;gt; {process.exit(r.statusCode === 200 ? 0 : 1)})"

# Expose port
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;

&lt;span class="c"&gt;# Run application&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "src/index.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Application code (src/index.js):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;winston&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;winston&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Configure logger&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;winston&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createLogger&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;info&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;winston&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;defaultMeta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;notification-service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;transports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;winston&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Configure AWS SDK&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SNS&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;us-east-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secretsManager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SecretsManager&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;us-east-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// Health check endpoint&lt;/span&gt;
&lt;span class="nx"&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="s1"&gt;/health&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&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="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;healthy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Send notification endpoint&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/notify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;topic&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sending notification&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;messageLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;TopicArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`arn:aws:sns:us-east-1:745812456855:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Notification sent successfully&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;topic&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&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="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Notification sent&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="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed to send notification&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
      &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt; 
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed to send notification&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Graceful shutdown&lt;/span&gt;
&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SIGTERM&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SIGTERM received, shutting down gracefully&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Notification service listening on port &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Create ECR Repository&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create the repository&lt;/span&gt;
aws ecr create-repository &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--repository-name&lt;/span&gt; notification-service &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--image-scanning-configuration&lt;/span&gt; &lt;span class="nv"&gt;scanOnPush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--encryption-configuration&lt;/span&gt; &lt;span class="nv"&gt;encryptionType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;KMS,kmsKey&lt;span class="o"&gt;=&lt;/span&gt;arn:aws:kms:us-east-1:745812456855:key/12345678-1234-1234-1234-745812456855 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1

&lt;span class="c"&gt;# Set lifecycle policy to keep only last 10 images&lt;/span&gt;
aws ecr put-lifecycle-policy &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--repository-name&lt;/span&gt; notification-service &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--lifecycle-policy-text&lt;/span&gt; &lt;span class="s1"&gt;'{
      "rules": [
        {
          "rulePriority": 1,
          "description": "Keep last 10 images",
          "selection": {
            "tagStatus": "any",
            "countType": "imageCountMoreThan",
            "countNumber": 10
          },
          "action": {
            "type": "expire"
          }
        }
      ]
    }'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Expected output:&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;"repository"&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;"repositoryArn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:ecr:us-east-1:745812456855:repository/notification-service"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"registryId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"745812456855"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"repositoryName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"notification-service"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"repositoryUri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"745812456855.dkr.ecr.us-east-1.amazonaws.com/notification-service"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"createdAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-12-15T10:30:00.000000+00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"imageScanningConfiguration"&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;"scanOnPush"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"encryptionConfiguration"&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;"encryptionType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"KMS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"kmsKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:kms:us-east-1:745812456855:key/12345678-1234-1234-1234-745812456855"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3: Build and Push Image Manually (First Time)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Authenticate Docker to ECR&lt;/span&gt;
aws ecr get-login-password &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1 | &lt;span class="se"&gt;\&lt;/span&gt;
    docker login &lt;span class="nt"&gt;--username&lt;/span&gt; AWS &lt;span class="nt"&gt;--password-stdin&lt;/span&gt; 745812456855.dkr.ecr.us-east-1.amazonaws.com

&lt;span class="c"&gt;# Build the image&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; notification-service:v1.0.0 &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Tag for ECR&lt;/span&gt;
docker tag notification-service:v1.0.0 &lt;span class="se"&gt;\&lt;/span&gt;
    745812456855.dkr.ecr.us-east-1.amazonaws.com/notification-service:v1.0.0

docker tag notification-service:v1.0.0 &lt;span class="se"&gt;\&lt;/span&gt;
    745812456855.dkr.ecr.us-east-1.amazonaws.com/notification-service:latest

&lt;span class="c"&gt;# Push to ECR&lt;/span&gt;
docker push 745812456855.dkr.ecr.us-east-1.amazonaws.com/notification-service:v1.0.0
docker push 745812456855.dkr.ecr.us-east-1.amazonaws.com/notification-service:latest

&lt;span class="c"&gt;# Wait for scan to complete&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Waiting for image scan..."&lt;/span&gt;
&lt;span class="nb"&gt;sleep &lt;/span&gt;30

&lt;span class="c"&gt;# Check scan results&lt;/span&gt;
aws ecr describe-image-scan-findings &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--repository-name&lt;/span&gt; notification-service &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--image-id&lt;/span&gt; &lt;span class="nv"&gt;imageTag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;v1.0.0 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'imageScanFindings.findingSeverityCounts'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output of scan:&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;"MEDIUM"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"LOW"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"INFORMATIONAL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&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;There are no any critical or high vulnerabilities, so safe to deploy!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Create IAM Roles&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Task execution role (same for all services):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create trust policy&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; trust-policy.json &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ecs-tasks.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="c"&gt;# Create role&lt;/span&gt;
aws iam create-role &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--role-name&lt;/span&gt; ecsTaskExecutionRole &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--assume-role-policy-document&lt;/span&gt; file://trust-policy.json

&lt;span class="c"&gt;# Attach AWS managed policy&lt;/span&gt;
aws iam attach-role-policy &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--role-name&lt;/span&gt; ecsTaskExecutionRole &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--policy-arn&lt;/span&gt; arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Task role (specific to notification service):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create task role&lt;/span&gt;
aws iam create-role &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--role-name&lt;/span&gt; notificationServiceTaskRole &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--assume-role-policy-document&lt;/span&gt; file://trust-policy.json

&lt;span class="c"&gt;# Create custom policy&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; notification-policy.json &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "sns:Publish"
      ],
      "Resource": [
        "arn:aws:sns:us-east-1:745812456855:app-notifications",
        "arn:aws:sns:us-east-1:745812456855:alert-notifications"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "secretsmanager:GetSecretValue"
      ],
      "Resource": [
        "arn:aws:secretsmanager:us-east-1:745812456855:secret:prod/notification-config-*"
      ]
    }
  ]
}
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="c"&gt;# Attach policy&lt;/span&gt;
aws iam put-role-policy &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--role-name&lt;/span&gt; notificationServiceTaskRole &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--policy-name&lt;/span&gt; NotificationServicePolicy &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--policy-document&lt;/span&gt; file://notification-policy.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 5: Create Task Definition&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create task definition JSON&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; task-definition.json &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
{
  "family": "notification-service-task",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "256",
  "memory": "512",
  "executionRoleArn": "arn:aws:iam::745812456855:role/ecsTaskExecutionRole",
  "taskRoleArn": "arn:aws:iam::745812456855:role/notificationServiceTaskRole",
  "containerDefinitions": [
    {
      "name": "notification-container",
      "image": "745812456855.dkr.ecr.us-east-1.amazonaws.com/notification-service:latest",
      "essential": true,
      "portMappings": [
        {
          "containerPort": 3000,
          "protocol": "tcp"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/production-cluster/notification-service",
          "awslogs-region": "us-east-1",
          "awslogs-stream-prefix": "ecs"
        }
      },
      "environment": [
        {
          "name": "NODE_ENV",
          "value": "production"
        },
        {
          "name": "PORT",
          "value": "3000"
        }
      ],
      "secrets": [
        {
          "name": "NOTIFICATION_CONFIG",
          "valueFrom": "arn:aws:secretsmanager:us-east-1:745812456855:secret:prod/notification-config-AbCdEf"
        }
      ],
      "readonlyRootFilesystem": false,
      "linuxParameters": {
        "capabilities": {
          "drop": ["ALL"],
          "add": ["NET_BIND_SERVICE"]
        }
      },
      "healthCheck": {
        "command": ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"],
        "interval": 30,
        "timeout": 5,
        "retries": 3,
        "startPeriod": 60
      }
    }
  ]
}
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="c"&gt;# Register task definition&lt;/span&gt;
aws ecs register-task-definition &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--cli-input-json&lt;/span&gt; file://task-definition.json &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 6: Create ECS Service&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create CloudWatch log group first&lt;/span&gt;
aws logs create-log-group &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--log-group-name&lt;/span&gt; /ecs/production-cluster/notification-service &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1

&lt;span class="c"&gt;# Create the ECS service&lt;/span&gt;
aws ecs create-service &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--cluster&lt;/span&gt; production-cluster &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--service-name&lt;/span&gt; notification-service &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--task-definition&lt;/span&gt; notification-service-task &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--desired-count&lt;/span&gt; 2 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--launch-type&lt;/span&gt; FARGATE &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--platform-version&lt;/span&gt; LATEST &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--network-configuration&lt;/span&gt; &lt;span class="s2"&gt;"awsvpcConfiguration={
        subnets=[subnet-0a1b2c3d,subnet-0e1f2g3h],
        securityGroups=[sg-0a1b2c3d],
        assignPublicIp=DISABLED
    }"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--load-balancers&lt;/span&gt; &lt;span class="s2"&gt;"targetGroupArn=arn:aws:elasticloadbalancing:us-east-1:745812456855:targetgroup/notification-tg/745812456855,containerName=notification-container,containerPort=3000"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--health-check-grace-period-seconds&lt;/span&gt; 60 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--deployment-configuration&lt;/span&gt; &lt;span class="s2"&gt;"maximumPercent=200,minimumHealthyPercent=100,deploymentCircuitBreaker={enable=true,rollback=true}"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--enable-execute-command&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Important Deployment Configuration Options&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;2 - The number of tasks that are running at any given time to ensure maximum uptime for the application (high availability).&lt;br&gt;
DISABLED - To indicate that this task will be running in the private subnets of this VPC.&lt;br&gt;
deploymentCircuitBreaker - To automatically roll back any failed deployments.&lt;br&gt;
enable-execute-command - To allow the use of ECS Exec to assist in debugging your application (must include an appropriate IAM role).&lt;/p&gt;

&lt;p&gt;** Step 7: ** Create a .github/workflows/deploy.yml file (as illustrated above) for this notification-service project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy Notification Service&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="nv"&gt;main&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;notification-service/**'&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;AWS_REGION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;us-east-1&lt;/span&gt;
  &lt;span class="na"&gt;ECR_REPOSITORY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;notification-service&lt;/span&gt;
  &lt;span class="na"&gt;ECS_CLUSTER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production-cluster&lt;/span&gt;
  &lt;span class="na"&gt;ECS_SERVICE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;notification-service&lt;/span&gt;
  &lt;span class="na"&gt;CONTAINER_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;notification-container&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build, Scan, and Deploy&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;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@v2&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;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;${{ env.AWS_REGION }}&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Login to Amazon ECR&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;login-ecr&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/amazon-ecr-login@v1&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build, scan, and push image&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build-image&lt;/span&gt;
      &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ECR_REGISTRY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.login-ecr.outputs.registry }}&lt;/span&gt;
        &lt;span class="na"&gt;IMAGE_TAG&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.sha }}&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;cd notification-service&lt;/span&gt;
        &lt;span class="s"&gt;docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .&lt;/span&gt;
        &lt;span class="s"&gt;docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG&lt;/span&gt;

        &lt;span class="s"&gt;# Wait for scan&lt;/span&gt;
        &lt;span class="s"&gt;sleep 30&lt;/span&gt;

        &lt;span class="s"&gt;# Check vulnerabilities&lt;/span&gt;
        &lt;span class="s"&gt;CRITICAL=$(aws ecr describe-image-scan-findings \&lt;/span&gt;
          &lt;span class="s"&gt;--repository-name $ECR_REPOSITORY \&lt;/span&gt;
          &lt;span class="s"&gt;--image-id imageTag=$IMAGE_TAG \&lt;/span&gt;
          &lt;span class="s"&gt;--query 'imageScanFindings.findingSeverityCounts.CRITICAL' \&lt;/span&gt;
          &lt;span class="s"&gt;--output text 2&amp;gt;/dev/null || echo "0")&lt;/span&gt;

        &lt;span class="s"&gt;if [ "$CRITICAL" != "0" ] &amp;amp;&amp;amp; [ "$CRITICAL" != "None" ]; then&lt;/span&gt;
          &lt;span class="s"&gt;echo " CRITICAL vulnerabilities found!"&lt;/span&gt;
          &lt;span class="s"&gt;exit 1&lt;/span&gt;
        &lt;span class="s"&gt;fi&lt;/span&gt;

        &lt;span class="s"&gt;echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" &amp;gt;&amp;gt; $GITHUB_OUTPUT&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 ECS&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/amazon-ecs-deploy-task-definition@v1&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;task-definition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;notification-service/task-definition.json&lt;/span&gt;
        &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.ECS_SERVICE }}&lt;/span&gt;
        &lt;span class="na"&gt;cluster&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.ECS_CLUSTER }}&lt;/span&gt;
        &lt;span class="na"&gt;wait-for-service-stability&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 8: Verify Deployment&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check service status&lt;/span&gt;
aws ecs describe-services &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--cluster&lt;/span&gt; production-cluster &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--services&lt;/span&gt; notification-service &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'services[0].[serviceName,status,runningCount,desiredCount]'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--output&lt;/span&gt; table

&lt;span class="c"&gt;# Check task health&lt;/span&gt;
aws ecs list-tasks &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--cluster&lt;/span&gt; production-cluster &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--service-name&lt;/span&gt; notification-service &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1

&lt;span class="c"&gt;# Get task details&lt;/span&gt;
aws ecs describe-tasks &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--cluster&lt;/span&gt; production-cluster &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--tasks&lt;/span&gt; arn:aws:ecs:us-east-1:745812456855:task/production-cluster/1234567890abcdef &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'tasks[0].[taskArn,lastStatus,healthStatus,containers[0].healthStatus]'&lt;/span&gt;

&lt;span class="c"&gt;# Check logs&lt;/span&gt;
aws logs &lt;span class="nb"&gt;tail&lt;/span&gt; /ecs/production-cluster/notification-service &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--follow&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected healthy output:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service Name&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Desired Tasks&lt;/th&gt;
&lt;th&gt;Running Tasks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;notification-service&lt;/td&gt;
&lt;td&gt;ACTIVE&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Step 9: Test the Service&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Get ALB DNS name&lt;/span&gt;
&lt;span class="nv"&gt;ALB_DNS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws elbv2 describe-load-balancers &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--names&lt;/span&gt; production-alb &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'LoadBalancers[0].DNSName'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--output&lt;/span&gt; text &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Health check&lt;/span&gt;
curl https://&lt;span class="nv"&gt;$ALB_DNS&lt;/span&gt;/health

&lt;span class="c"&gt;# Send test notification&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://&lt;span class="nv"&gt;$ALB_DNS&lt;/span&gt;/notify &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
      "message": "Test notification from secure pipeline",
      "topic": "app-notifications"
    }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Expected response:&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;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Notification sent"&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;Security &amp;amp; Lessons Learned &amp;amp; Best Practices&lt;/p&gt;

&lt;p&gt;From the time we ran this configuration in production for several months, below are my major takeaways:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security Lessons&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Automating Everything or It Doesn't Get Done&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When under pressure, manually performing security checks gets overlooked,&lt;/li&gt;
&lt;li&gt;Automated gates in Continuous Integration/Continuous Deployment (CI/CD) are a must,&lt;/li&gt;
&lt;li&gt;The easiest way to secure everything is to make it the easiest thing to do because making something insecure will almost always be easier.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Some Vulnerabilities Are Worse Than Others&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CRITICAL/HIGH: block deployment right away&lt;/li&gt;
&lt;li&gt;MEDIUM: accept and track with ticket&lt;/li&gt;
&lt;li&gt;LOW/INFORMATIONAL: trend monitoring.&lt;/li&gt;
&lt;li&gt;Context is KEY here: if you have a SQL Injection CVE in a package that your environment does NOT use, your response will be quite different from that of the SQL Injection in your Web Framework.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Utilizing Defense-in-Depth Is Beneficial&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple layers of security caught issues: ECR Scan caught OS vulnerabilities, Trivy was able to catch application dependencies within an organization, and Inspector caught runtime issues.&lt;/li&gt;
&lt;li&gt;Network isolation limited lateral movement by exploiting a single security incident.&lt;/li&gt;
&lt;li&gt;IAM least-privilege limited the blast radius on a credential leak situation. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Secrets Management Is Important&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Never keep secrets in environment variables (you can view them in logs, console, etc.)&lt;/li&gt;
&lt;li&gt;AWS Secrets Manager integration with ECS is straightforward; rotating secrets regularly (e.g., every 90 days) is best practice.
&lt;strong&gt;5. Operational Lessons&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;** Read-Only File Systems can create issues for Applications**&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Many applications rely upon writing to /tmp.&lt;/li&gt;
&lt;li&gt;Therefore, it is recommended that you mount tmpfs volumes to isolate temporary data.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"mountPoints"&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;"sourceVolume"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tmp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"containerPath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/tmp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"readOnly"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&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="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"volumes"&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;"tmp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6. The Importance of Health Checks&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implement proper health checks (a response code of '200' isn't a true indicator of health). &lt;/li&gt;
&lt;li&gt;Check the database for connectivity and external service health.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;7. Considerations for Logging&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A structured logging format (such as JSON) allows for easier search and analysis of logs,the usage of trace IDs provides a means to correlate requests between systems, application logs should be separated from access logs, and use retention policies for logs (we maintain our application logs in CloudWatch for 30 days and for 7 years in S3).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;8. Cost-Effective Practices&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ECR image scanning is free the first time it is pushed to the ECR repository on a given day, however if enhanced image scanning is performed it costs $0.09 per image/month (this is well worth the cost), CloudWatch Logs Insights query charges (the cost of running these queries) will eat up your costs so caching is helpful when running frequently used query patterns, Fargate Spot pricing provides an opportunity to save 70% for workloads that are not time-critical.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Lessons Learned in CI/CD&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;9. Deployment Speed versus Security&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The CI/CD pipeline takes between 8-12 minutes (build: 3m, scan: 2m, deploy: 5m) this is acceptable for production systems however, it is not acceptable to sacrifice security for speed. To accommodate faster iteration times, take advantage of dev environments with relaxed security policies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10. Rollback Planning&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a deployment fails the ECS circuit breaker will auto roll back the deployment, always retain previous versions of the task definitions, use Blue/Green deployments for critical services, and always test your rollback plans in advance&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned on Architecture
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;11. Isolated Services:&lt;/strong&gt;&lt;br&gt;
 Each microservice has its own ECR repository.&lt;br&gt;
Each service has its unique IAM Task Role.&lt;br&gt;
Services communicate using ALB/API Gateway only.&lt;br&gt;
This limits the potential effects of a compromised service on other services.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;12. Monitoring is Mandatory:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An Alarm should be set before incidents occur.&lt;/li&gt;
&lt;li&gt;Alerts should be generated for security events such as unusual traffic, failed authentication attempts...&lt;/li&gt;
&lt;li&gt;Weekly review meetings for security findings.&lt;/li&gt;
&lt;li&gt;AWS Security Hub is used to provide a centralised monitoring service.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;My Ten Best Security Practices&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Your Practice: How Is It Applied?&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Scan for vulnerabilities on each push and catch issues when they start.
Use the ECR scan-on push feature and GitHub actions integrated into your existing DevOps pipeline.&lt;/li&gt;
&lt;li&gt;Set up automated scans to catch high-severity vulnerabilities in the continuous integration/continuous deployment process. &lt;/li&gt;
&lt;li&gt;Limit the ability of services to cause harm if compromised. Create separate task roles for each service for least privilege IAM practices.&lt;/li&gt;
&lt;li&gt;Reduce the attack surface by placing services in private subnets with no public IP addresses and utilizing NAT gateways for outbound internet access.&lt;/li&gt;
&lt;li&gt;Use non-root container images to prevent privilege escalation. Use a user ID of 1001 in your Dockerfiles.&lt;/li&gt;
&lt;li&gt;Use immutable infrastructure (e.g., Fargate) for consistent infrastructure that is auditable and reproducible. This includes using task definitions in Git.&lt;/li&gt;
&lt;li&gt;Encrypt all data at rest and in transit using KMS (Key Management Service) for protected Amazon ECR repositories, logs, and secrets.&lt;/li&gt;
&lt;li&gt;Enable Structured JSON logs (with trace IDs) to allow you to investigate more thoroughly when a security incident occurs. &lt;/li&gt;
&lt;li&gt;Use CloudWatch alarms and AWS Inspector to enable quick reporting and to monitor your environment.&lt;/li&gt;
&lt;li&gt;Stay ahead of the current threat landscape by using automated tools for dependency management (such as Dependabot) and by updating base container images through a regular monthly schedule.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;What I did wrong, For You To Avoid Making The Same Mistakes&lt;/strong&gt; &lt;br&gt;
Assigned each service equal IAM role access to everything - If a service gets compromised, it gains access to everything. Fixed this issue by creating IAM roles per service.&lt;br&gt;
 Did not set log retention amount of time - The CloudWatch Logs bill was unbelievable. Therefore, I set a 30-day retention period for all log groups.&lt;br&gt;
 Did not consider image layer caching - My initial Dockerfiles took 15 minutes to build. So, I changed the order of COPY commands around to optimize the cache layers.&lt;br&gt;
Deployed to Production from a local computer one time - I started to think, "just this one time". This eventually led to a pattern. We enforce all production deployments through GitHub Actions.&lt;br&gt;
I ignored my MEDIUM vulnerabilities - They accumulated to over 200. Now, we are tracking and solving these quarterly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In summary&lt;/strong&gt;&lt;br&gt;
When I first began this project, I anticipated that security would be measures to the speed of our work. I was incorrect.&lt;br&gt;
The implementation of Automatic Container Security has actually improved the speed of deployment for us:&lt;br&gt;
We were able to eliminate the "security review" bottlenecks.&lt;br&gt;
Our Development Team was able to find problems in CI before they made their way to production.&lt;br&gt;
The overall number of incidents that required solutions decreased and therefore we spent less time on development.&lt;br&gt;
Compliance audits have become easy because we have access to all of the necessary logs, scans, and configurations in documented form.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;By The Numbers:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the past 6 months, we have experienced Zero production security incidents that were caused by vulnerabilities in containers.&lt;br&gt;
Our average deployment time (including all security checks) is 8 minutes.&lt;br&gt;
100% of Images were scanned for vulnerabilities prior to deployment.&lt;br&gt;
Average 15 minutes / Deployment Saved because there was no manual review needed.&lt;br&gt;
2 hours a week were saved due to the elimination of a need for security meetings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advice I would give my former self:&lt;/strong&gt;&lt;br&gt;
When you start a project like this, you must begin with security in mind.&lt;br&gt;
It is far more difficult to ,manage security than it is to build security in from the beginning.&lt;br&gt;
AWS has fantastic tools (ECR Scanning, Fargate Isolation, IAM) that do all of the "heavy lifting" for you; you just have to properly configure and integrate them.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Resources That Helped Me&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/bestpracticesguide/security.html" rel="noopener noreferrer"&gt;AWS ECS Security Best Practices&lt;br&gt;
&lt;/a&gt;&lt;a href="https://docs.aws.amazon.com/AmazonECR/latest/userguide/image-scanning.html" rel="noopener noreferrer"&gt;ECR Image Scanning Documentation&lt;/a&gt;&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/AWS_Fargate.html" rel="noopener noreferrer"&gt;Fargate Security Guide&lt;br&gt;
&lt;/a&gt;&lt;a href="https://github.com/aws-actions" rel="noopener noreferrer"&gt;GitHub Actions for AWS&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.oreilly.com/library/view/container-security/9781492056690/" rel="noopener noreferrer"&gt;Container Security by Liz Rice&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Automating security makes it almost transparent to most developers while providing security for your production infrastructure.&lt;br&gt;
By using AWS Fargate and GitHub Actions, you can build a secure pipeline as part of your deployment process without having to manage that infrastructure yourself.&lt;/p&gt;

&lt;p&gt;After following this guide, you can also create an automated container security pipeline. You can use these examples of code to get started and customize them as necessary.&lt;/p&gt;

&lt;p&gt;If you have any questions, comments or war stories to share regarding this guide, I would love to hear from you! You can post a comment here or connect with me on social media. Together we can work towards providing a more secure AWS ecosystem! &lt;/p&gt;

&lt;h2&gt;
  
  
  Connect with me
&lt;/h2&gt;

&lt;p&gt;• Portfolio: &lt;a href="https://khanalsamir.com" rel="noopener noreferrer"&gt;https://khanalsamir.com&lt;/a&gt;&lt;br&gt;&lt;br&gt;
• GitHub: &lt;a href="https://github.com/Shawmeer" rel="noopener noreferrer"&gt;https://github.com/Shawmeer&lt;/a&gt;&lt;br&gt;&lt;br&gt;
• LinkedIn: &lt;a href="https://linkedin.com/in/samir-khanal" rel="noopener noreferrer"&gt;https://linkedin.com/in/samir-khanal&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;If you're working on similar DevOps or AWS projects, feel free to connect. I regularly share practical cloud and CI/CD content.&lt;/p&gt;

</description>
      <category>ecs</category>
      <category>containers</category>
      <category>aws</category>
      <category>community</category>
    </item>
    <item>
      <title>Building Production-Grade Microservices on AWS ECS Fargate with GitLab CI/CD Automation</title>
      <dc:creator>Samir Khanal</dc:creator>
      <pubDate>Fri, 12 Dec 2025 07:17:50 +0000</pubDate>
      <link>https://dev.to/shawmeer22/building-production-grade-microservices-on-aws-ecs-fargate-with-gitlab-cicd-automation-4n46</link>
      <guid>https://dev.to/shawmeer22/building-production-grade-microservices-on-aws-ecs-fargate-with-gitlab-cicd-automation-4n46</guid>
      <description>&lt;p&gt;&lt;strong&gt;1. Introduction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Nowadays, applications are moving towards microservices because of their scalability, rollback deployment, and faster development. &lt;br&gt;
But with the growing number of services, deployment pipelines have become:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slow (like updating all services on every commit)&lt;/li&gt;
&lt;li&gt;Hard to manage (multiple pipelines for a single service)&lt;/li&gt;
&lt;li&gt;This was causing problems (manual deployments, Docker images being inconsistent, etc.).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I combined &lt;strong&gt;AWS Elastic Container Service (ECS) Fargate&lt;/strong&gt; and &lt;strong&gt;Amazon ECR,&lt;/strong&gt; combined with &lt;strong&gt;GitLab CI/CD&lt;/strong&gt;, which made the pipeline deploy only the service which was changed.&lt;/p&gt;

&lt;p&gt;In this guide, I have created an actual production-ready microservices deployment system with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ECS Fargate&lt;/strong&gt; for serverless containers&lt;/li&gt;
&lt;li&gt;Five microservices:
&lt;strong&gt;billing&lt;/strong&gt;, &lt;strong&gt;client_app_api&lt;/strong&gt;, &lt;strong&gt;driver_api&lt;/strong&gt;, &lt;strong&gt;odmu_auth&lt;/strong&gt;, &lt;strong&gt;odmu_contractual&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon ECR&lt;/strong&gt; as an image registry for containers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ALB (Application Load Balancer)&lt;/strong&gt; with multiple target groups&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;GitLab CI/CD&lt;/strong&gt; with folder based triggers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terraform&lt;/strong&gt; for infrastructure as code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudWatch&lt;/strong&gt; for monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end, you’ll have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; Independent deployments per microservice&lt;/li&gt;
&lt;li&gt; Folder-based Automated CI/CD&lt;/li&gt;
&lt;li&gt; Production grade ECS services&lt;/li&gt;
&lt;li&gt; Scalable, secure, monitored architecture&lt;/li&gt;
&lt;li&gt; No manual SSH or PM2&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Real Problem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When I was previously working with ECS, every change in code triggered a full redeployment of all services, although the change was in one service folder. It caused unnecessary downtime and more deployment times, and rollback was risky. I also faced Docker image drift in various environments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I found a solution&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only the service that changes is rebuilt and deployed&lt;/li&gt;
&lt;li&gt;Each service remains fully isolated&lt;/li&gt;
&lt;li&gt;Deployments are repeatable and predictable&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  2. ARCHITECTURE:
&lt;/h2&gt;

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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ECS Fargate&lt;/td&gt;
&lt;td&gt;Serverless containers (no EC2 management)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ECR&lt;/td&gt;
&lt;td&gt;Private container registry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ALB&lt;/td&gt;
&lt;td&gt;Routes incoming traffic to the right microservice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CloudWatch&lt;/td&gt;
&lt;td&gt;Logs and metrics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terraform&lt;/td&gt;
&lt;td&gt;Infrastructure provisioning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitLab CI/CD&lt;/td&gt;
&lt;td&gt;Automated container build and deployment&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Why &lt;strong&gt;ECS Fargate&lt;/strong&gt;?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No EC2 provisioning or patching&lt;/li&gt;
&lt;li&gt;Auto-scaling built-in&lt;/li&gt;
&lt;li&gt;Secure &lt;/li&gt;
&lt;li&gt;Suits for microservices&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  3. Microservices Structure
&lt;/h2&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services/
 ├── billing/
 ├── client_app_api/
 ├── driver_api/
 ├── odmu_auth/
 └── odmu_contractual/
.gitlab-ci.yml
terraform/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each microservice has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Its own Dockerfile&lt;/li&gt;
&lt;li&gt;Its own business logic&lt;/li&gt;
&lt;li&gt;Its own task definition&lt;/li&gt;
&lt;li&gt;Its own ECS service&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Dockerizing the Microservices
&lt;/h2&gt;

&lt;p&gt;Example Dockerfile for client_app_api:&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; node:20-alpine&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; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I used different ports for each microservice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Service              Port
billing              3001
client_app_api       3000
driver_api           3002
odmu_auth            3003
odmu_contractual     3004
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Creating ECR Repositories
&lt;/h2&gt;

&lt;p&gt;Account ID: 745812456855&lt;br&gt;
Region: us-east-1&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws ecr create-repository &lt;span class="nt"&gt;--repository-name&lt;/span&gt; billing
aws ecr create-repository &lt;span class="nt"&gt;--repository-name&lt;/span&gt; client_app_api
aws ecr create-repository &lt;span class="nt"&gt;--repository-name&lt;/span&gt; driver_api
aws ecr create-repository &lt;span class="nt"&gt;--repository-name&lt;/span&gt; odmu_auth
aws ecr create-repository &lt;span class="nt"&gt;--repository-name&lt;/span&gt; odmu_contractual


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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Build and Push (example: billing)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; billing ./services/billing

docker tag billing:latest &lt;span class="se"&gt;\&lt;/span&gt;
  745812456855.dkr.ecr.us-east-1.amazonaws.com/billing:latest

aws ecr get-login-password &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1 &lt;span class="se"&gt;\&lt;/span&gt;
  | docker login &lt;span class="nt"&gt;--username&lt;/span&gt; AWS &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--password-stdin&lt;/span&gt; 745812456855.dkr.ecr.us-east-1.amazonaws.com

docker push &lt;span class="se"&gt;\&lt;/span&gt;
  745812456855.dkr.ecr.us-east-1.amazonaws.com/billing:latest

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. ECS Fargate: Task Definition (Terraform)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Example for the billing microservice:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ecs_task_definition"&lt;/span&gt; &lt;span class="s2"&gt;"billing"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;family&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"billing-task"&lt;/span&gt;
  &lt;span class="nx"&gt;network_mode&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"awsvpc"&lt;/span&gt;
  &lt;span class="nx"&gt;requires_compatibilities&lt;/span&gt; &lt;span class="p"&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="nx"&gt;cpu&lt;/span&gt;                      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"512"&lt;/span&gt;
  &lt;span class="nx"&gt;memory&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1024"&lt;/span&gt;

  &lt;span class="nx"&gt;execution_role_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:iam::745812456855:role/ecsTaskExecutionRole"&lt;/span&gt;

  &lt;span class="nx"&gt;container_definitions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"billing"&lt;/span&gt;
      &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"745812456855.dkr.ecr.us-east-1.amazonaws.com/billing:latest"&lt;/span&gt;

      &lt;span class="nx"&gt;portMappings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="nx"&gt;containerPort&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3001&lt;/span&gt;
        &lt;span class="nx"&gt;protocol&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
      &lt;span class="p"&gt;}]&lt;/span&gt;

      &lt;span class="nx"&gt;essential&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similar type for all microservices.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Application Load Balancer Routing
&lt;/h2&gt;

&lt;p&gt;Each microservice has a separate target group:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Target Group Name&lt;/th&gt;
&lt;th&gt;Path Pattern&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;billing&lt;/td&gt;
&lt;td&gt;tg-billing&lt;/td&gt;
&lt;td&gt;/billing/*&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;client_app_api&lt;/td&gt;
&lt;td&gt;tg-client&lt;/td&gt;
&lt;td&gt;/client/*&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;driver_api&lt;/td&gt;
&lt;td&gt;tg-driver&lt;/td&gt;
&lt;td&gt;/driver/*&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;odmu_auth&lt;/td&gt;
&lt;td&gt;tg-auth&lt;/td&gt;
&lt;td&gt;/auth/*&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;odmu_contractual&lt;/td&gt;
&lt;td&gt;tg-contract&lt;/td&gt;
&lt;td&gt;/contract/*&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Security Considerations
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Security was a first-class concern in this design&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each ECS task runs with VPC networking. Each ECS task has ENI-level network isolation.&lt;/li&gt;
&lt;li&gt;Containers cannot be accessed via SSH; therefore, all deployments take place through CI/CD.&lt;/li&gt;
&lt;li&gt;IAM task execution roles are created following the principle of least privilege.&lt;/li&gt;
&lt;li&gt;All container images are stored in private ECR repositories on Amazon.&lt;/li&gt;
&lt;li&gt;The ALB serves as the only public access point to ECS, and all services remain in a private subnet inside the VPC.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lb_target_group"&lt;/span&gt; &lt;span class="s2"&gt;"billing"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tg-billing"&lt;/span&gt;
  &lt;span class="nx"&gt;port&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3001&lt;/span&gt;
  &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTTP"&lt;/span&gt;
  &lt;span class="nx"&gt;target_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ip"&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;GitLab CI/CD: Folder-Based Deployments
This is the most important section.
Instead of running one giant pipeline or manually using SSH/PM2, I configured the GitLab yml file to only deploy the service that changed.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;

&lt;span class="c1"&gt;# ----------------------------&lt;/span&gt;
&lt;span class="c1"&gt;# Billing Microservice&lt;/span&gt;
&lt;span class="c1"&gt;# ----------------------------&lt;/span&gt;
&lt;span class="na"&gt;build_billing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
  &lt;span class="na"&gt;only&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;changes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;services/billing/**&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;docker build -t billing ./services/billing&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;docker tag billing:latest 745812456855.dkr.ecr.us-east-1.amazonaws.com/billing:latest&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 745812456855.dkr.ecr.us-east-1.amazonaws.com&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;docker push 745812456855.dkr.ecr.us-east-1.amazonaws.com/billing:latest&lt;/span&gt;

&lt;span class="na"&gt;deploy_billing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
  &lt;span class="na"&gt;only&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;changes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;services/billing/**&lt;/span&gt;
  &lt;span class="na"&gt;script&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 ecs update-service \&lt;/span&gt;
        &lt;span class="s"&gt;--cluster production-cluster \&lt;/span&gt;
        &lt;span class="s"&gt;--service billing-service \&lt;/span&gt;
        &lt;span class="s"&gt;--force-new-deployment&lt;/span&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  9. Monitoring &amp;amp; Observability (CloudWatch)
&lt;/h2&gt;

&lt;p&gt;Logs appear under:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/ecs/billing
/ecs/client_app_api
/ecs/driver_api
/ecs/odmu_auth
/ecs/odmu_contractual
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create log groups:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws logs create-log-group &lt;span class="nt"&gt;--log-group-name&lt;/span&gt; /ecs/billing
aws logs put-retention-policy &lt;span class="nt"&gt;--log-group-name&lt;/span&gt; /ecs/billing &lt;span class="nt"&gt;--retention-in-days&lt;/span&gt; 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CPU Alarm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws cloudwatch put-metric-alarm &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--alarm-name&lt;/span&gt; BillingCPUHigh &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--metric-name&lt;/span&gt; CPUUtilization &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--namespace&lt;/span&gt; AWS/ECS &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--statistic&lt;/span&gt; Average &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--period&lt;/span&gt; 300 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--threshold&lt;/span&gt; 80 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--comparison-operator&lt;/span&gt; GreaterThanThreshold &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--dimensions&lt;/span&gt; &lt;span class="nv"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ServiceName,Value&lt;span class="o"&gt;=&lt;/span&gt;billing-service &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--evaluation-periods&lt;/span&gt; 2 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--alarm-actions&lt;/span&gt; arn:aws:sns:us-east-1:745812456855:NotifyTeam

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Effects and Outcomes of Implementing New Architecture
&lt;/h3&gt;

&lt;p&gt;By deploying this architecture I was able to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduce deployment time, since only those services that had their code changed must be rebuilt.&lt;/li&gt;
&lt;li&gt;Allow teams to deploy their microservices independently of one another.&lt;/li&gt;
&lt;li&gt;No need to deploy via SSH using PM2.&lt;/li&gt;
&lt;li&gt;Create a more predictable and safer rollback process.&lt;/li&gt;
&lt;li&gt;Provide for reuse of the same Terraform modules in all target environments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With the increasing number of services, it became clear that this architecture can be scaled successfully.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Lessons Learned:
&lt;/h2&gt;

&lt;p&gt;Here are some of the lessons I learned from building this system:&lt;br&gt;
✔Keep microservices fully isolated&lt;br&gt;
The Docker images, task definitions and ALB routes need to be decoupled.&lt;br&gt;
✔ Use folder-based CI/CD&lt;br&gt;
It reduces deployment times so much.&lt;br&gt;
✔ Choose ECR instead of using public registries&lt;br&gt;
It works well with ECS.&lt;br&gt;
✔ Allocate the right CPU &amp;amp; Memory&lt;br&gt;
Under-provisioning leads to throttling.&lt;br&gt;
Over-provisioning wastes money.&lt;br&gt;
✔ Terraform modules save weeks&lt;br&gt;
When multiple environment setups are in question, reusability is the key.&lt;br&gt;
✔ With this CloudWatch setu you get 80% of your monitoring&lt;br&gt;
Later, add Prometheus or Grafana Cloud.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Conclusion
&lt;/h2&gt;

&lt;p&gt;Containers are the foundation of modern infrastructure, and AWS ECS Fargate enables you to run microservices without managing servers. Mixed with GitLab CI/CD and Terraform, you have a scalable, automated, production-grade workflow.&lt;br&gt;
This architecture ensures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast, independent deployments&lt;/li&gt;
&lt;li&gt;Secure and isolated workloads&lt;/li&gt;
&lt;li&gt;ALB with Fargate for high availability&lt;/li&gt;
&lt;li&gt;Complete observability via CloudWatch&lt;/li&gt;
&lt;li&gt;Infrastructure is defined as code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are deploying microservices on AWS and experiencing slow or high risk deployments, this might be a good working solution validated across real-world environments with the increasing number of services.&lt;/p&gt;

&lt;p&gt;If you enjoyed this post, please share it on your favourite social network and get in touch if I can help with anything. Feel free to reach out if you’d like to chat about AWS, DevOps, or container architectures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect with me
&lt;/h2&gt;

&lt;p&gt;• Portfolio: &lt;a href="https://khanalsamir.com" rel="noopener noreferrer"&gt;https://khanalsamir.com&lt;/a&gt;&lt;br&gt;&lt;br&gt;
• GitHub: &lt;a href="https://github.com/Shawmeer" rel="noopener noreferrer"&gt;https://github.com/Shawmeer&lt;/a&gt;&lt;br&gt;&lt;br&gt;
• LinkedIn: &lt;a href="https://linkedin.com/in/samir-khanal" rel="noopener noreferrer"&gt;https://linkedin.com/in/samir-khanal&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;If you're working on similar DevOps or AWS projects, feel free to connect. I regularly share practical cloud and CI/CD content.&lt;/p&gt;

</description>
      <category>containers</category>
      <category>devops</category>
      <category>aws</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Title: Exploring the DevOps Mindset: Why It’s More Than Just Tools</title>
      <dc:creator>Samir Khanal</dc:creator>
      <pubDate>Sun, 07 Dec 2025 07:33:39 +0000</pubDate>
      <link>https://dev.to/shawmeer22/title-exploring-the-devops-mindset-why-its-more-than-just-tools-58pi</link>
      <guid>https://dev.to/shawmeer22/title-exploring-the-devops-mindset-why-its-more-than-just-tools-58pi</guid>
      <description>&lt;p&gt;Body:&lt;/p&gt;

&lt;p&gt;In today’s fast-paced software world, DevOps is often mistaken as a set of tools or a job title. In reality, DevOps is a mindset — a way of thinking that emphasizes collaboration, automation, and continuous improvement across development and operations.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Collaboration Over Silos&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;DevOps encourages developers and operations teams to work together, breaking down barriers that slow down delivery. This collaboration helps detect issues early and accelerates software deployment.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Automation is Key&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;From CI/CD pipelines to automated monitoring, automation reduces human error and ensures that repetitive tasks are handled consistently. Tools like Jenkins, GitLab CI/CD, and AWS CodePipeline are just enablers — the mindset is what drives the improvement.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Continuous Learning&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;DevOps isn’t static. Teams must adapt, learn, and experiment continuously to improve processes and enhance software quality. Metrics, feedback loops, and retrospectives are essential to growth.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Bigger Picture&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ultimately, DevOps is about delivering value to end-users faster and reliably. Tools and technologies are secondary — the culture and mindset are what truly make a difference.&lt;/p&gt;

&lt;p&gt;Conclusion:&lt;br&gt;
Adopting a DevOps mindset transforms the way teams think about software delivery. It’s not just about writing code or deploying servers — it’s about improving collaboration, automation, and customer value continuously.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Serverless Computing: From Basics to Advanced Concepts</title>
      <dc:creator>Samir Khanal</dc:creator>
      <pubDate>Sun, 07 Dec 2025 03:58:54 +0000</pubDate>
      <link>https://dev.to/shawmeer22/serverless-computing-from-basics-to-advanced-concepts-3dgk</link>
      <guid>https://dev.to/shawmeer22/serverless-computing-from-basics-to-advanced-concepts-3dgk</guid>
      <description>&lt;p&gt;&lt;strong&gt;Why Serverless Matters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I honestly thought "serverless" was just another buzzword when I first heard it. How can a serverless thing be if there are clearly servers? However, after digging into AWS, I came to understand that it's not about the tech; it's a new way of doing things that changes the whole app-building paradigm.&lt;br&gt;
Here's the thing that confused me the most: I thought serverless meant "no servers at all" (which is incorrect), and that everything should be serverless (which is also incorrect). This blog is all about the journey of my understanding — from being puzzled to getting it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Serverless Actually Means&lt;/strong&gt;&lt;br&gt;
'Serverless' means that you don't bother with the servers and continue with writing code.&lt;br&gt;
To explain it, one can compare it to Uber vs. car ownership:&lt;br&gt;
When you have a car, you take care of maintenance, insurance, and parking.&lt;/p&gt;

&lt;p&gt;With Uber, you simply state your destination.&lt;/p&gt;

&lt;p&gt;Serverless is similar to Uber, but for computing. Instead of asking whether there are servers, you focus on what your function needs to do. You handle the logic layer, not the hardware layer.&lt;/p&gt;

&lt;p&gt;AWS Lambda: The Heart of Serverless&lt;br&gt;
Lambda runs your code on an event without requiring server setup.&lt;br&gt;
How it works:&lt;br&gt;
You write a function (Python, Node.js, Java, etc.)&lt;/p&gt;

&lt;p&gt;An event triggers it (API call, file upload, timer)&lt;/p&gt;

&lt;p&gt;Lambda executes in milliseconds&lt;/p&gt;

&lt;p&gt;You pay only for execution time&lt;/p&gt;

&lt;p&gt;The first time I ran Lambda, I couldn’t believe it — no EC2, no load balancers. Just code → event → execution.&lt;br&gt;
Lambda changed the way I work: instead of building big systems, I write small functions that “listen” to events.&lt;br&gt;
The pay-as-you-go model is great, especially the 1M free requests per month.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core Serverless Services&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;API Gateway&lt;br&gt;
Creates HTTP APIs that trigger Lambda.&lt;br&gt;
Flow:&lt;br&gt;
User → API Gateway → Lambda → Response&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DynamoDB&lt;br&gt;
AWS's fully managed NoSQL database.&lt;br&gt;
Fast (single-digit ms), scalable, and a perfect partner for Lambda.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;S3 + CloudFront&lt;br&gt;
Great for storing and delivering content at scale.&lt;br&gt;
I once built a system where:&lt;br&gt;
Uploading an image to S3 triggered a Lambda&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Lambda resized the image&lt;br&gt;
 No servers involved at all.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;SNS, SQS, EventBridge
These services enable communication between systems.
SNS → Pub/Sub&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;SQS → Reliable queueing&lt;/p&gt;

&lt;p&gt;EventBridge → Event routing and filtering&lt;/p&gt;

&lt;p&gt;They help build event-driven, fault-tolerant systems.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Step Functions
Connect multiple Lambdas for workflows needing retries, error handling, or branching logic.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Common Architecture Patterns&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Simple API&lt;br&gt;
User → API Gateway → Lambda → DynamoDB&lt;br&gt;
I built this in a day, and it scaled automatically.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Event Processing&lt;br&gt;
S3 Upload → Lambda → Processing&lt;br&gt;
No polling, fully automated.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Async Workflows&lt;br&gt;
API → SQS → Lambda&lt;br&gt;
Allows background processing while returning immediate responses.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;My Key Learnings&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Challenge 1: Traditional Thinking Didn’t Work&lt;br&gt;
I tried building traditional architectures using serverless.&lt;br&gt;
 Breakthrough: Serverless = small, event-driven tasks.&lt;/p&gt;

&lt;p&gt;Challenge 2: Cold Starts Confused Me&lt;br&gt;
Response fluctuated from 50ms to 3 seconds.&lt;br&gt;
 Breakthrough: Cold starts happen during initialization. After that, performance stabilizes.&lt;/p&gt;

&lt;p&gt;Challenge 3: Debugging Felt Blind&lt;br&gt;
No servers to SSH into.&lt;br&gt;
 Breakthrough: CloudWatch logs everything automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Helped Me the Most&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Actually building things.&lt;br&gt;
 Deploy functions, break them on purpose, connect services.&lt;br&gt;
 Hands-on experience makes everything clearer.&lt;/p&gt;

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

&lt;p&gt;Limit Lambda package size&lt;/p&gt;

&lt;p&gt;One function = one responsibility&lt;/p&gt;

&lt;p&gt;Think in terms of events&lt;/p&gt;

&lt;p&gt;Secure IAM roles (least privilege)&lt;/p&gt;

&lt;p&gt;Use CloudWatch &amp;amp; X-Ray from day one&lt;/p&gt;

&lt;p&gt;Prepare for failure with retries, DLQs, and timeouts&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advanced Concepts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Step Functions&lt;br&gt;
Great for multi-step processes, parallel branches, and complex orchestration.&lt;/p&gt;

&lt;p&gt;Cold Start Optimization&lt;br&gt;
Keep the package small&lt;/p&gt;

&lt;p&gt;Use Provisioned Concurrency&lt;/p&gt;

&lt;p&gt;Choose faster runtimes&lt;br&gt;
 Most apps don't need serious optimization.&lt;/p&gt;

&lt;p&gt;When Not to Use Serverless&lt;br&gt;
Long-running processes&lt;/p&gt;

&lt;p&gt;Persistent high-concurrency connections&lt;/p&gt;

&lt;p&gt;Workloads needing special hardware&lt;/p&gt;

&lt;p&gt;Constant heavy traffic where servers are cheaper&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How Serverless Changed My Thinking&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Serverless shifted my mindset:&lt;br&gt;
Focus on business logic&lt;/p&gt;

&lt;p&gt;Think in events, not servers&lt;/p&gt;

&lt;p&gt;Plan for automatic scaling&lt;/p&gt;

&lt;p&gt;Prefer small, separate components&lt;/p&gt;

&lt;p&gt;Yes, I struggled with debugging, IAM, and cold starts — but each taught me something crucial.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My advice: just build something.&lt;br&gt;
Deploy an API.&lt;br&gt;
 Create an S3 trigger.&lt;br&gt;
 Break things and learn from them.&lt;br&gt;
AWS Free Tier is generous, the docs are solid, and the community helps a lot.&lt;br&gt;
At the end of the day, servers still exist — but you don’t have to care about them anymore. That's the whole point.&lt;br&gt;
The Serverless journey you faced is intriguing. Share your experiences to find solutions together.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Host Vite Frontend on AWS S3 and CloudFront with SSL and Custom Domain 2025(Step-by-Step Guide)</title>
      <dc:creator>Samir Khanal</dc:creator>
      <pubDate>Wed, 19 Nov 2025 15:04:44 +0000</pubDate>
      <link>https://dev.to/shawmeer22/host-vite-frontend-on-aws-s3-and-cloudfront-with-ssl-and-custom-domain-2025step-by-step-guide-3o92</link>
      <guid>https://dev.to/shawmeer22/host-vite-frontend-on-aws-s3-and-cloudfront-with-ssl-and-custom-domain-2025step-by-step-guide-3o92</guid>
      <description>&lt;p&gt;If you are looking for a quick, secure, and inexpensive method to host Vite frontend on AWS S3 AND Cloudfront , you are in the right place! In this step-by-step guide I will detail for you how to:&lt;/p&gt;

&lt;p&gt;– Deploy a vite frontend on an AWS S3 bucket&lt;br&gt;
– Serve it globally via AWS CloudFront&lt;br&gt;
– Secure it with SSL/HTTPS&lt;br&gt;
– Connect it to a custom domain using Route 53&lt;/p&gt;

&lt;p&gt;With this set-up you will enjoy fast page loading times, global availability, and industry standard security using scalable AWS services.&lt;/p&gt;

&lt;p&gt;Prerequisites&lt;br&gt;
A Vite frontend project (Node.js based)&lt;br&gt;
An AWS account with permissions for S3, CloudFront, Certificate Manager, and Route 53)&lt;br&gt;
STEPS&lt;br&gt;
1.Clone your project&lt;br&gt;
git clone your-repo-url&lt;br&gt;
cd your-project-folder&lt;br&gt;
2.Building your vite project:&lt;br&gt;
Go to your root directory of project and run the following command:&lt;/p&gt;

&lt;p&gt;npm install&lt;br&gt;
npm run build&lt;br&gt;
In my case it was : npm vite build&lt;/p&gt;

&lt;p&gt;This command generates optimized production files, typically in a dist folder in your project.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Setting Up an S3 Bucket:
Go to the S3 Console.
Click Create bucket.
Enter a unique bucket name (e.g., vite-frontend-bucket).
Choose a region close to your target audience.
Uncheck Block all public access, then confirm.
Click Create bucket.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;4.Upload Your Files to your S3 bucket.&lt;br&gt;
Click on your newly created bucket in the S3 Console.&lt;/p&gt;

&lt;p&gt;Go to the Objects tab and click Upload.&lt;br&gt;
Drag and drop the contents of the dist folder (not the folder itself) into the upload area.&lt;br&gt;
Click Upload.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enable Static Website Hosting
Go to the Properties tab of your bucket.
Scroll down to the Static website hosting section.
Click Edit and enable static website hosting.
Set the Index document to index.html.
Save the changes.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configure Bucket Permissions&lt;br&gt;
Go to the Permissions tab of your bucket.&lt;br&gt;
Scroll to the Bucket policy section and click Edit.&lt;br&gt;
Add the following policy (replace your-bucket-name with your bucket’s name):&lt;br&gt;
{&lt;br&gt;
"Version": "2012-10-17",&lt;br&gt;
"Statement": [&lt;br&gt;
{&lt;br&gt;
  "Effect": "Allow",&lt;br&gt;
  "Principal": "&lt;em&gt;",&lt;br&gt;
  "Action": "s3:GetObject",&lt;br&gt;
  "Resource": "arn:aws:s3:::your-bucket-name/&lt;/em&gt;"&lt;br&gt;
}&lt;br&gt;
]&lt;br&gt;
}&lt;br&gt;
Save Changes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Test the S3 Static Website&lt;br&gt;
Go to the Properties tab and scroll down to the Static website hosting section.&lt;br&gt;
Copy the Endpoint URL and open it in your browser.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your Vite project should load!&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set Up CloudFront for Your S3 Bucket
Go to the CloudFront Console.
Click Create Distribution.
Under the Origin section:
Select S3 bucket as the origin.
Choose your bucket from the dropdown.
4.Set the Default cache behavior:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Viewer Protocol Policy: Redirect HTTP to HTTPS.&lt;br&gt;
5.Add an Alternate Domain Name (CNAME) if you plan to use a custom domain.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Click Create Distribution.&lt;/p&gt;

&lt;p&gt;Restrict Bucket Access to CloudFront&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Go back to your S3 bucket and open the Permissions tab.&lt;br&gt;
Remove the public access permissions you set earlier.&lt;br&gt;
Add the CloudFront OAC to allow only CloudFront to access your bucket.&lt;br&gt;
9.Get an SSL Certificate&lt;br&gt;
Go to the Certificate Manager service.&lt;br&gt;
Click Request a certificate.&lt;br&gt;
Choose Request a public certificate and click Next.&lt;br&gt;
Enter your domain name (e.g., &lt;a href="http://www.example.com" rel="noopener noreferrer"&gt;www.example.com&lt;/a&gt;) and click Add another name to include the root domain (example.com).&lt;br&gt;
Choose DNS validation and click Request.&lt;br&gt;
        Validate the Certificate&lt;/p&gt;

&lt;p&gt;Go to Route 53 and open the hosted zone for your domain.&lt;br&gt;
Add the DNS records provided by the Certificate Manager.&lt;br&gt;
Wait until the certificate status changes to Issued.&lt;/p&gt;

&lt;p&gt;Or you can do it in the same page.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Link CloudFront with Your Domain
Open your CloudFront distribution.
Go to the General tab and click Edit.
Add your domain name (e.g., &lt;a href="http://www.example.com" rel="noopener noreferrer"&gt;www.example.com&lt;/a&gt;) in the Alternate Domain Names (CNAMEs) field.
Select the SSL certificate you created in the Custom SSL Certificate section.
Save the changes.
   Update Route 53&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Go to Route 53 and open the hosted zone for your domain.&lt;br&gt;
Add an A Record:&lt;br&gt;
Record name: www (or leave it blank for the root domain).&lt;br&gt;
Alias: Yes.&lt;br&gt;
Target: Select your CloudFront distribution.&lt;br&gt;
     3. Save the record.&lt;/p&gt;

&lt;p&gt;11: Test Your Setup&lt;br&gt;
Open your browser and navigate to your domain (e.g., &lt;a href="https://www.example.com" rel="noopener noreferrer"&gt;https://www.example.com&lt;/a&gt;).&lt;br&gt;
Verify that your Vite project loads correctly over HTTPS&lt;br&gt;
12: Conclusion&lt;br&gt;
Congratulations! You’ve successfully hosted your Vite frontend project on AWS S3, served it with CloudFront, secured it with an SSL certificate, and connected it to your custom domain using Route 53. With this setup, your site is fast, secure, and ready for global traffic.&lt;/p&gt;

&lt;p&gt;With this setup, you’ve successfully learned how to host your Vite frontend on AWS S3 and CloudFront with full HTTPS support and a custom domain — a fast, secure, and scalable way to deploy modern web apps.&lt;/p&gt;

&lt;p&gt;FAQ: Hosting Your Vite Frontend with AWS S3, CloudFront &amp;amp; Route 53&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Why should I use AWS S3 and CloudFront to host my frontend?&lt;br&gt;
Great question! S3 is a super reliable and affordable way to store static files like HTML, CSS, and JavaScript. Pair it with CloudFront, and your site gets delivered from servers all around the world, making it fast for users no matter where they are. Add in Route 53 for custom domains and SSL for security, and you’ve got a professional, secure setup that scales easily.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How do I update my site after it’s live?&lt;br&gt;
It’s simple: just make your changes, then run npm run build to generate the latest version of your site. Upload the new files from your dist folder to your S3 bucket. If you’re using CloudFront, don’t forget to invalidate the cache so visitors can see the latest version immediately.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What’s the difference between using the S3 website link and CloudFront?&lt;br&gt;
The S3 website URL works fine, but it doesn’t support HTTPS by itself—which is a deal breaker these days. CloudFront not only brings HTTPS support (via SSL certificates), but it also speeds up your site with caching and global delivery.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How can I get HTTPS (SSL) working with my custom domain?&lt;br&gt;
AWS has a free tool called Certificate Manager. Just request a certificate for your domain, verify it using Route 53 DNS records, and then attach that certificate to your CloudFront distribution. Once that’s done, your site will be secure with HTTPS.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How long does it take for DNS changes to show up?&lt;br&gt;
Usually just a few minutes, but it can sometimes take up to 48 hours to fully update across the internet. If it’s not showing right away, give it a bit of time and try clearing your browser cache.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I’m seeing errors when I load my site. What should I check?&lt;br&gt;
Here’s a quick checklist:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Did you invalidate the CloudFront cache after updating?&lt;/p&gt;

&lt;p&gt;Are your S3 permissions set correctly for CloudFront to access your files?&lt;/p&gt;

&lt;p&gt;Is your index.html file uploaded and named correctly?&lt;/p&gt;

&lt;p&gt;Are your DNS records in Route 53 pointing to the right place?&lt;/p&gt;

&lt;p&gt;Is your SSL certificate properly attached in CloudFront?&lt;/p&gt;

&lt;p&gt;Go through those steps, and most issues can be sorted out pretty quickly.&lt;/p&gt;

&lt;p&gt;Need a Hand?&lt;br&gt;
Setting this all up can take time and attention to detail. If you’re looking for a partner to manage AWS deployments or streamline DevOps, Cloudlaya is here to help.&lt;/p&gt;

&lt;p&gt;Yoy May Find these Blog Useful.&lt;/p&gt;

&lt;p&gt;7 Powerful AWS NOVA Gen AI Tools That Make AI Work For You (No Coding Required!)&lt;/p&gt;

&lt;p&gt;AWS PartyRock: Showcasing the Creativity of Artificial Intelligence&lt;/p&gt;

&lt;p&gt;Delivering High-Performance OTT Streaming with Amazon CloudFront&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>tutorial</category>
      <category>aws</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
