<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[DevOps Blog]]></title><description><![CDATA[DevOps &amp; AWS best practices for deploying Applications.]]></description><link>https://buildstory.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Thu, 24 Sep 2026 18:00:02 GMT</lastBuildDate><atom:link href="https://buildstory.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[The Right Way to Deploy a Frontend App on AWS (The Production Method)]]></title><description><![CDATA[COST: $0 (with AWS Free Tier)
TIME: 15-20 Minutes
SERIES: Deploy like a Pro on AWS

Every curious developer starting with the cloud asks the same questions: “What’s the right way to deploy my app?” or “How do I deploy this like a real production appl...]]></description><link>https://buildstory.hashnode.dev/the-right-way-to-deploy-a-frontend-app-on-aws-the-production-method</link><guid isPermaLink="true">https://buildstory.hashnode.dev/the-right-way-to-deploy-a-frontend-app-on-aws-the-production-method</guid><category><![CDATA[AWS]]></category><category><![CDATA[Cloud Computing]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[Terraform]]></category><dc:creator><![CDATA[Vineet]]></dc:creator><pubDate>Wed, 17 Sep 2025 07:57:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1758095709119/111348c9-951a-42b6-a371-502027e32bfb.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p><strong>COST:</strong> $0 (with AWS Free Tier)</p>
<p><strong>TIME:</strong> 15-20 Minutes</p>
<p><strong>SERIES:</strong> Deploy like a Pro on AWS</p>
</blockquote>
<p>Every curious developer starting with the cloud asks the same questions: “What’s the <em>right</em> way to deploy my app?” or “How do I deploy this like a real production application?”</p>
<p>If that's you, you're in the right place. Forget dragging files into a server. Forget overpaying for an EC2 instance you don't need.</p>
<p>This article will show you the modern, professional method for deploying a frontend application (React, Vue, Angular, etc.) on AWS. This architecture is <strong>cost-effective</strong>, <strong>infinitely scalable</strong>, and <strong>fully automated</strong>. The concepts you learn here are the standard across AWS, GCP, and Azure.</p>
<hr />
<h2 id="heading-the-core-concept-static-files-dont-need-a-server">The Core Concept: Static Files Don't Need a Server</h2>
<p>First, a fundamental rule: <strong>Never jump directly into a cloud service without understanding the <em>why</em>.</strong></p>
<p>Modern frontend frameworks like React, Angular, or Vue have one thing in common: when you run the <code>build</code> command (e.g., <code>npm run build</code>), they don't give you a server. They give you a <code>build</code> or <code>dist</code> folder.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758090160257/e863e86c-df7f-4040-b3fc-e7050220b7d7.png" alt class="image--center mx-auto" /></p>
<p>This folder contains static files: HTML, CSS, JavaScript, and images. The browser understands these files directly. The browser doesn’t know or care about React.js or JSX. Your complex framework code has been compiled down to the basic building blocks of the web that browser can use.</p>
<p>Since we are only dealing with a folder of static files, using a full-blown virtual server like an EC2 instance is inefficient and expensive. It's like using a cargo ship to deliver a pizza.</p>
<p>The professional approach for production focuses on three things:</p>
<ol>
<li><p><strong>Cost-Effectiveness:</strong> Use the cheapest possible storage.</p>
</li>
<li><p><strong>Scalability &amp; Performance:</strong> Serve content to users globally, fast.</p>
</li>
<li><p><strong>Automation:</strong> Never deploy manually. Ever.</p>
</li>
</ol>
<p>Here’s how we achieve it:</p>
<ul>
<li><p>For <strong>Cost</strong>, we use <strong>Amazon S3</strong>. It’s an object storage service, perfect for hosting static files at an incredibly low cost.</p>
</li>
<li><p>For <strong>Scalability and Performance</strong>, we use <strong>Amazon CloudFront</strong>. This is a Content Delivery Network (CDN) that caches our website in data centers all over the world, delivering it to users from the location nearest to them. This means low latency and lightning-fast load times.</p>
</li>
<li><p>For <strong>Automation</strong>, we build a CI/CD pipeline with <strong>GitHub Actions</strong>. When we push new code to our repository, it will automatically build our application and deploy the new files to S3.</p>
</li>
</ul>
<hr />
<h2 id="heading-the-hands-on-lab-lets-build-it">The Hands-On Lab: Let's Build It</h2>
<p>Follow along. No steps skipped.</p>
<h3 id="heading-prerequisites">Prerequisites</h3>
<p>Before we start, make sure you have the following:</p>
<ul>
<li><p>An <a target="_blank" href="https://aws.amazon.com/free/">AWS Account</a> (The Free Tier is more than enough for this tutorial).</p>
</li>
<li><p>A <a target="_blank" href="https://github.com/">GitHub Account</a>.</p>
</li>
<li><p><a target="_blank" href="https://nodejs.org/en">Node.js</a> installed on your computer.</p>
</li>
</ul>
<h3 id="heading-step-1-get-the-frontend-project">Step 1: Get the Frontend Project</h3>
<p>For this tutorial, we'll use a simple React quiz app. First, you need to get the code onto your computer.</p>
<ol>
<li><p>Open your terminal or command prompt.</p>
</li>
<li><p>Clone the repository using the following <code>git</code> command:</p>
<p> /</p>
<pre><code class="lang-bash"> git <span class="hljs-built_in">clone</span> https://github.com/vinsin21/cloud-quiz-craft.git
</code></pre>
</li>
<li><p>Navigate into the newly created project directory:</p>
<pre><code class="lang-bash"> <span class="hljs-built_in">cd</span> cloud-quiz-craft
</code></pre>
</li>
</ol>
<p><strong>(Optional) Run the Project Locally</strong></p>
<p>Let's make sure the app works on your machine before deploying it.</p>
<ol>
<li><p>Install all the necessary project dependencies by running:</p>
<pre><code class="lang-bash"> npm install
</code></pre>
</li>
<li><p>Start the local development server:</p>
<pre><code class="lang-bash"> npm run dev
</code></pre>
</li>
<li><p>Open your web browser and go to <a target="_blank" href="http://localhost:3000"><code>http://localhost:3000</code></a>. You should see the quiz app running.</p>
</li>
<li><p>Once you're done, go back to your terminal and press <code>CTRL + C</code> to stop the local server.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758090356721/1adf4aa6-fbb9-4ad3-a220-38ef467bda5f.png" alt class="image--center mx-auto" /></p>
</li>
</ol>
<p><strong>(Optional) See the Build Folder</strong></p>
<p>Let's generate the <code>dist</code> folder to see the static files we've been talking about.</p>
<ol>
<li><p>In your terminal, run the build command:</p>
<pre><code class="lang-bash"> npm run build
</code></pre>
<p> You will now see a new <code>dist</code> folder in your project. This folder contains the final HTML, CSS, and JavaScript that we will deploy. You can delete this <code>dist</code> folder now; to ensure our CI/CD pipeline is the only thing creating our production build, guaranteeing a clean state every time.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758090385934/1216039f-0819-4ccd-af60-5257c424dace.png" alt class="image--center mx-auto" /></p>
</li>
</ol>
<h3 id="heading-step-2-create-and-configure-the-s3-bucket">Step 2: Create and Configure the S3 Bucket</h3>
<p>An S3 bucket is a container in the cloud where our static files will live.</p>
<ol>
<li><p>Log in to your AWS Management Console and navigate to the <strong>S3</strong> service.</p>
</li>
<li><p>Click the <strong>Create bucket</strong> button.</p>
</li>
<li><p><strong>Bucket name:</strong> Give it a <strong>globally unique name</strong>. This means no one else in the world can have the same name. A good practice is to add your name or a random number to it (e.g., <code>my-pro-frontend-app-2025-cloud-quize</code>).</p>
</li>
<li><p><strong>Object Ownership:</strong> Select “ACLs disabled (recommended)”</p>
</li>
<li><p><strong>Block Public Access settings:</strong> This is a critical security step. Ensure the box for <strong>Block all public access</strong> is checked. We will <em>not</em> make the bucket public. CloudFront will be the only service allowed to access it.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758091013997/51c8cb5f-f421-4826-84f3-6200bd8a8311.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Leave all other settings as default and click <strong>Create bucket</strong>.</p>
</li>
</ol>
<h3 id="heading-step-3-set-up-the-cloudfront-distribution-cdn">Step 3: Set Up the CloudFront Distribution (CDN)</h3>
<p>CloudFront will act as the public face of our website, fetching our files from the private S3 bucket and serving them to users quickly and securely.</p>
<ol>
<li><p>In the AWS Console, navigate to the <strong>CloudFront</strong> service.</p>
</li>
<li><p>Click <strong>Create a CloudFront distribution</strong>.</p>
</li>
<li><p><strong>Step 1: Get started</strong></p>
<ul>
<li><p><strong>Distribution name:</strong> Enter a descriptive name like <code>cloudfront-for-cloud-quiz-app</code>.</p>
</li>
<li><p><strong>Description (optional):</strong> Add a note like <code>This CloudFront distribution is for cloud quiz web app</code>.</p>
</li>
<li><p><strong>Distribution type:</strong> Select <strong>Single website or app</strong>.</p>
</li>
<li><p>Leave <strong>Custom domain</strong> and <strong>Tags</strong> as default.</p>
</li>
<li><p>Click <strong>Next</strong>.</p>
</li>
</ul>
</li>
</ol>
<p>    <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758091657412/ad2a893b-529b-46e3-b060-69e1b77b843d.jpeg" alt class="image--center mx-auto" /></p>
<ol start="4">
<li><p><strong>Step 2: Specify origin</strong></p>
<ul>
<li><p><strong>Origin type:</strong> Select <strong>Amazon S3</strong>.</p>
</li>
<li><p><strong>S3 origin:</strong> Click the field and select the S3 bucket you just created from the dropdown list (e.g., <a target="_blank" href="http://my-pro-frontend-app-2025-cloud-quize.s3.ap-south-1.amazonaws.com"><code>my-pro-frontend-app-2025-cloud-quize.s3.ap-south-1.amazonaws.com</code></a>).</p>
</li>
<li><p><strong>Origin path - optional:</strong> Leave this empty.</p>
</li>
<li><p>Under <strong>Settings</strong>, ensure <strong>Allow private S3 bucket access to CloudFront</strong> is checked and <strong>Allow private S3 bucket access to CloudFront - Recommended</strong> is selected.</p>
<ul>
<li>This creates a special <strong>Origin access control (OAC)</strong> that allows CloudFront to read objects from your S3 bucket without making your bucket public.</li>
</ul>
</li>
<li><p>Leave <strong>Origin settings</strong> and <strong>Cache settings</strong> as "Use recommended settings".</p>
</li>
<li><p>Click <strong>Next</strong>.</p>
</li>
</ul>
</li>
</ol>
<p>    <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758091691002/b9ad3f4f-3239-498f-ba41-a81b3e9b6d8e.jpeg" alt class="image--center mx-auto" /></p>
<ol start="5">
<li><p><strong>Step 3: Enable security</strong></p>
<ul>
<li><p>Under <strong>Web Application Firewall (WAF)</strong>, select <strong>Do not enable security protections</strong>. Because it charges $8-$14 per month (For this tutorial, we don't need WAF, but for production applications, you might consider enabling it.)</p>
</li>
<li><p>Click <strong>Next</strong>.</p>
</li>
</ul>
</li>
</ol>
<p>    <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758091731900/4efd6072-cf77-4d5f-8bcc-505dcdb27a9e.jpeg" alt class="image--center mx-auto" /></p>
<ol start="6">
<li><p><strong>Step 4: Review and create</strong></p>
<ul>
<li><p>Review all the settings you've configured.</p>
</li>
<li><p>At the bottom, click <strong>Create distribution</strong>.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758091854259/65643f05-2be5-4e6c-9314-9d898c848b91.jpeg" alt class="image--center mx-auto" /></p>
</li>
<li><p>The status will show as "Deploying" for a few minutes while CloudFront provisions its resources globally.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758091942057/123e2288-4866-4b6b-a0b4-d38483cab741.jpeg" alt class="image--center mx-auto" /></p>
</li>
</ul>
</li>
</ol>
<h3 id="heading-step-4-create-an-iam-role-for-github-actions">Step 4: Create an IAM Role for GitHub Actions</h3>
<p>We need to give GitHub a secure way to access our AWS account to upload files. We'll use an IAM Role, which is the modern, password-less way to grant permissions.</p>
<h4 id="heading-part-a-set-up-the-github-identity-provider">Part A: Set Up the GitHub Identity Provider</h4>
<ol>
<li><p>In the AWS Console, navigate to the <strong>IAM</strong> service.</p>
</li>
<li><p>On the left-hand menu, click <strong>Identity providers</strong> and then click <strong>Add provider</strong>.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758092142223/6fdb41e6-4071-4767-ac91-e062e5347288.jpeg" alt class="image--center mx-auto" /></p>
</li>
<li><p>Select <strong>OpenID Connect</strong>.</p>
<ul>
<li><p><strong>Provider URL:</strong> <a target="_blank" href="https://token.actions.githubusercontent.com"><code>https://token.actions.githubusercontent.com</code></a></p>
</li>
<li><p><strong>Audience:</strong> <a target="_blank" href="http://sts.amazonaws.com"><code>sts.amazonaws.com</code></a></p>
</li>
<li><p>Click <strong>Add provider</strong>.</p>
</li>
</ul>
</li>
</ol>
<p>    <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758092288159/34f0b0ab-ce7f-4281-b0eb-b51c1f5fc3d1.jpeg" alt class="image--center mx-auto" /></p>
<h4 id="heading-part-b-create-the-custom-iam-policy">Part B: Create the Custom IAM Policy</h4>
<ol>
<li><p>In the IAM service, click on <strong>Policies</strong> in the left-hand menu.</p>
</li>
<li><p>Click the <strong>Create policy</strong> button.</p>
</li>
<li><p>Select the <strong>JSON</strong> editor tab.</p>
</li>
<li><p>Delete the template content and paste the following policy. <strong>Remember to replace</strong> <code>YOUR_BUCKET_NAME</code> with your actual S3 bucket name.</p>
<p> JSON</p>
<pre><code class="lang-bash"> {
     <span class="hljs-string">"Version"</span>: <span class="hljs-string">"2012-10-17"</span>,
     <span class="hljs-string">"Statement"</span>: [
         {
             <span class="hljs-string">"Sid"</span>: <span class="hljs-string">"AllowFileUploadsAndSyncToS3"</span>,
             <span class="hljs-string">"Effect"</span>: <span class="hljs-string">"Allow"</span>,
             <span class="hljs-string">"Action"</span>: [
                 <span class="hljs-string">"s3:PutObject"</span>,
                 <span class="hljs-string">"s3:ListBucket"</span>,
                 <span class="hljs-string">"s3:DeleteObject"</span>
             ],
             <span class="hljs-string">"Resource"</span>: [
                 <span class="hljs-string">"arn:aws:s3:::YOUR_BUCKET_NAME"</span>,
                 <span class="hljs-string">"arn:aws:s3:::YOUR_BUCKET_NAME/*"</span>
             ]
         },
         {
             <span class="hljs-string">"Sid"</span>: <span class="hljs-string">"InvalidateCloudFrontCache"</span>,
             <span class="hljs-string">"Effect"</span>: <span class="hljs-string">"Allow"</span>,
             <span class="hljs-string">"Action"</span>: <span class="hljs-string">"cloudfront:CreateInvalidation"</span>,
             <span class="hljs-string">"Resource"</span>: <span class="hljs-string">"*"</span>
         }
     ]
 }
</code></pre>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758093586247/cd29715e-a2f4-48d4-9236-a85456a3cbb1.jpeg" alt class="image--center mx-auto" /></p>
</li>
<li><p>Click <strong>Next</strong>.</p>
</li>
<li><p>On the "Review and create" page, give the policy a name like <code>GitHub-S3-Deploy-Policy</code>.</p>
</li>
<li><p>Click <strong>Create policy</strong>.</p>
</li>
</ol>
<h4 id="heading-part-c-create-the-iam-role">Part C: Create the IAM Role</h4>
<ol start="4">
<li><p>Now, go to <strong>Roles</strong> in the left menu and click <strong>Create role</strong>.</p>
</li>
<li><p>For <strong>Trusted entity type</strong>, select <strong>Web identity</strong>.</p>
</li>
<li><p>Under <strong>Web identity</strong>, choose the <strong>Identity provider</strong> you just created. The <strong>Audience</strong> should be automatically selected.</p>
</li>
<li><p>For <strong>GitHub organization</strong>, enter <strong>only</strong> your GitHub username. For example: <code>vinsin21</code>. <strong>Do not paste the full URL.</strong></p>
</li>
<li><p>For <strong>GitHub repository</strong>, enter <strong>only</strong> the name of your repository. For example: <code>cloud-quiz-craft</code>.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758092768422/d921a111-1a9e-4415-805e-9211f9047416.jpeg" alt class="image--center mx-auto" /></p>
</li>
<li><p>Click <strong>Next</strong>.</p>
</li>
<li><p>On the <strong>Add permissions</strong> page, use the search bar to find the policy you created in Part B: <code>GitHub-S3-Deploy-Policy</code>.</p>
</li>
<li><p>Check the box next to your custom policy in the search results.</p>
</li>
<li><p>Click <strong>Next</strong>.</p>
</li>
<li><p>Give the role a name like <code>GitHub-S3-Deploy-Role</code>, review the details, and click <strong>Create role</strong>.</p>
</li>
<li><p>Finally, click on the role you just created from the list and copy its <strong>ARN</strong>. It will look like <code>arn:aws:iam::123456789012:role/GitHub-S3-Deploy-Role</code>. Save this for the next step.</p>
</li>
</ol>
<h3 id="heading-step-5-build-the-cicd-pipeline-with-github-actions">Step 5: Build the CI/CD Pipeline with GitHub Actions</h3>
<p>This is the final step where we automate everything.</p>
<ol>
<li><p>Go to your forked repository on <a target="_blank" href="http://GitHub.com">GitHub.com</a>.</p>
</li>
<li><p>Click on the <strong>Settings</strong> tab.</p>
</li>
<li><p>In the left menu, go to <strong>Secrets and variables &gt; Actions</strong>.</p>
</li>
<li><p>Click <strong>New repository secret</strong> and create the following four secrets:</p>
<ul>
<li><p><code>AWS_ROLE_ARN</code>: Paste the IAM Role ARN you copied earlier.</p>
</li>
<li><p><code>AWS_REGION</code>: Enter the region of your S3 bucket (e.g., <code>ap-south-1</code>).</p>
</li>
<li><p><code>AWS_S3_BUCKET</code>: Enter your unique S3 bucket name.</p>
</li>
<li><p><code>CLOUDFRONT_DISTRIBUTION_ID</code>: Go to your CloudFront distribution page in AWS and copy its ID (it looks like <code>E123ABCDEF45G</code>).</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758094958235/f25b9548-a268-4f01-843b-5421c64665e5.jpeg" alt class="image--center mx-auto" /></p>
</li>
</ul>
</li>
</ol>
<p>    <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758094249567/1528144d-ffce-45f4-8f89-a2c6a87442dc.jpeg" alt class="image--center mx-auto" /></p>
<ol start="5">
<li><p>Now, go back to your project code in VS Code. Create a new folder structure: <code>.github/workflows/</code>.</p>
</li>
<li><p>Inside the <code>workflows</code> folder, create a new file named <code>deploy.yml</code>.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758094673879/a9bd5d8f-afbb-4509-a520-4a779886cc9b.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Paste the following code into <code>deploy.yml</code>:</p>
<pre><code class="lang-yaml"> <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">Frontend</span> <span class="hljs-string">to</span> <span class="hljs-string">AWS</span> <span class="hljs-string">S3</span>

 <span class="hljs-attr">on:</span>
   <span class="hljs-attr">push:</span>
     <span class="hljs-attr">branches:</span>
       <span class="hljs-bullet">-</span> <span class="hljs-string">main</span> <span class="hljs-comment"># Trigger deployment only on pushes to the main branch</span>

 <span class="hljs-attr">jobs:</span>
   <span class="hljs-attr">deploy:</span>
     <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
     <span class="hljs-attr">permissions:</span>
       <span class="hljs-attr">id-token:</span> <span class="hljs-string">write</span> <span class="hljs-comment"># Required for OIDC authentication to AWS</span>
       <span class="hljs-attr">contents:</span> <span class="hljs-string">read</span>

     <span class="hljs-attr">steps:</span>
       <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span> <span class="hljs-string">repository</span>
         <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>

       <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Configure</span> <span class="hljs-string">AWS</span> <span class="hljs-string">Credentials</span>
         <span class="hljs-attr">uses:</span> <span class="hljs-string">aws-actions/configure-aws-credentials@v4</span>
         <span class="hljs-attr">with:</span>
           <span class="hljs-attr">role-to-assume:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.AWS_ROLE_ARN</span> <span class="hljs-string">}}</span>
           <span class="hljs-attr">aws-region:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.AWS_REGION</span> <span class="hljs-string">}}</span>

       <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">Node.js</span>
         <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-node@v4</span>
         <span class="hljs-attr">with:</span>
           <span class="hljs-attr">node-version:</span> <span class="hljs-string">'20'</span>

       <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">dependencies</span>
         <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">install</span>

       <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">application</span>
         <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">build</span>

       <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">to</span> <span class="hljs-string">S3</span>
         <span class="hljs-comment"># Syncs the build directory (dist) with the S3 bucket</span>
         <span class="hljs-attr">run:</span> <span class="hljs-string">aws</span> <span class="hljs-string">s3</span> <span class="hljs-string">sync</span> <span class="hljs-string">./dist</span> <span class="hljs-string">s3://${{</span> <span class="hljs-string">secrets.AWS_S3_BUCKET</span> <span class="hljs-string">}}</span> <span class="hljs-string">--delete</span>

       <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Invalidate</span> <span class="hljs-string">CloudFront</span> <span class="hljs-string">Cache</span>
         <span class="hljs-comment"># This ensures users see the new version immediately</span>
         <span class="hljs-attr">run:</span> <span class="hljs-string">aws</span> <span class="hljs-string">cloudfront</span> <span class="hljs-string">create-invalidation</span> <span class="hljs-string">--distribution-id</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.CLOUDFRONT_DISTRIBUTION_ID</span> <span class="hljs-string">}}</span> <span class="hljs-string">--paths</span> <span class="hljs-string">"/*"</span>
</code></pre>
</li>
</ol>
<hr />
<h2 id="heading-the-result-push-and-watch-the-magic">The Result: Push and Watch the Magic</h2>
<p>You're all set! Now, commit and push your changes, including the new <code>deploy.yml</code> file, to your GitHub repository.</p>
<ol>
<li><p>In your terminal, run these commands one by one:</p>
<pre><code class="lang-bash"> git add .
 git commit -m <span class="hljs-string">"feat: Add CI/CD deployment workflow"</span>
 git push origin main
</code></pre>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758094826576/3f1e5fc4-793a-4433-be03-b2a6a73c14ea.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Go to the <strong>Actions</strong> tab in your GitHub repository. You will see your workflow running. It will:</p>
<ol>
<li><p>Check out your code.</p>
</li>
<li><p>Securely authenticate with AWS.</p>
</li>
<li><p>Install dependencies and build your project.</p>
</li>
<li><p>Sync the new files to your S3 bucket.</p>
</li>
<li><p>Tell CloudFront to refresh its cache.</p>
</li>
</ol>
</li>
<li><p>Once the pipeline shows a green checkmark, go back to your <strong>CloudFront dashboard</strong> in AWS, copy the <strong>Distribution domain name</strong> (e.g., <a target="_blank" href="http://d123xyz.cloudfront.net"><code>d123xyz.cloudfront.net</code></a>), and paste it into your browser.</p>
</li>
</ol>
<hr />
<h3 id="heading-troubleshooting-fixing-the-common-access-denied-error">Troubleshooting: Fixing the Common "Access Denied" Error</h3>
<p>After deploying, it's very common to see an XML <code>AccessDenied</code> error when you visit your CloudFront URL. Don't worry, your pipeline worked perfectly! This is a simple permissions issue between CloudFront and S3.</p>
<p>Here are the two most common causes and how to fix them.</p>
<h4 id="heading-1-add-a-default-root-object">1. Add a Default Root Object</h4>
<p>CloudFront doesn't automatically know that <code>index.html</code> is your main page. You have to tell it.</p>
<ul>
<li><p>In your CloudFront distribution, go to the <strong>General</strong> tab.</p>
</li>
<li><p>Under <strong>Settings</strong>, click <strong>Edit</strong>.</p>
</li>
<li><p>In the <strong>Default root object</strong> field, type <code>index.html</code>.</p>
</li>
<li><p>Click <strong>Save changes</strong>.</p>
</li>
</ul>
<p>Your application is now live, served from a global CDN, and deployed with a professional-grade CI/CD pipeline.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758095693312/0db380d5-a79d-4bd8-9323-5312931a54bc.png" alt class="image--center mx-auto" /></p>
<p>To test it, make a small change to the code (e.g., change the title in <code>src/App.jsx</code>), commit, and push again. Within minutes, your change will be live without any manual intervention.</p>
<p>That's how you deploy like a pro.<br />`<strong><em>Note` Added terraform scripts so you can provision entire AWS infrastructure in one click:</em></strong> <a target="_blank" href="https://github.com/vinsin21/cloud-quiz-craft/tree/main/terraform">terraform script</a></p>
<hr />
<blockquote>
<p><strong>Next Steps:</strong> In a future article, we'll see how to deploy backend Rest API like a pro!</p>
</blockquote>
]]></content:encoded></item></channel></rss>