<?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[yuvraj-sankilwar]]></title><description><![CDATA[This publication shares practical, real-world engineering insights based on hands-on development and implementation experience.The content emphasizes how things]]></description><link>https://yuvraj-sankilwar.hashnode.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1769969122827/38f35dc4-dd28-46c2-92e1-01282453c5b0.jpeg</url><title>yuvraj-sankilwar</title><link>https://yuvraj-sankilwar.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Tue, 23 Jun 2026 05:40:09 GMT</lastBuildDate><atom:link href="https://yuvraj-sankilwar.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Is Docker Really Overrated? A Developer’s Perspective]]></title><description><![CDATA[Docker is often labeled as “overrated”, especially by developers who have only worked on small or single-service applications. But once you step into real-world, production-grade systems, Docker stops being optional and starts becoming foundational.
...]]></description><link>https://yuvraj-sankilwar.hashnode.dev/is-docker-really-overrated-a-developers-perspective</link><guid isPermaLink="true">https://yuvraj-sankilwar.hashnode.dev/is-docker-really-overrated-a-developers-perspective</guid><category><![CDATA[Devops]]></category><category><![CDATA[Docker]]></category><category><![CDATA[backend]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Microservices]]></category><dc:creator><![CDATA[Yuvraj Sankilwar]]></dc:creator><pubDate>Sun, 08 Feb 2026 10:24:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1770545529304/99229b2c-14da-4705-aaff-8819bc4314d9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Docker is often labeled as <em>“overrated”</em>, especially by developers who have only worked on small or single-service applications. But once you step into real-world, production-grade systems, Docker stops being optional and starts becoming foundational.</p>
<p>This blog explains <strong>what Docker is</strong>, <strong>why it exists</strong>, and <strong>how it helps you replicate production environments locally in a predictable way</strong>.</p>
<h2 id="heading-what-is-docker">What Is Docker?</h2>
<p>Docker is a <strong>containerization platform</strong> that allows you to package an application along with all its dependencies—libraries, runtime, system tools, and configuration—into a single unit called a <strong>container</strong>.</p>
<p>That container can run <strong>consistently across environments</strong>:</p>
<ul>
<li><p>Your local machine</p>
</li>
<li><p>Staging</p>
</li>
<li><p>Production</p>
</li>
</ul>
<p>In short:</p>
<blockquote>
<p><strong>Docker ensures “it works on my machine” also means “it works in production.”</strong></p>
</blockquote>
<h2 id="heading-why-docker-exists-the-real-problem">Why Docker Exists (The Real Problem)</h2>
<p>Most beginners think an application consists of:</p>
<ul>
<li><p>Frontend</p>
</li>
<li><p>Backend</p>
</li>
</ul>
<p>But a <strong>real production application</strong> is much more than that.</p>
<p>A single backend usually depends on:</p>
<ul>
<li><p>Databases: PostgreSQL, MongoDB, Cassandra</p>
</li>
<li><p>Messaging systems: Kafka</p>
</li>
<li><p>Caching: Redis</p>
</li>
<li><p>Workflow engines: Temporal</p>
</li>
<li><p>Auth services</p>
</li>
<li><p>Migration services</p>
</li>
<li><p>Background workers</p>
</li>
<li><p>Artifacts &amp; configs</p>
</li>
</ul>
<p>All of these components must work <strong>together seamlessly</strong>.</p>
<p>Now imagine installing all of these <strong>directly on your local machine</strong>.</p>
<h3 id="heading-problems-without-docker">Problems Without Docker</h3>
<ul>
<li><p>Dependency conflicts</p>
</li>
<li><p>No isolation between projects</p>
</li>
<li><p>One app breaks another</p>
</li>
<li><p>Different versions of the same service</p>
</li>
<li><p>Local setup ≠ Production setup</p>
</li>
<li><p>Debugging becomes unpredictable</p>
</li>
</ul>
<p>Example:</p>
<ul>
<li><p>Application A and B both use Kafka</p>
</li>
<li><p>Both are running on the same local Kafka instance</p>
</li>
<li><p>Topics, consumers, offsets clash</p>
</li>
<li><p>No isolation</p>
</li>
<li><p>Debugging becomes painful</p>
</li>
</ul>
<p>This is exactly where Docker solves the problem.</p>
<h2 id="heading-what-is-the-use-of-docker">What Is the Use of Docker?</h2>
<p>Docker allows you to:</p>
<ul>
<li><p>Isolate applications and their dependencies</p>
</li>
<li><p>Run multiple services without conflicts</p>
</li>
<li><p>Mirror production-like conditions locally</p>
</li>
<li><p>Make systems predictable and reproducible</p>
</li>
</ul>
<p>With Docker:</p>
<ul>
<li><p>Each application runs in its <strong>own isolated environment</strong></p>
</li>
<li><p>Each service has <strong>its own version, config, and lifecycle</strong></p>
</li>
<li><p>Your local setup behaves like production</p>
</li>
</ul>
<blockquote>
<p><strong>Local = Staging = Production (as close as possible)</strong></p>
</blockquote>
<p>This predictability is critical for stable deployments.</p>
<h2 id="heading-key-docker-concepts">Key Docker Concepts</h2>
<h3 id="heading-1-images">1. Images</h3>
<p>A <strong>Docker image</strong> is a <strong>read-only blueprint</strong> of an application.</p>
<p>It contains:</p>
<ul>
<li><p>Application code</p>
</li>
<li><p>Runtime (Node, Java, Python, etc.)</p>
</li>
<li><p>System dependencies</p>
</li>
<li><p>Configuration defaults</p>
</li>
</ul>
<p>Images are usually pulled from <strong>Docker Hub</strong>, Docker’s public image registry.</p>
<p>Example:</p>
<ul>
<li><p><code>postgres:15</code></p>
</li>
<li><p><code>redis:7</code></p>
</li>
<li><p><code>node:20</code></p>
</li>
</ul>
<h3 id="heading-2-containers">2. Containers</h3>
<p>A <strong>container</strong> is a <strong>running instance of an image</strong>.</p>
<p>Think of it as:</p>
<blockquote>
<p>An isolated process running on your host OS with its own filesystem, network, and environment.</p>
</blockquote>
<p>Key points:</p>
<ul>
<li><p>Lightweight (no full OS)</p>
</li>
<li><p>Fast to start</p>
</li>
<li><p>Fully isolated from other containers</p>
</li>
</ul>
<h3 id="heading-3-volumes">3. Volumes</h3>
<p><strong>Volumes</strong> are used for <strong>persistent data storage</strong>.</p>
<p>Why they matter:</p>
<ul>
<li><p>Containers are ephemeral (they can be destroyed)</p>
</li>
<li><p>Databases need persistent data</p>
</li>
<li><p>Volumes store data outside the container lifecycle, so even if the container is deleted, the data remains intact.</p>
</li>
</ul>
<p>Example:</p>
<ul>
<li><p>PostgreSQL data</p>
</li>
<li><p>MongoDB collections</p>
</li>
<li><p>Uploaded files</p>
</li>
</ul>
<p>Each application can have its <strong>own isolated volume</strong>, preventing data conflicts.</p>
<h3 id="heading-4-virtual-machine-vm">4. Virtual Machine (VM)</h3>
<p>A <strong>VM</strong> is the host system where containers run.</p>
<p>Examples:</p>
<ul>
<li><p>Your laptop</p>
</li>
<li><p>A cloud server (EC2, GCE, Azure VM)</p>
</li>
</ul>
<p>Key idea:</p>
<ul>
<li><p>One VM can run <strong>multiple containers</strong></p>
</li>
<li><p>Containers share the host kernel but stay isolated</p>
</li>
</ul>
<p>Your laptop itself behaves like a VM in this context.</p>
<h3 id="heading-5-docker-network">5. Docker Network</h3>
<p>Docker networks are used to control <strong>communication between containers and services</strong>.</p>
<p><strong>Why they matter:</strong></p>
<ul>
<li><p>Containers are isolated by default</p>
</li>
<li><p>Services need a reliable way to talk to each other</p>
</li>
<li><p>Production systems rely on private, secure networking</p>
</li>
</ul>
<p>Docker networks help replicate <strong>production-style service communication</strong> locally.</p>
<p><strong>What networks provide:</strong></p>
<ul>
<li><p>Service discovery using container or service names</p>
</li>
<li><p>Isolation between different applications</p>
</li>
<li><p>Controlled exposure of services to the host</p>
</li>
</ul>
<p><strong>Example:</strong></p>
<ul>
<li><p>Backend service communicating with PostgreSQL</p>
</li>
<li><p>Kafka talking to producers and consumers</p>
</li>
<li><p>Redis accessed only by internal services</p>
</li>
</ul>
<p><strong>Key benefit:</strong></p>
<p>Each application can run on its own network, allowing multiple apps to use the same ports and services without conflicts, while maintaining proper isolation and predictable behavior.</p>
<h2 id="heading-how-docker-maps-production-to-local">How Docker Maps Production to Local</h2>
<p>In production:</p>
<ul>
<li><p>Services run in isolated environments</p>
</li>
<li><p>Each dependency is versioned</p>
</li>
<li><p>Infrastructure is predictable</p>
</li>
</ul>
<p>Docker allows you to <strong>replicate this exact behavior locally</strong>:</p>
<ul>
<li><p>Same services</p>
</li>
<li><p>Same versions</p>
</li>
<li><p>Same configuration</p>
</li>
<li><p>Same isolation</p>
</li>
</ul>
<p>This ensures:</p>
<ul>
<li><p>Fewer surprises during deployment</p>
</li>
<li><p>Faster debugging</p>
</li>
<li><p>Reliable CI/CD pipelines</p>
</li>
<li><p>Confident production releases</p>
</li>
</ul>
<h2 id="heading-real-life-example-e-commerce-application-using-docker">Real-Life Example: E-commerce Application Using Docker</h2>
<p>Suppose you are a developer setting up an <strong>e-commerce application</strong> with:</p>
<ul>
<li><p><strong>Backend:</strong> Node.js</p>
</li>
<li><p><strong>Frontend:</strong> React</p>
</li>
<li><p><strong>Database:</strong> PostgreSQL</p>
</li>
</ul>
<p>Instead of installing each service separately on your local machine, you create a <strong>Docker Compose</strong> setup.</p>
<h3 id="heading-how-it-works">How It Works</h3>
<ul>
<li><p>You define all services (frontend, backend, database) in a single <code>docker-compose.yml</code></p>
</li>
<li><p>Docker creates a shared network, for example: <code>ecommerce-network</code></p>
</li>
<li><p>Each service runs as an <strong>individual container</strong></p>
</li>
<li><p>All containers are connected to the same Docker network</p>
</li>
</ul>
<p>Because they are on the same network:</p>
<ul>
<li><p>Frontend can talk to the backend</p>
</li>
<li><p>Backend can connect to PostgreSQL using the service name</p>
</li>
<li><p>No ports or services conflict with other applications</p>
</li>
</ul>
<h3 id="heading-data-persistence-with-volumes">Data Persistence with Volumes</h3>
<p>The PostgreSQL container is attached to a <strong>Docker volume</strong>.</p>
<p>This means:</p>
<ul>
<li><p>Database data is stored outside the container</p>
</li>
<li><p>Even if you stop or delete containers, the data remains safe</p>
</li>
<li><p>You can recreate containers without losing data</p>
</li>
</ul>
<h3 id="heading-sharing-data-with-teammates">Sharing Data with Teammates</h3>
<p>If you want to share the same local database with a teammate:</p>
<ul>
<li><p>The Docker volume can be <strong>mounted to a local folder</strong></p>
</li>
<li><p>That folder can be shared with the teammate</p>
</li>
<li><p>The teammate mounts the same folder as a volume in their setup</p>
</li>
</ul>
<p>Both of you now work with the <strong>same database state</strong>, ensuring consistency during development.</p>
<h3 id="heading-key-takeaway">Key Takeaway</h3>
<p>Docker Compose allows you to:</p>
<ul>
<li><p>Run multiple services together</p>
</li>
<li><p>Isolate them inside a private network</p>
</li>
<li><p>Persist and optionally share data using volumes</p>
</li>
</ul>
<p>This setup closely mirrors how real production systems are designed, making local development reliable and predictable.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p><strong>So, is Docker still overrated?</strong><br /><strong>No. Docker is misunderstood.</strong></p>
<p>Docker may feel unnecessary for:</p>
<ul>
<li><p>Simple scripts</p>
</li>
<li><p>Single-service applications</p>
</li>
<li><p>Short-lived experiments</p>
</li>
</ul>
<p>But for:</p>
<ul>
<li><p>Microservices</p>
</li>
<li><p>Distributed systems</p>
</li>
<li><p>Production-grade applications</p>
</li>
</ul>
<p>Docker is about <strong>control, isolation, and predictability</strong>.<br />Docker is not overrated—it is <strong>essential infrastructure</strong>.</p>
<p>Docker is the tool that keeps your system sane—from local development to production.</p>
<blockquote>
<p>If you found this useful or learned something new, feel free to like, share, and comment—I’d love to hear your feedback.</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Built a Website but Don’t Know How to Make It Live?]]></title><description><![CDATA[You’ve built your frontend.The UI works. The code looks fine.
But then comes the hard part:
How do you deploy it like a real production website?With HTTPS, fast loading, redeployments on every push, and zero manual AWS work?
I was stuck here for a lo...]]></description><link>https://yuvraj-sankilwar.hashnode.dev/built-a-website-but-dont-know-how-to-make-it-live</link><guid isPermaLink="true">https://yuvraj-sankilwar.hashnode.dev/built-a-website-but-dont-know-how-to-make-it-live</guid><category><![CDATA[Devops]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Terraform]]></category><category><![CDATA[software development]]></category><dc:creator><![CDATA[Yuvraj Sankilwar]]></dc:creator><pubDate>Sat, 31 Jan 2026 18:31:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1769884220483/26decc09-7bd8-4da4-9689-a219fb332a71.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You’ve built your frontend.<br />The UI works. The code looks fine.</p>
<p>But then comes the hard part:</p>
<p><strong>How do you deploy it like a real production website?</strong><br />With HTTPS, fast loading, redeployments on every push, and zero manual AWS work?</p>
<p>I was stuck here for a long time.</p>
<p>After breaking things multiple times in the AWS console, I finally implemented a <strong>fully automated, production-grade setup</strong> using:</p>
<ul>
<li><p><strong>Amazon S3</strong></p>
</li>
<li><p><strong>CloudFront</strong></p>
</li>
<li><p><strong>Terraform</strong></p>
</li>
<li><p><strong>GitHub Actions</strong></p>
</li>
</ul>
<p>This blog explains <strong>exactly how I did it</strong>, step by step.</p>
<h2 id="heading-what-production-ready-actually-means">What “production-ready” actually means</h2>
<p>For me, production-ready meant:</p>
<ul>
<li><p>HTTPS by default</p>
</li>
<li><p>CDN for fast loading</p>
</li>
<li><p>No public S3 buckets</p>
</li>
<li><p>Infrastructure as code</p>
</li>
<li><p>Automatic deployments on every push</p>
</li>
<li><p>No manual AWS console clicks</p>
</li>
</ul>
<p>Uploading files to S3 manually is <strong>not</strong> production-ready.</p>
<h3 id="heading-see-the-full-project-on-github">See the Full Project on GitHub</h3>
<p>This project is available at <a target="_blank" href="https://github.com/yuvraj-sankilwar/my_hello_website">https://github.com/yuvraj-sankilwar/my_hello_website</a> — feel free to check it out to better understand the complete setup.</p>
<h2 id="heading-final-architecture">Final architecture</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769883290885/d3bddb2c-fe03-4d06-ac85-8f20230b26cc.png" alt class="image--center mx-auto" /></p>
<p>Terraform handles <strong>infrastructure</strong><br />GitHub Actions handles <strong>deployments</strong></p>
<h2 id="heading-tech-stack-used">Tech stack used</h2>
<ul>
<li><p>Frontend: any static site (HTML / React / Vite)</p>
</li>
<li><p>AWS S3 – static file storage</p>
</li>
<li><p>AWS CloudFront – CDN + HTTPS</p>
</li>
<li><p>Terraform – infrastructure automation</p>
</li>
<li><p>GitHub Actions – CI/CD</p>
</li>
<li><p>Terraform remote state stored in S3</p>
</li>
</ul>
<p>Project name:</p>
<pre><code class="lang-plaintext">my_hello_website
</code></pre>
<h2 id="heading-step-1-build-the-frontend">Step 1: Build the frontend</h2>
<p>Your project must generate static files without errors, which means your frontend is ready for deployment.</p>
<p>Example:</p>
<pre><code class="lang-bash">npm run build
</code></pre>
<p>Output:</p>
<pre><code class="lang-plaintext">dist/
 ├── index.html
 └── assets/
</code></pre>
<hr />
<h2 id="heading-step-2-terraform-project-structure">Step 2: Terraform project structure</h2>
<p>Now, inside the same frontend project, create a folder named <code>infra</code>. This folder will store all your infrastructure-related files. These could be Ansible playbooks or YAML files, but in our project we are using Terraform, so we will create the following files.</p>
<pre><code class="lang-plaintext">infra/
 ├── main.tf
 ├── state-storage.tf
 └── variables.tf
</code></pre>
<hr />
<h2 id="heading-step-3-store-terraform-state-in-s3-important">Step 3: Store Terraform state in S3 (IMPORTANT)</h2>
<p>We need this <code>state-storage.tf</code> file to store our infrastructure state in a central place so it’s not tied to one laptop.</p>
<p>By storing the Terraform state in S3:</p>
<ul>
<li><p>The state is not local</p>
</li>
<li><p>It is safe to use with CI/CD</p>
</li>
<li><p>It can be shared across the team</p>
</li>
<li><p>You can recover everything even if you lose your local data</p>
</li>
</ul>
<p>This makes managing AWS resources much simpler and safer.</p>
<h3 id="heading-state-storagetf"><code>state-storage.tf</code></h3>
<pre><code class="lang-plaintext">resource "aws_s3_bucket" "terraform_state" {
  bucket = var.state_storage_bucket_name
  tags = merge(var.tags, {
    Name        = var.state_storage_bucket_name
    Purpose     = "terraform-state-storage"
    Description = "Stores Terraform state files for ${var.bucket_name} infrastructure"
  })
}

resource "aws_s3_bucket_versioning" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

resource "aws_s3_bucket_public_access_block" "terraform_state" {
  bucket                  = aws_s3_bucket.terraform_state.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

output "terraform_state_bucket" {
  value = aws_s3_bucket.terraform_state.id
}

output "terraform_state_bucket_arn" {
  value = aws_s3_bucket.terraform_state.arn
}
</code></pre>
<h2 id="heading-step-4-terraform-infrastructure">Step 4: Terraform infrastructure</h2>
<p>This creates:</p>
<ul>
<li><p>A private S3 bucket to store your website files (the <code>build</code> or <code>dist</code> folder)</p>
</li>
<li><p>A CloudFront distribution that serves your website securely to users across the globe</p>
</li>
<li><p>Origin Access Control (OAC) to control access between CloudFront and the S3 bucket</p>
</li>
<li><p>A secure bucket policy that allows the CloudFront distribution to access the S3 bucket for serving content</p>
<h3 id="heading-maintf"><code>main.tf</code></h3>
</li>
</ul>
<pre><code class="lang-bash">terraform {
  required_version = <span class="hljs-string">"&gt;= 1.0"</span>
  required_providers {
    aws = {
      <span class="hljs-built_in">source</span>  = <span class="hljs-string">"hashicorp/aws"</span>
      version = <span class="hljs-string">"~&gt; 6.0"</span>
    }
  }
  backend <span class="hljs-string">"s3"</span> {
    bucket         = <span class="hljs-string">"my-hello-website-terraform-state"</span>
    key            = <span class="hljs-string">"infrastructure/terraform.tfstate"</span>
    region         = <span class="hljs-string">"ap-south-1"</span>
    encrypt        = <span class="hljs-literal">true</span>
  }
}

provider <span class="hljs-string">"aws"</span> {
  region = var.aws_region
}

resource <span class="hljs-string">"aws_s3_bucket"</span> <span class="hljs-string">"site"</span> {
  bucket = var.bucket_name
  tags   = var.tags
}

resource <span class="hljs-string">"aws_s3_bucket_versioning"</span> <span class="hljs-string">"site"</span> {
  count  = var.enable_versioning ? 1 : 0
  bucket = aws_s3_bucket.site.id
  versioning_configuration {
    status = <span class="hljs-string">"Enabled"</span>
  }
}

resource <span class="hljs-string">"aws_s3_bucket_server_side_encryption_configuration"</span> <span class="hljs-string">"site"</span> {
  bucket = aws_s3_bucket.site.id
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = <span class="hljs-string">"AES256"</span>
    }
  }
}

resource <span class="hljs-string">"aws_s3_bucket_public_access_block"</span> <span class="hljs-string">"block"</span> {
  bucket                  = aws_s3_bucket.site.id
  block_public_acls       = <span class="hljs-literal">true</span>
  block_public_policy     = <span class="hljs-literal">true</span>
  ignore_public_acls      = <span class="hljs-literal">true</span>
  restrict_public_buckets = <span class="hljs-literal">true</span>
}

resource <span class="hljs-string">"aws_cloudfront_origin_access_control"</span> <span class="hljs-string">"oac"</span> {
  name                              = <span class="hljs-string">"my-hello-website-oac"</span>
  description                       = <span class="hljs-string">"OAC for my-hello-website"</span>
  origin_access_control_origin_type = <span class="hljs-string">"s3"</span>
  signing_behavior                  = <span class="hljs-string">"always"</span>
  signing_protocol                  = <span class="hljs-string">"sigv4"</span>
}

resource <span class="hljs-string">"aws_cloudfront_function"</span> <span class="hljs-string">"rewrite_request"</span> {
  name    = <span class="hljs-string">"my-hello-website-rewrite-request"</span>
  runtime = <span class="hljs-string">"cloudfront-js-2.0"</span>
  code    = &lt;&lt;-EOF
<span class="hljs-keyword">function</span> handler(event) {
  var request = event.request;
  var uri = request.uri;
  var hasExtension = /\.[^/]+$/.<span class="hljs-built_in">test</span>(uri);
  <span class="hljs-keyword">if</span> (!hasExtension) {
    <span class="hljs-keyword">if</span> (!uri.endsWith(<span class="hljs-string">'/'</span>)) {
      uri = uri + <span class="hljs-string">'/'</span>;
    }
    request.uri = uri + <span class="hljs-string">'index.html'</span>;
  }
  <span class="hljs-built_in">return</span> request;
}
EOF
  publish = <span class="hljs-literal">true</span>
}

resource <span class="hljs-string">"aws_cloudfront_distribution"</span> <span class="hljs-string">"cdn"</span> {
  origin {
    domain_name              = aws_s3_bucket.site.bucket_regional_domain_name
    origin_id                = <span class="hljs-string">"my-hello-website-origin"</span>
    origin_access_control_id = aws_cloudfront_origin_access_control.oac.id
  }

  enabled             = <span class="hljs-literal">true</span>
  is_ipv6_enabled     = <span class="hljs-literal">true</span>
  default_root_object = <span class="hljs-string">"index.html"</span>
  comment             = <span class="hljs-string">"My Hello Website Distribution"</span>

  default_cache_behavior {
    allowed_methods  = [<span class="hljs-string">"GET"</span>, <span class="hljs-string">"HEAD"</span>, <span class="hljs-string">"OPTIONS"</span>]
    cached_methods   = [<span class="hljs-string">"GET"</span>, <span class="hljs-string">"HEAD"</span>]
    target_origin_id = <span class="hljs-string">"my-hello-website-origin"</span>
    viewer_protocol_policy = <span class="hljs-string">"redirect-to-https"</span>
    compress               = <span class="hljs-literal">true</span>
    cache_policy_id        = <span class="hljs-string">"658327ea-f89d-4fab-a63d-7e88639e58f6"</span>

    function_association {
      event_type   = <span class="hljs-string">"viewer-request"</span>
      function_arn = aws_cloudfront_function.rewrite_request.arn
    }
  }

  custom_error_response {
    error_code         = 404
    response_code      = 200
    response_page_path = <span class="hljs-string">"/index.html"</span>
  }

  custom_error_response {
    error_code         = 403
    response_code      = 200
    response_page_path = <span class="hljs-string">"/index.html"</span>
  }

  price_class = var.cloudfront_price_class

  restrictions {
    geo_restriction {
      restriction_type = <span class="hljs-string">"none"</span>
    }
  }

  viewer_certificate {
    cloudfront_default_certificate = <span class="hljs-literal">true</span>
  }

  wait_for_deployment = <span class="hljs-literal">true</span>

  lifecycle {
    ignore_changes = [web_acl_id, price_class]
  }

  tags = merge(var.tags, {
    Name = <span class="hljs-string">"my-hello-website-distribution"</span>
  })
}

resource <span class="hljs-string">"aws_s3_bucket_policy"</span> <span class="hljs-string">"policy"</span> {
  bucket = aws_s3_bucket.site.id
  depends_on = [aws_cloudfront_distribution.cdn]

  policy = jsonencode({
    Version = <span class="hljs-string">"2012-10-17"</span>
    Statement = [
      {
        Sid    = <span class="hljs-string">"AllowCloudFrontServicePrincipal"</span>
        Effect = <span class="hljs-string">"Allow"</span>
        Principal = {
          Service = <span class="hljs-string">"cloudfront.amazonaws.com"</span>
        }
        Action   = <span class="hljs-string">"s3:GetObject"</span>
        Resource = <span class="hljs-string">"<span class="hljs-variable">${aws_s3_bucket.site.arn}</span>/*"</span>
        Condition = {
          StringEquals = {
            <span class="hljs-string">"AWS:SourceArn"</span> = aws_cloudfront_distribution.cdn.arn
          }
        }
      }
    ]
  })
}

output <span class="hljs-string">"s3_bucket_name"</span> {
  value = aws_s3_bucket.site.id
}

output <span class="hljs-string">"s3_bucket_arn"</span> {
  value = aws_s3_bucket.site.arn
}

output <span class="hljs-string">"cloudfront_distribution_id"</span> {
  value = aws_cloudfront_distribution.cdn.id
}

output <span class="hljs-string">"cloudfront_distribution_arn"</span> {
  value = aws_cloudfront_distribution.cdn.arn
}

output <span class="hljs-string">"cloudfront_url"</span> {
  value = <span class="hljs-string">"https://<span class="hljs-variable">${aws_cloudfront_distribution.cdn.domain_name}</span>"</span>
}

output <span class="hljs-string">"cloudfront_domain_name"</span> {
  value = aws_cloudfront_distribution.cdn.domain_name
}
</code></pre>
<h3 id="heading-variablestf"><code>variables.tf</code></h3>
<pre><code class="lang-bash">variable <span class="hljs-string">"bucket_name"</span> {
  <span class="hljs-built_in">type</span>        = string
  default     = <span class="hljs-string">"my-hello-website"</span>
}

variable <span class="hljs-string">"aws_region"</span> {
  <span class="hljs-built_in">type</span>        = string
  default     = <span class="hljs-string">"ap-south-1"</span>
}

variable <span class="hljs-string">"cloudfront_price_class"</span> {
  <span class="hljs-built_in">type</span>        = string
  default     = <span class="hljs-string">"PriceClass_100"</span>
  validation {
    condition = contains([
      <span class="hljs-string">"PriceClass_All"</span>,
      <span class="hljs-string">"PriceClass_200"</span>,
      <span class="hljs-string">"PriceClass_100"</span>
    ], var.cloudfront_price_class)
    error_message = <span class="hljs-string">"Price class must be one of: PriceClass_All, PriceClass_200, PriceClass_100"</span>
  }
}

variable <span class="hljs-string">"enable_versioning"</span> {
  <span class="hljs-built_in">type</span>        = bool
  default     = <span class="hljs-literal">true</span>
}

variable <span class="hljs-string">"tags"</span> {
  <span class="hljs-built_in">type</span>        = map(string)
  default = {
    Project     = <span class="hljs-string">"my-hello-website"</span>
    Environment = <span class="hljs-string">"production"</span>
    ManagedBy   = <span class="hljs-string">"terraform"</span>
  }
}

variable <span class="hljs-string">"state_storage_bucket_name"</span> {
  <span class="hljs-built_in">type</span>        = string
  default     = <span class="hljs-string">"my-hello-website-terraform-state"</span>
}
</code></pre>
<h2 id="heading-step-5-deploy-infrastructure">Step 5: Deploy infrastructure</h2>
<pre><code class="lang-bash">terraform init
terraform plan
terraform apply
</code></pre>
<p>Terraform now creates everything correctly without manual AWS work.</p>
<p><strong>Note:</strong> By default, CloudFront may create a <strong>Pay-As-You-Go</strong> pricing plan, which can result in usage-based charges. To avoid unexpected billing, follow the steps below to switch to the <strong>Free plan</strong>.</p>
<p><code>AWS Console → CloudFront → Distributions → select my_hello_website → click Distribution ID → Switch to a plan → select Free plan → Review changes → Submit</code></p>
<h2 id="heading-step-6-github-actions-for-automatic-deployment">Step 6: GitHub Actions for automatic deployment</h2>
<h3 id="heading-goal">Goal</h3>
<p>Every time I push to <code>main</code> on github:</p>
<ul>
<li><p>Frontend builds</p>
</li>
<li><p>Your <code>dist</code> or <code>build</code> folder get uploaded to S3</p>
</li>
<li><p>CloudFront cache invalidates which refresh your website contents immediately</p>
</li>
</ul>
<h3 id="heading-githubworkflowsdeployyml"><code>.github/workflows/deploy.yml</code></h3>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">Next.js</span> <span class="hljs-string">to</span> <span class="hljs-string">S3</span> <span class="hljs-string">via</span> <span class="hljs-string">CloudFront</span>

<span class="hljs-comment"># This workflow deploys the Next.js app to S3 and invalidates CloudFront cache</span>
<span class="hljs-comment"># Infrastructure (S3 bucket, CloudFront distribution, bucket policies) is managed by Terraform</span>
<span class="hljs-comment"># See infra/ directory for infrastructure as code</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-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">env:</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-attr">S3_BUCKET:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.S3_BUCKET</span> <span class="hljs-string">}}</span>
      <span class="hljs-attr">CLOUDFRONT_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-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span> <span class="hljs-string">code</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">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">Setup</span> <span class="hljs-string">pnpm</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">pnpm/action-setup@v4</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">version:</span> <span class="hljs-number">9</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Get</span> <span class="hljs-string">pnpm</span> <span class="hljs-string">store</span> <span class="hljs-string">directory</span>
        <span class="hljs-attr">shell:</span> <span class="hljs-string">bash</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          echo "STORE_PATH=$(pnpm store path --silent)" &gt;&gt; $GITHUB_ENV
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">pnpm</span> <span class="hljs-string">cache</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/cache@v4</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">${{</span> <span class="hljs-string">env.STORE_PATH</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">key:</span> <span class="hljs-string">${{</span> <span class="hljs-string">runner.os</span> <span class="hljs-string">}}-pnpm-store-${{</span> <span class="hljs-string">hashFiles('**/pnpm-lock.yaml')</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">restore-keys:</span> <span class="hljs-string">|
            ${{ runner.os }}-pnpm-store-
</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">pnpm</span> <span class="hljs-string">install</span> <span class="hljs-string">--no-frozen-lockfile</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">Next.js</span> <span class="hljs-string">project</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">pnpm</span> <span class="hljs-string">build</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Verify</span> <span class="hljs-string">build</span> <span class="hljs-string">output</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          echo "🔍 Checking build output..."
          if [ ! -d "out" ]; then
            echo "❌ Error: 'out' directory not found!"
            echo "Build might have failed. Checking for errors..."
            exit 1
          fi
          echo "✅ Build output directory exists"
          echo "📁 Contents of out/ directory:"
          ls -la out/ | head -20
          echo "📊 Total files: $(find out -type f | wc -l)"
</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">aws-access-key-id:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.AWS_ACCESS_KEY_ID</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">aws-secret-access-key:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.AWS_SECRET_ACCESS_KEY</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">Verify</span> <span class="hljs-string">AWS</span> <span class="hljs-string">access</span> <span class="hljs-string">and</span> <span class="hljs-string">S3</span> <span class="hljs-string">bucket</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          echo "🔍 Verifying AWS credentials..."
          aws sts get-caller-identity
</span>
          <span class="hljs-string">echo</span> <span class="hljs-string">"🔍 Checking S3 bucket access..."</span>
          <span class="hljs-string">if</span> <span class="hljs-string">aws</span> <span class="hljs-string">s3</span> <span class="hljs-string">ls</span> <span class="hljs-string">s3://${{</span> <span class="hljs-string">secrets.S3_BUCKET</span> <span class="hljs-string">}}</span> <span class="hljs-number">2</span><span class="hljs-string">&gt;&amp;1;</span> <span class="hljs-string">then</span>
            <span class="hljs-string">echo</span> <span class="hljs-string">"✅ S3 bucket accessible: $<span class="hljs-template-variable">{{ secrets.S3_BUCKET }}</span>"</span>
            <span class="hljs-string">echo</span> <span class="hljs-string">"📁 Current files in bucket:"</span>
            <span class="hljs-string">aws</span> <span class="hljs-string">s3</span> <span class="hljs-string">ls</span> <span class="hljs-string">s3://${{</span> <span class="hljs-string">secrets.S3_BUCKET</span> <span class="hljs-string">}}</span> <span class="hljs-string">--recursive</span> <span class="hljs-string">|</span> <span class="hljs-string">head</span> <span class="hljs-number">-10</span>
          <span class="hljs-string">else</span>
            <span class="hljs-string">echo</span> <span class="hljs-string">"❌ Error: Cannot access S3 bucket: $<span class="hljs-template-variable">{{ secrets.S3_BUCKET }}</span>"</span>
            <span class="hljs-string">echo</span> <span class="hljs-string">"Please check:"</span>
            <span class="hljs-string">echo</span> <span class="hljs-string">"  1. Bucket name is correct"</span>
            <span class="hljs-string">echo</span> <span class="hljs-string">"  2. AWS credentials have s3:ListBucket permission"</span>
            <span class="hljs-string">exit</span> <span class="hljs-number">1</span>
          <span class="hljs-string">fi</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">build</span> <span class="hljs-string">to</span> <span class="hljs-string">S3</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          set -e  # Exit on any error
</span>
          <span class="hljs-string">echo</span> <span class="hljs-string">"📤 Starting S3 upload..."</span>
          <span class="hljs-string">echo</span> <span class="hljs-string">"📦 Bucket: $<span class="hljs-template-variable">{{ secrets.S3_BUCKET }}</span>"</span>
          <span class="hljs-string">echo</span> <span class="hljs-string">"📁 Source: out/"</span>

          <span class="hljs-comment"># Upload static assets (JS, CSS, images, etc.) with long cache</span>
          <span class="hljs-string">echo</span> <span class="hljs-string">"📤 Uploading static assets..."</span>
          <span class="hljs-string">aws</span> <span class="hljs-string">s3</span> <span class="hljs-string">sync</span> <span class="hljs-string">out/</span> <span class="hljs-string">s3://${{</span> <span class="hljs-string">secrets.S3_BUCKET</span> <span class="hljs-string">}}</span> <span class="hljs-string">\</span>
            <span class="hljs-string">--delete</span> <span class="hljs-string">\</span>
            <span class="hljs-string">--exclude</span> <span class="hljs-string">"*.html"</span> <span class="hljs-string">\</span>
            <span class="hljs-string">--exclude</span> <span class="hljs-string">"*.json"</span> <span class="hljs-string">\</span>
            <span class="hljs-string">--cache-control</span> <span class="hljs-string">"public, max-age=31536000, immutable"</span> <span class="hljs-string">\</span>
            <span class="hljs-string">--no-progress</span> <span class="hljs-string">||</span> {
              <span class="hljs-string">echo</span> <span class="hljs-string">"❌ Failed to upload static assets"</span>
              <span class="hljs-string">exit</span> <span class="hljs-number">1</span>
            }

          <span class="hljs-comment"># Upload HTML files with no-cache for immediate updates</span>
          <span class="hljs-string">echo</span> <span class="hljs-string">"📤 Uploading HTML files..."</span>
          <span class="hljs-string">aws</span> <span class="hljs-string">s3</span> <span class="hljs-string">sync</span> <span class="hljs-string">out/</span> <span class="hljs-string">s3://${{</span> <span class="hljs-string">secrets.S3_BUCKET</span> <span class="hljs-string">}}</span> <span class="hljs-string">\</span>
            <span class="hljs-string">--exclude</span> <span class="hljs-string">"*"</span> <span class="hljs-string">\</span>
            <span class="hljs-string">--include</span> <span class="hljs-string">"*.html"</span> <span class="hljs-string">\</span>
            <span class="hljs-string">--cache-control</span> <span class="hljs-string">"public, max-age=0, must-revalidate"</span> <span class="hljs-string">\</span>
            <span class="hljs-string">--content-type</span> <span class="hljs-string">"text/html"</span> <span class="hljs-string">\</span>
            <span class="hljs-string">--no-progress</span> <span class="hljs-string">||</span> {
              <span class="hljs-string">echo</span> <span class="hljs-string">"❌ Failed to upload HTML files"</span>
              <span class="hljs-string">exit</span> <span class="hljs-number">1</span>
            }

          <span class="hljs-comment"># Upload JSON files (like _next/static files) with appropriate cache</span>
          <span class="hljs-string">echo</span> <span class="hljs-string">"📤 Uploading JSON files..."</span>
          <span class="hljs-string">aws</span> <span class="hljs-string">s3</span> <span class="hljs-string">sync</span> <span class="hljs-string">out/</span> <span class="hljs-string">s3://${{</span> <span class="hljs-string">secrets.S3_BUCKET</span> <span class="hljs-string">}}</span> <span class="hljs-string">\</span>
            <span class="hljs-string">--exclude</span> <span class="hljs-string">"*"</span> <span class="hljs-string">\</span>
            <span class="hljs-string">--include</span> <span class="hljs-string">"*.json"</span> <span class="hljs-string">\</span>
            <span class="hljs-string">--cache-control</span> <span class="hljs-string">"public, max-age=31536000, immutable"</span> <span class="hljs-string">\</span>
            <span class="hljs-string">--content-type</span> <span class="hljs-string">"application/json"</span> <span class="hljs-string">\</span>
            <span class="hljs-string">--no-progress</span> <span class="hljs-string">||</span> {
              <span class="hljs-string">echo</span> <span class="hljs-string">"❌ Failed to upload JSON files"</span>
              <span class="hljs-string">exit</span> <span class="hljs-number">1</span>
            }

          <span class="hljs-string">echo</span> <span class="hljs-string">"✅ Files uploaded successfully!"</span>
          <span class="hljs-string">echo</span> <span class="hljs-string">"📊 Verifying upload..."</span>
          <span class="hljs-string">aws</span> <span class="hljs-string">s3</span> <span class="hljs-string">ls</span> <span class="hljs-string">s3://${{</span> <span class="hljs-string">secrets.S3_BUCKET</span> <span class="hljs-string">}}</span> <span class="hljs-string">--recursive</span> <span class="hljs-string">|</span> <span class="hljs-string">wc</span> <span class="hljs-string">-l</span> <span class="hljs-string">|</span> <span class="hljs-string">xargs</span> <span class="hljs-string">echo</span> <span class="hljs-string">"Total files in bucket:"</span>
          <span class="hljs-string">echo</span> <span class="hljs-string">"ℹ️  S3 bucket policy and CloudFront are managed by Terraform"</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-attr">if:</span> <span class="hljs-string">env.CLOUDFRONT_DISTRIBUTION_ID</span> <span class="hljs-type">!=</span> <span class="hljs-string">''</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          echo "🔄 Invalidating CloudFront cache..."
          aws cloudfront create-invalidation \
            --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \
            --paths "/*"
          echo "✅ CloudFront cache invalidation initiated"
          echo "🌐 Website will be updated within 1-5 minutes"</span>
</code></pre>
<hr />
<h2 id="heading-step-7-github-secrets-required">Step 7: GitHub Secrets required</h2>
<pre><code class="lang-bash">AWS_ACCESS_KEY_ID = iam_access_key
AWS_SECRET_ACCESS_KEY = iam_secret_key
CLOUDFRONT_DISTRIBUTION_ID = you_got_after_running_terraform_apply
S3_BUCKET = my-hello-website
AWS_REGION = ap-south-1
</code></pre>
<h2 id="heading-deployment-flow-now">Deployment flow now</h2>
<ol>
<li><p>Push code to <code>main</code></p>
</li>
<li><p>GitHub Actions runs</p>
</li>
<li><p>Build generated</p>
</li>
<li><p>Files uploaded to S3</p>
</li>
<li><p>CloudFront cache invalidated</p>
</li>
<li><p>Website updated globally</p>
</li>
</ol>
<p><strong>No manual steps.</strong></p>
<h2 id="heading-common-mistakes-i-made-learn-from-this">Common mistakes I made (learn from this)</h2>
<ul>
<li><p>Mixing S3 website hosting with CloudFront</p>
</li>
<li><p>Editing policies in the wrong region</p>
</li>
<li><p>Doing infra manually in AWS console</p>
</li>
<li><p>Not using remote Terraform state</p>
</li>
<li><p>Forgetting CloudFront invalidation</p>
</li>
</ul>
<p>Terraform + GitHub Actions solved all of this.</p>
<h2 id="heading-final-result">Final result</h2>
<ul>
<li><p>The website is HTTPS enabled</p>
</li>
<li><p>Fast for global delivery</p>
</li>
<li><p>Your S3 code in the bucket is safe</p>
</li>
<li><p>Automatic redeployments for further updates</p>
</li>
<li><p>Repeatable setup — you can follow the same approach for any frontend application</p>
</li>
<li><p>No headache of checking whether AWS is billing for unwanted resources; you have clear visibility into the resources used per project, which helps you better understand and manage AWS billing</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>If you’re stuck after building a website and don’t know how to deploy it properly, this setup will save you days of frustration.</p>
<p>Once automated, shipping a frontend becomes easy and hassle-free.</p>
<p><strong>If you like this approach, don’t forget to</strong> <code>like</code> <strong>and</strong> <code>share</code> <strong>the blog.</strong></p>
]]></content:encoded></item></channel></rss>