<?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: Francisco Silvério</title>
    <description>The latest articles on DEV Community by Francisco Silvério (@franciscogsilverio).</description>
    <link>https://dev.to/franciscogsilverio</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%2F3745895%2Fd0ecb00e-85c8-4037-af4d-00f855f05d3f.JPEG</url>
      <title>DEV Community: Francisco Silvério</title>
      <link>https://dev.to/franciscogsilverio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/franciscogsilverio"/>
    <language>en</language>
    <item>
      <title>Microservices with Bounded Contexts, Event-Driven Operations and Sagas: The Art of Orchestrating a Distributed System</title>
      <dc:creator>Francisco Silvério</dc:creator>
      <pubDate>Wed, 18 Feb 2026 13:17:11 +0000</pubDate>
      <link>https://dev.to/franciscogsilverio/microservices-beyond-the-theory-how-bounded-contexts-event-driven-operations-and-sagas-5go7</link>
      <guid>https://dev.to/franciscogsilverio/microservices-beyond-the-theory-how-bounded-contexts-event-driven-operations-and-sagas-5go7</guid>
      <description>&lt;p&gt;In a monolithic architecture, a transaction is a single, atomic database operation. It either happens in full, or it doesn't happen at all. It’s safe, but it can become a bottleneck when millions of users are trying to process transactions simultaneously. As we scale into distributed systems, we trade that localized safety for a "relay race" across multiple services. &lt;/p&gt;

&lt;p&gt;To prevent this relay race from ending in a catastrophe, we must move beyond the basic "microservices" buzzword and master the orchestration between services.&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%2Fdzqkeu5l0dvac3el3yro.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%2Fdzqkeu5l0dvac3el3yro.png" alt="Architecture Diagram" width="800" height="700"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The System: International Wire Transfer
&lt;/h2&gt;

&lt;p&gt;This architecture draft draws a microscope to a banking engine, specifically, the part responsible for wire transfers. Its mission is to move funds across borders while satisfying three distinct business requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Integrity:&lt;/strong&gt; Ensuring the math adds up and funds are accounted for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security:&lt;/strong&gt; Screening every transaction against global watchlists.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Market Accuracy:&lt;/strong&gt; Locking in volatile currency exchange rates in real-time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of a single "Banking Service," we have three autonomous "islands" of logic: &lt;strong&gt;Ledger (Core)&lt;/strong&gt;, &lt;strong&gt;Fraud (Compliance)&lt;/strong&gt;, and &lt;strong&gt;FX (Currency)&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Establishing the concepts
&lt;/h2&gt;

&lt;h4&gt;
  
  
  I. Bounded Context: The "Territory" of Data
&lt;/h4&gt;

&lt;p&gt;The term "Bounded Context" was originated by Eric Evans, who defines it as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A description of a boundary (typically a subsystem, or the work of a particular team) within which a particular model is defined and applicable.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Looking at the diagram, you’ll notice that the &lt;strong&gt;Entity: Account&lt;/strong&gt; exists in all three services, but the &lt;strong&gt;Account Model&lt;/strong&gt; is different in each one. This is the &lt;strong&gt;Bounded Context&lt;/strong&gt; in action.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;In the Ledger Context:&lt;/strong&gt; An account is a &lt;em&gt;Balance Container&lt;/em&gt;. It only cares about &lt;code&gt;Available Balance&lt;/code&gt; and &lt;code&gt;Currency&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In the Fraud Context:&lt;/strong&gt; An account is a &lt;em&gt;Legal Risk Profile&lt;/em&gt;. It cares about the &lt;code&gt;Owner Legal Name&lt;/code&gt; and &lt;code&gt;Watchlist Flags&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In the FX Context:&lt;/strong&gt; An account is a &lt;em&gt;Regional Wallet&lt;/em&gt;. It cares about the &lt;code&gt;Home ISO&lt;/code&gt; and the &lt;code&gt;Local Market Rate&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By isolating these models, we ensure that a change in the Compliance department's risk-rating logic never accidentally breaks the code responsible for calculating account balances. Each service owns its own "version of the truth."&lt;/p&gt;

&lt;h4&gt;
  
  
  II. Event-Driven Operations: The "Nervous System"
&lt;/h4&gt;

&lt;p&gt;In the provided architecture, notice that the services never call each other's APIs directly. They communicate via a &lt;strong&gt;Message Bus&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When a request arrives, the Ledger doesn't "tell" Fraud to start a check; it simply announces a fact: &lt;code&gt;TransferRequested&lt;/code&gt;. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Decoupling:&lt;/strong&gt; The Ledger doesn't know (or care) if the Fraud service is currently online. It drops the message on the Bus and continues its work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Autonomy:&lt;/strong&gt; The Fraud and FX services are "Subscribers." They react to the message when they have the resources to do so. This prevents a slow-down in one service from cascading into a total system failure.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. The Saga: Orchestrating the Distributed "Undo"
&lt;/h2&gt;

&lt;p&gt;Now that we have established what the system does and why it is organized this way, one question might pop up: If these services are independent islands, who ensures the process actually finishes? &lt;/p&gt;

&lt;p&gt;This is where the &lt;strong&gt;Saga Pattern&lt;/strong&gt; enters.&lt;/p&gt;

&lt;p&gt;In a distributed system, there is no "Global Rollback." If the money is locked in the Ledger (Step 2) and the Fraud service emits a &lt;code&gt;SecurityCleared&lt;/code&gt; message, but the FX system finds a problem, there is no way to automatically revert all the other database records. We must perform a &lt;strong&gt;Compensating Transaction&lt;/strong&gt;.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  How the Saga Orchestrates the System:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The Happy Path:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ledger&lt;/strong&gt; locks the funds emits &lt;code&gt;TransferRequested&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fraud&lt;/strong&gt; clears the user emits &lt;code&gt;SecurityCleared&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FX&lt;/strong&gt; locks the conversion rate emits &lt;code&gt;RateLocked&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result:&lt;/strong&gt; The transfer is finalized.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The Failure Path (The "Undo" Button):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ledger&lt;/strong&gt; locks $10,000 and emits &lt;code&gt;TransferRequested&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fraud Service&lt;/strong&gt; checks pass and it emits a &lt;code&gt;SecurityCleared&lt;/code&gt; message.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FX Service&lt;/strong&gt; fails to process the transaction and emits &lt;code&gt;TransactionNotAllowed&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Compensation:&lt;/strong&gt; The Ledger and Fraud services are "listening" for failure events. Upon hearing &lt;code&gt;TransactionNotAllowed&lt;/code&gt;, they trigger their own internal "Undo" logic to unlock the $10,000 and notify the user.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Choreography vs. Orchestration
&lt;/h3&gt;

&lt;p&gt;Our system uses &lt;strong&gt;Choreography&lt;/strong&gt;. There is no central "Boss" or "Orchestrator" service. The Ledger works as an entry point for the operation, but it is not responsible for managing the state of the entire flow. &lt;/p&gt;

&lt;p&gt;If we required more granular control over the operation, we could implement &lt;strong&gt;Orchestration&lt;/strong&gt;, where a dedicated service directs the transaction and has the final word to approve or deny it. For many decentralized systems, however, the Choreography shown here offers the best balance of scale and simplicity.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion: Resilience by Design
&lt;/h2&gt;

&lt;p&gt;By combining &lt;strong&gt;Bounded Contexts&lt;/strong&gt; (to isolate logic), &lt;strong&gt;Event-Driven Operations&lt;/strong&gt; (to decouple communication), and &lt;strong&gt;Sagas&lt;/strong&gt; (to handle failures), we build a system that is far more resilient and performant than any monolith. We accept "Eventual Consistency" in exchange for a system that can scale globally and fail gracefully.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>microservices</category>
      <category>api</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Automating React App deployments to AWS with GitHub Actions and OIDC</title>
      <dc:creator>Francisco Silvério</dc:creator>
      <pubDate>Sun, 01 Feb 2026 21:26:37 +0000</pubDate>
      <link>https://dev.to/franciscogsilverio/automating-react-app-deployments-to-aws-with-github-actions-and-oidc-3487</link>
      <guid>https://dev.to/franciscogsilverio/automating-react-app-deployments-to-aws-with-github-actions-and-oidc-3487</guid>
      <description>&lt;p&gt;After deploying my &lt;a href="https://portfolio.franciscogsilverio.com/" rel="noopener noreferrer"&gt;personal website&lt;/a&gt; to AWS, I realized the need to automate the deployment process to streamline the delivery of new features. For that, you can’t go wrong with a CI/CD pipeline powered by your Git provider — in this case, GitHub and GitHub Actions.&lt;/p&gt;

&lt;p&gt;In this article, I’ll walk through the architecture and CI/CD pipeline I use to deploy a React application to S3 + CloudFront, authenticated via GitHub Actions OIDC (no long-lived AWS credentials).&lt;/p&gt;

&lt;p&gt;This setup is suitable for real-world projects and follows AWS and GitHub best practices.&lt;/p&gt;




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

&lt;p&gt;Before diving into CI/CD, let’s clarify the infrastructure that was &lt;strong&gt;already in place&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Initial AWS Setup (Pre-requisites)
&lt;/h2&gt;

&lt;p&gt;The following resources were already created before the pipeline was written.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1 - S3 bucket&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Static website hosting enabled&lt;/li&gt;
&lt;li&gt;Public access configured appropriately&lt;/li&gt;
&lt;li&gt;Hosts the built React assets (index.html, /assets/*, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2 - CloudFront distribution&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Origin pointing to the S3 website endpoint&lt;/li&gt;
&lt;li&gt;Default root object: index.html&lt;/li&gt;
&lt;li&gt;Caching enabled&lt;/li&gt;
&lt;li&gt;Custom domain configured&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3 - Name.com&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Custom domain pointing to the CloudFront distribution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once this setup is complete, the website can already be accessed via the custom domain.&lt;br&gt;
The CI/CD pipeline’s job is to automate updates safely and consistently.&lt;/p&gt;


&lt;h2&gt;
  
  
  AWS: Configuring OIDC Authentication
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1 - Create an OIDC Identity Provider&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In AWS IAM:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provider URL:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://token.actions.githubusercontent.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Audience:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sts.amazonaws.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This allows AWS to trust GitHub as an identity provider.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;2 - Create an IAM Role for GitHub Action&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This role will be assumed by the GitHub Actions runner.&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;"Principal"&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;"Federated"&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:iam::&amp;lt;ACCOUNT_ID&amp;gt;:oidc-provider/token.actions.githubusercontent.com"&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="s2"&gt;"sts:AssumeRoleWithWebIdentity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&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;"StringEquals"&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;"token.actions.githubusercontent.com:aud"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sts.amazonaws.com"&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;"StringLike"&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;"token.actions.githubusercontent.com:sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"repo:&amp;lt;ORG&amp;gt;/&amp;lt;REPO&amp;gt;:*"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="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;3 - Attach Required IAM Permissions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The role needs permissions to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sync files to S3&lt;/li&gt;
&lt;li&gt;Create CloudFront invalidations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example policy (simplified):&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;"s3:PutObject"&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:DeleteObject"&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:ListBucket"&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="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::&amp;lt;BUCKET_NAME&amp;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;"arn:aws:s3:::&amp;lt;BUCKET_NAME&amp;gt;/*"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"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="s2"&gt;"cloudfront:CreateInvalidation"&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;h2&gt;
  
  
  GitHub Actions Pipeline Overview
&lt;/h2&gt;

&lt;p&gt;The pipeline is split into three jobs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;install – install dependencies&lt;/li&gt;
&lt;li&gt;build – build the React app and upload artifacts&lt;/li&gt;
&lt;li&gt;deploy – authenticate to AWS, deploy to S3, invalidate CloudFront&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This separation improves clarity, caching, and debuggability.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Job 1: Installing Dependencies&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;install:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4
    - name: Install dependencies
      uses: actions/setup-node@v4
      with:
        node-version: "22.x"
        cache: 'npm'
    - run: npm ci
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What this job does&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Checks out the repository&lt;/li&gt;
&lt;li&gt;Sets up Node.js 22&lt;/li&gt;
&lt;li&gt;Uses npm ci for deterministic installs&lt;/li&gt;
&lt;li&gt;Enables dependency caching for faster builds&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This job ensures the dependency tree is valid before moving forward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Job 2: Building the React Application&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;build:
  runs-on: ubuntu-latest
  needs: install
  steps:
    - uses: actions/checkout@v4
    - name: Use Node.js 22
      uses: actions/setup-node@v4
      with:
        node-version: "22.x"
        cache: 'npm'
    - run: npm ci
    - run: npm run build
    - name: Upload dist
      uses: actions/upload-artifact@v4
      with:
        name: dist
        path: ./dist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What this job does&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rebuilds the application in a clean environment&lt;/li&gt;
&lt;li&gt;Produces a static dist/ folder&lt;/li&gt;
&lt;li&gt;Uploads the build output as a pipeline artifact&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Artifacts allow the deploy job to be fully decoupled from the build process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Job 3: Deploying to AWS (S3 + CloudFront)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment: aws
    permissions:
      id-token: write
      contents: read

    steps:
      - name: Download dist artifact
        uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_ARN }}
          aws-region: ${{ vars.AWS_REGION }}
          role-session-name: ${{ vars.ROLE_SESSION_NAME}}

      - name: Deploy to S3
        run: |
          aws s3 sync dist s3://${{ vars.S3_BUCKET_NAME }} \
            --delete \
            --exact-timestamps

      - name: Invalidate CloudFront
        run: |
          aws cloudfront create-invalidation \
            --distribution-id ${{ vars.CLOUDFRONT_DISTRIBUTION_ID }} \
            --paths "/*"

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What this job does&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Downloads the build artifact&lt;/li&gt;
&lt;li&gt;Authenticates to AWS&lt;/li&gt;
&lt;li&gt;Uploads the static files to the bucket&lt;/li&gt;
&lt;li&gt;Invalidate CloudFront's cache&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Invalidating /* ensures CloudFront fetches the new version immediately.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Result
&lt;/h2&gt;

&lt;p&gt;After every push to main:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The app is built&lt;/li&gt;
&lt;li&gt;Assets are uploaded to S3&lt;/li&gt;
&lt;li&gt;CloudFront cache is invalidated&lt;/li&gt;
&lt;li&gt;The custom domain serves the latest version reliably&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All without storing a single AWS secret in GitHub.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect" rel="noopener noreferrer"&gt;- GitHub Actions OIDC&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html" rel="noopener noreferrer"&gt;- AWS IAM OIDC&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html" rel="noopener noreferrer"&gt;- CloudFront invalidations&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/aws-actions/configure-aws-credentials" rel="noopener noreferrer"&gt;- aws-actions/configure-aws-credentials&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>react</category>
      <category>githubactions</category>
      <category>cicd</category>
    </item>
  </channel>
</rss>
