<?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" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Terraform - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Terraform - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sat, 23 May 2026 22:20:08 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/terraform/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Migrate to S3 Native State Locking in Terraform ]]>
                </title>
                <description>
                    <![CDATA[ If you've been running Terraform on AWS for any length of time, you know the setup: an S3 bucket for state storage, a DynamoDB table for state locking, and a handful of IAM policies tying them togethe ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-migrate-to-s3-native-state-locking-in-terraform/</link>
                <guid isPermaLink="false">69fd19239f93a850a430069b</guid>
                
                    <category>
                        <![CDATA[ Devops ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Terraform ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Cloud Computing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Infrastructure as code ]]>
                    </category>
                
                    <category>
                        <![CDATA[ S3 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tolani Akintayo ]]>
                </dc:creator>
                <pubDate>Thu, 07 May 2026 22:58:43 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/9619ad45-15c5-4be7-9221-ed4b76bc2b24.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you've been running Terraform on AWS for any length of time, you know the setup: an S3 bucket for state storage, a DynamoDB table for state locking, and a handful of IAM policies tying them together. It works. It has worked for years.</p>
<p>But it has always carried a cost that rarely gets discussed openly. That cost isn't just money, though a DynamoDB table with on-demand billing adds up across multiple teams and environments.</p>
<p>The real cost is complexity. Every new AWS environment needs both resources provisioned before Terraform can manage anything else. Every engineer who sets up their first Terraform backend has to understand why two completely different AWS services are responsible for what is logically one thing: storing and protecting state. And every incident involving a stuck lock has required someone to manually delete a record from DynamoDB to unblock the team.</p>
<p>In November 2024, AWS announced that S3 now supports native object locking for Terraform state files, meaning <strong>DynamoDB is no longer required for state locking</strong>. Terraform 1.10 added support for this feature, and it's now generally available.</p>
<p>In this tutorial, you'll learn:</p>
<ul>
<li><p>What S3 native locking is and how it works</p>
</li>
<li><p>How to set it up from scratch if you're starting a new project</p>
</li>
<li><p>How to migrate an existing S3 + DynamoDB setup to S3 native locking safely</p>
</li>
<li><p>How to verify locking is working and handle edge cases</p>
</li>
</ul>
<p>By the end, you'll have a simpler, cleaner Terraform backend with one fewer AWS resource to manage.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-what-is-terraform-state-locking">What Is Terraform State Locking?</a></p>
</li>
<li><p><a href="#heading-what-is-s3-native-state-locking">What Is S3 Native State Locking?</a></p>
</li>
<li><p><a href="#heading-how-s3-native-locking-compares-to-the-s3-dynamodb-approach">How S3 Native Locking Compares to the S3 + DynamoDB Approach</a></p>
</li>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-part-1-fresh-setup-how-to-configure-s3-native-locking-from-scratch">Part 1: Fresh Setup – How to Configure S3 Native Locking from Scratch</a></p>
<ul>
<li><p><a href="#heading-step-1-create-the-s3-bucket-with-versioning-and-encryption">Step 1: Create the S3 Bucket with Versioning and Encryption</a></p>
</li>
<li><p><a href="#heading-step-2-configure-the-terraform-backend-with-native-locking">Step 2: Configure the Terraform Backend with Native Locking</a></p>
</li>
<li><p><a href="#heading-step-3-initialize-and-verify">Step 3: Initialize and Verify</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-part-2-migration-how-to-move-from-s3-dynamodb-to-s3-native-locking">Part 2: Migration – How to Move from S3 + DynamoDB to S3 Native Locking</a></p>
<ul>
<li><p><a href="#heading-step-1-verify-your-current-setup">Step 1: Verify Your Current Setup</a></p>
</li>
<li><p><a href="#heading-step-2-enable-object-lock-on-the-existing-s3-bucket">Step 2: Enable Object Lock on the Existing S3 Bucket</a></p>
</li>
<li><p><a href="#heading-step-3-update-the-terraform-backend-configuration">Step 3: Update the Terraform Backend Configuration</a></p>
</li>
<li><p><a href="#heading-step-4-reinitialize-terraform">Step 4: Reinitialize Terraform</a></p>
</li>
<li><p><a href="#heading-step-5-verify-the-migration">Step 5: Verify the Migration</a></p>
</li>
<li><p><a href="#heading-step-6-clean-up-the-dynamodb-table">Step 6: Clean Up the DynamoDB Table</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-how-to-verify-that-locking-is-working">How to Verify That Locking Is Working</a></p>
</li>
<li><p><a href="#heading-how-to-handle-a-stuck-lock">How to Handle a Stuck Lock</a></p>
</li>
<li><p><a href="#heading-rollback-plan-if-something-goes-wrong">Rollback Plan: If Something Goes Wrong</a></p>
</li>
<li><p><a href="#heading-security-best-practices-for-your-state-bucket">Security Best Practices for Your State Bucket</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a href="#heading-references">References</a></p>
</li>
</ul>
<h2 id="heading-what-is-terraform-state-locking">What is Terraform State Locking?</h2>
<p>Before looking at the new approach, it helps to understand what state locking is solving.</p>
<p>Terraform stores everything it knows about your infrastructure in a <strong>state file</strong> – a JSON document that maps your configuration to real AWS resources. When you run <code>terraform apply</code>, Terraform reads this file, calculates the difference between the current state and your configuration, and makes the necessary changes.</p>
<p>The problem arises when two engineers or two CI/CD pipelines run and try to apply changes at the same time. If both read the state file simultaneously, calculate changes independently, and both try to write back, you get a <strong>race condition</strong>. The second write overwrites changes from the first, and your state is now out of sync with reality. This is a serious problem that can cause resources to be untracked, doubled, or destroyed unexpectedly.</p>
<p><strong>State locking</strong> solves this by creating a lock when any operation starts that could modify state. If a lock already exists, Terraform refuses to proceed and reports who holds the lock and when it was acquired. Only one operation can hold the lock at a time. When the operation completes, the lock is released.</p>
<pre><code class="language-plaintext">Terraform Run A                 State File / Lock                Terraform Run B
(User 1)                         (S3/DynamoDB)                   (User 2)

   |                                   |                            |
   |------- 1. Acquire Lock ----------&gt;|                            |
   |                                   |                            |
   |&lt;------ 2. Lock Granted -----------|                            |
   |                                   |                            |
   |                                   |------- 3. Acquire Lock ---&gt;|
   |            [PROCESSING]           |                            |
   |      (Modifying Infrastructure)   |&lt;------ 4. Lock Denied -----|
   |                                   |        (Wait / Retry)      |
   |                                   |                            |
   |------- 5. Release Lock ----------&gt;|                            |
   |                                   |                            |
   |           [COMPLETED]             |&lt;------ 6. Lock Granted ----|
   |                                   |                            |
   |                                   |       [PROCESSING]         |
   |                                   | (Modifying Infrastructure) |              
   |                                   |                            |
</code></pre>
<h2 id="heading-what-is-s3-native-state-locking">What Is S3 Native State Locking?</h2>
<p>Previously, Terraform's S3 backend used a DynamoDB table as the locking mechanism. When a lock was needed, Terraform wrote a record to DynamoDB with a <code>LockID</code> primary key. DynamoDB's conditional writes guaranteed that only one process could create that record, which is what made the locking atomic.</p>
<p>S3 native locking uses <strong>S3 Object Lock</strong> instead. S3 Object Lock is an S3 feature originally designed to enforce WORM (Write Once, Read Many) compliance for regulatory requirements. AWS extended this capability to support Terraform's state locking workflow.</p>
<p>When S3 native locking is enabled in your Terraform backend:</p>
<ol>
<li><p>Terraform writes your state to an <code>.tfstate</code> object in S3 (as before)</p>
</li>
<li><p>To acquire a lock, Terraform uses <strong>S3's conditional write operations</strong> – specifically the <code>if-none-match</code> conditional header to create a lock file atomically</p>
</li>
<li><p>If the lock file already exists, S3 rejects the write, and Terraform reports that a lock is held</p>
</li>
<li><p>When the operation completes, Terraform deletes the lock file to release the lock.</p>
</li>
</ol>
<p>The key difference from DynamoDB: the entire locking mechanism lives inside S3. No second service. No second set of IAM permissions. No second resource to provision.</p>
<p><strong>Note:</strong> This feature requires Terraform version <strong>1.10.0 or later</strong> and an S3 bucket with <strong>Object Lock enabled</strong>. Object Lock must be enabled at bucket creation time. You can't enable it on an existing bucket through the console or CLI. But there is a supported workaround for existing buckets, which we'll cover in Part 2.</p>
<h2 id="heading-how-s3-native-locking-compares-to-the-s3-dynamodb-approach">How S3 Native Locking Compares to the S3 + DynamoDB Approach</h2>
<table>
<thead>
<tr>
<th><strong>Aspect</strong></th>
<th><strong>S3 + DynamoDB (Old)</strong></th>
<th><strong>S3 Native Locking (New)</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>AWS services required</strong></td>
<td>S3 + DynamoDB</td>
<td>S3 only</td>
</tr>
<tr>
<td><strong>IAM permissions needed</strong></td>
<td>S3 + DynamoDB permissions</td>
<td>S3 permissions only</td>
</tr>
<tr>
<td><strong>Terraform version</strong></td>
<td>Any</td>
<td>1.10.0 or later</td>
</tr>
<tr>
<td><strong>Setup complexity</strong></td>
<td>Two resources, two IAM scopes</td>
<td>One resource</td>
</tr>
<tr>
<td><strong>Stuck lock resolution</strong></td>
<td>Delete DynamoDB record</td>
<td>Delete S3 lock file</td>
</tr>
<tr>
<td><strong>Cost</strong></td>
<td>S3 storage + DynamoDB on-demand</td>
<td>S3 storage only</td>
</tr>
<tr>
<td><strong>Object Lock requirement</strong></td>
<td>Not required</td>
<td>Required on S3 bucket</td>
</tr>
<tr>
<td><strong>Locking mechanism</strong></td>
<td>DynamoDB conditional writes</td>
<td>S3 conditional writes (<code>if-none-match</code>)</td>
</tr>
<tr>
<td><strong>State versioning</strong></td>
<td>S3 Versioning (recommended)</td>
<td>S3 Versioning (required for full safety)</td>
</tr>
</tbody></table>
<p>The functional behavior from Terraform's perspective is identical. Locking works the same way. The lock information displayed when a lock is held has the same structure. The only difference is what happens under the hood.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before you start, make sure you have the following in place:</p>
<ul>
<li><strong>Terraform 1.10.0 or later</strong> installed. Check your version:</li>
</ul>
<pre><code class="language-shell">terraform version
</code></pre>
<p>If you need to upgrade, follow the <a href="https://developer.hashicorp.com/terraform/install">official upgrade guide</a>.</p>
<ul>
<li><strong>AWS CLI</strong> installed and configured with credentials that have permission to create and manage S3 buckets.</li>
</ul>
<pre><code class="language-shell">aws --version
aws sts get-caller-identity   # confirm you're authenticated
</code></pre>
<ul>
<li><p><strong>IAM permissions</strong> to perform the following S3 actions:</p>
<ul>
<li><p><code>s3:CreateBucket</code></p>
</li>
<li><p><code>s3:PutBucketVersioning</code></p>
</li>
<li><p><code>s3:PutBucketEncryption</code></p>
</li>
<li><p><code>s3:PutObjectLegalHold</code></p>
</li>
<li><p><code>s3:PutObjectRetention</code></p>
</li>
<li><p><code>s3:GetObject</code></p>
</li>
<li><p><code>s3:PutObject</code></p>
</li>
<li><p><code>s3:DeleteObject</code></p>
</li>
<li><p><code>s3:ListBucket</code></p>
</li>
</ul>
</li>
<li><p>For the <strong>migration path</strong>: access to your existing Terraform project and the S3 bucket and DynamoDB table currently in use.</p>
</li>
</ul>
<h2 id="heading-part-1-fresh-setup-how-to-configure-s3-native-locking-from-scratch">Part 1: Fresh Setup – How to Configure S3 Native Locking from Scratch</h2>
<p>Follow this section if you're starting a new Terraform project and want to use S3 native locking from the beginning.</p>
<h3 id="heading-step-1-create-the-s3-bucket-with-versioning-and-encryption">Step 1: Create the S3 Bucket with Versioning and Encryption</h3>
<p>Object Lock <strong>must be enabled at bucket creation time</strong>. You can't add it afterward through the standard console flow. Create the bucket using the AWS CLI with Object Lock enabled:</p>
<pre><code class="language-shell">aws s3api create-bucket \
  --bucket your-project-terraform-state \
  --region us-east-1 \
  --object-lock-enabled-for-bucket
</code></pre>
<p><strong>Note:</strong> For regions other than <code>us-east-1</code>, add the <code>--create-bucket-configuration</code> flag.</p>
<pre><code class="language-shell">aws s3api create-bucket \
  --bucket your-project-terraform-state \
  --region eu-west-1 \
  --create-bucket-configuration LocationConstraint=eu-west-1 \
  --object-lock-enabled-for-bucket
</code></pre>
<p>Now enable versioning on the bucket. Versioning is required alongside Object Lock and allows Terraform to recover previous state versions if something goes wrong:</p>
<pre><code class="language-shell">aws s3api put-bucket-versioning \
  --bucket your-project-terraform-state \
  --versioning-configuration Status=Enabled
</code></pre>
<p>Enable server-side encryption so your state files are encrypted at rest:</p>
<pre><code class="language-shell">aws s3api put-bucket-encryption \
  --bucket your-project-terraform-state \
  --server-side-encryption-configuration '{
    "Rules": [
      {
        "ApplyServerSideEncryptionByDefault": {
          "SSEAlgorithm": "AES256"
        },
        "BucketKeyEnabled": true
      }
    ]
  }'
</code></pre>
<p>Block all public access to the bucket. A Terraform state file contains resource IDs, IP addresses, and potentially sensitive values. It should never be publicly accessible:</p>
<pre><code class="language-shell">aws s3api put-public-access-block \
  --bucket your-project-terraform-state \
  --public-access-block-configuration \
    "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
</code></pre>
<p>Verify the bucket configuration:</p>
<pre><code class="language-shell"># Confirm Object Lock is enabled
aws s3api get-object-lock-configuration \
  --bucket your-project-terraform-state
 
# Confirm versioning is enabled
aws s3api get-bucket-versioning \
  --bucket your-project-terraform-state
 
# Confirm encryption is configured
aws s3api get-bucket-encryption \
  --bucket your-project-terraform-state
</code></pre>
<p>Expected output for the Object Lock check:</p>
<pre><code class="language-json">{
    "ObjectLockConfiguration": {
        "ObjectLockEnabled": "Enabled"
    }
}
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/65a5bfab4c73b29396c0b895/2b2e56cf-687f-4932-a61e-ed7cc33ea6f1.png" alt="Terminal showing AWS CLI verification commands confirming S3 bucket is configured correctly with Object Lock, versioning, and encryption enabled" style="display:block;margin:0 auto" width="1120" height="616" loading="lazy">

<h3 id="heading-step-2-configure-the-terraform-backend-with-native-locking">Step 2: Configure the Terraform Backend with Native Locking</h3>
<p>In your Terraform project, create or update your <code>backend.tf</code> file:</p>
<pre><code class="language-hcl">terraform {
  backend "s3" {
    bucket = "your-project-terraform-state"
    key    = "production/terraform.tfstate"
    region = "us-east-1"
 
    # Enable S3 native state locking
    # Requires Terraform 1.10.0+ and a bucket with Object Lock enabled
    use_lockfile = true
 
    # Encryption at rest
    encrypt = true
  }
}
</code></pre>
<p>The critical difference from the old configuration is the <code>use_lockfile = true</code> parameter. Notice what is <strong>absent</strong>: there's no <code>dynamodb_table</code> argument. No DynamoDB table. No second service.</p>
<p>Here's a direct comparison of the old and new configurations:</p>
<p><strong>Old configuration (S3 + DynamoDB):</strong></p>
<pre><code class="language-hcl">terraform {
  backend "s3" {
    bucket         = "your-project-terraform-state"
    key            = "production/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"   # this goes away
  }
}
</code></pre>
<p><strong>New configuration (S3 native locking):</strong></p>
<pre><code class="language-hcl">terraform {
  backend "s3" {
    bucket       = "your-project-terraform-state"
    key          = "production/terraform.tfstate"
    region       = "us-east-1"
    encrypt      = true
    use_lockfile = true   # this replaces dynamodb_table
  }
}
</code></pre>
<h3 id="heading-step-3-initialize-and-verify">Step 3: Initialize and Verify</h3>
<p>Run <code>terraform init</code> to initialize the backend:</p>
<pre><code class="language-shell">terraform init
</code></pre>
<p>Expected output:</p>
<pre><code class="language-plaintext">Initializing the backend...
 
Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.
 
Initializing provider plugins...
 
Terraform has been successfully initialized!
</code></pre>
<p>Run a plan to confirm everything is working end-to-end:</p>
<pre><code class="language-shell">terraform plan
</code></pre>
<p>If locking is working, you'll see a brief pause while Terraform acquires the lock before the plan output appears. You'll also see the lock information if you look at the S3 bucket&nbsp;– a <code>.tflock</code> file will appear temporarily alongside your state file during the operation and disappear when it completes.</p>
<h2 id="heading-part-2-migration-how-to-move-from-s3-dynamodb-to-s3-native-locking">Part 2: Migration&nbsp;– How to Move from S3 + DynamoDB to S3 Native Locking</h2>
<p>Follow this section if you have an <strong>existing Terraform setup</strong> using an S3 bucket and DynamoDB table for state locking, and you want to migrate to S3 native locking.</p>
<p><strong>Important:</strong> Migration requires a maintenance window or at minimum a period where no Terraform operations are running. You're changing the backend configuration, which means <strong>all team members and CI/CD pipelines must stop running</strong> <code>terraform plan</code> <strong>or</strong> <code>terraform apply</code> <strong>during the migration</strong>. The migration itself takes under 10 minutes.</p>
<h3 id="heading-step-1-verify-your-current-setup">Step 1: Verify Your Current Setup</h3>
<p>Before making any changes, document your existing backend configuration and confirm the state file is accessible:</p>
<pre><code class="language-shell"># Confirm your state file is in S3
aws s3 ls s3://your-existing-bucket/path/to/terraform.tfstate
 
# Confirm the DynamoDB table exists
aws dynamodb describe-table \
  --table-name your-dynamodb-lock-table \
  --query 'Table.TableStatus'
</code></pre>
<p>Check your current <code>backend.tf</code> and note the exact values:</p>
<pre><code class="language-shell"># Your current backend.tf - note these values before changing anything
terraform {
  backend "s3" {
    bucket         = "your-existing-bucket"       # note this
    key            = "path/to/terraform.tfstate"   # note this
    region         = "us-east-1"                   # note this
    encrypt        = true
    dynamodb_table = "your-dynamodb-lock-table"    # this will be removed
  }
}
</code></pre>
<p>Run one final plan to confirm the current state is clean and there are no unexpected changes pending:</p>
<pre><code class="language-shell">terraform plan
</code></pre>
<p>If the plan shows no changes, you're in a safe state to proceed.</p>
<h3 id="heading-step-2-enable-object-lock-on-the-existing-s3-bucket">Step 2: Enable Object Lock on the Existing S3 Bucket</h3>
<p>This is the most important step in the migration. Object Lock can't normally be enabled on an existing bucket. It's a setting that must be configured at creation time.</p>
<p>But AWS provides a way to enable Object Lock on an existing bucket through a support request or through a direct API call that's not exposed in the standard console UI. AWS has officially documented this path for the Terraform migration use case.</p>
<p>Run the following AWS CLI command to enable Object Lock on your <strong>existing</strong> bucket:</p>
<pre><code class="language-bash">aws s3api put-object-lock-configuration \
  --bucket your-existing-bucket \
  --object-lock-configuration '{"ObjectLockEnabled": "Enabled"}'
</code></pre>
<p><strong>Note:</strong> This command enables Object Lock in <strong>governance mode with no default retention</strong>, meaning it enables the locking capability without setting a default retention period on all objects. This is exactly what Terraform's native locking needs: the ability to create and delete lock files, not permanent object retention.</p>
<p>Verify Object Lock is now enabled:</p>
<pre><code class="language-shell">aws s3api get-object-lock-configuration \
  --bucket your-existing-bucket
</code></pre>
<p>Expected output:</p>
<pre><code class="language-json">{
    "ObjectLockConfiguration": {
        "ObjectLockEnabled": "Enabled"
    }
}
</code></pre>
<p>Also verify that versioning is already enabled (it should be if you are running a production Terraform setup):</p>
<pre><code class="language-shell">aws s3api get-bucket-versioning \
  --bucket your-existing-bucket
</code></pre>
<p>Expected output:</p>
<pre><code class="language-json">{
    "Status": "Enabled"
}
</code></pre>
<p>If versioning isn't enabled, enable it before proceeding:</p>
<pre><code class="language-shell">aws s3api put-bucket-versioning \
  --bucket your-existing-bucket \
  --versioning-configuration Status=Enabled
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/65a5bfab4c73b29396c0b895/cd17df01-3d0a-4f93-9250-3f51627e91c8.png" alt="Terminal output showing successful Object Lock enablement on an existing S3 bucket using the AWS CLI" style="display:block;margin:0 auto" width="1204" height="320" loading="lazy">

<h3 id="heading-step-3-update-the-terraform-backend-configuration">Step 3: Update the Terraform Backend Configuration</h3>
<p>Update your <code>backend.tf</code> to remove the <code>dynamodb_table</code> argument and add <code>use_lockfile = true</code>:</p>
<pre><code class="language-hcl">terraform {
  backend "s3" {
    bucket = "your-existing-bucket"
    key    = "path/to/terraform.tfstate"
    region = "us-east-1"
    encrypt = true
 
    # Add this:
    use_lockfile = true
 
    # Remove this line entirely:
    # dynamodb_table = "your-dynamodb-lock-table"
  }
}
</code></pre>
<p>Your updated <code>backend.tf</code> should look like this:</p>
<pre><code class="language-hcl">terraform {
  backend "s3" {
    bucket       = "your-existing-bucket"
    key          = "path/to/terraform.tfstate"
    region       = "us-east-1"
    encrypt      = true
    use_lockfile = true
  }
}
</code></pre>
<h3 id="heading-step-4-reinitialize-terraform">Step 4: Reinitialize Terraform</h3>
<p>Run <code>terraform init</code> with the <code>-reconfigure</code> flag. This flag tells Terraform that the backend configuration has changed intentionally and to reinitialize without prompting you to copy state (the state is already in the same bucket):</p>
<pre><code class="language-shell">terraform init -reconfigure
</code></pre>
<p>Expected output:</p>
<pre><code class="language-plaintext">Initializing the backend...
 
Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.
 
Initializing provider plugins...
- Reusing previous version of hashicorp/aws from the dependency lock file
 
Terraform has been successfully initialized!
</code></pre>
<p><strong>If you see an error here:</strong> The most common cause is that Object Lock wasn't successfully enabled on the bucket. Re-run the verification from Step 2 before proceeding.</p>
<h3 id="heading-step-5-verify-the-migration">Step 5: Verify the Migration</h3>
<p>Run a plan to confirm Terraform is working correctly with the new backend configuration:</p>
<pre><code class="language-shell">terraform plan
</code></pre>
<p>The plan should:</p>
<ul>
<li><p>Complete successfully</p>
</li>
<li><p>Show the same result as the plan you ran in Step 1 (no changes, or the same changes as before)</p>
</li>
<li><p>NOT mention DynamoDB anywhere in its output</p>
</li>
</ul>
<p>To confirm that locking is actually using S3 instead of DynamoDB, open a second terminal and run a plan while the first one is running. You should see the second terminal output a lock error that mentions S3, not DynamoDB:</p>
<pre><code class="language-plaintext">╷
│ Error: Error acquiring the state lock
│
│Error message: operation error S3: PutObject, https response       error StatusCode: 409,
│ RequestID: ..., api error Conflict: Object lock already exists for this key.
│
│ Lock Info:
│   ID:        a1b2c3d4-e5f6-7890-abcd-ef1234567890
│   Path:      your-existing-bucket/path/to/terraform.tfstate.tflock
│   Operation: OperationTypePlan
│   Who:       user@hostname
│   Version:   1.10.0
│   Created:   2026-05-06 14:22:01 UTC
│   Info:
╵
</code></pre>
<p>The <code>Path</code> field shows <code>.tfstate.tflock</code>, a file in your S3 bucket, not a DynamoDB record. This confirms that locking is now handled entirely by S3.</p>
<img src="https://cdn.hashnode.com/uploads/covers/65a5bfab4c73b29396c0b895/e9abb703-af6e-429c-83bb-2ea2dac43a3a.png" alt="Two terminals showing concurrent terraform plan commands, the second one displays a lock error confirming S3 native locking is working" style="display:block;margin:0 auto" width="1264" height="539" loading="lazy">

<h3 id="heading-step-6-clean-up-the-dynamodb-table">Step 6: Clean Up the DynamoDB Table</h3>
<p>Once you've confirmed the migration is working correctly and your team has run at least one successful <code>plan</code> and <code>apply</code> cycle using the new backend, you can remove the DynamoDB table.</p>
<p><strong>Wait at least 24-48 hours before deleting the DynamoDB table</strong> if you have CI/CD pipelines or multiple team members. This gives time to catch any pipeline that wasn't updated with the new backend configuration.</p>
<p>When you're ready, delete the DynamoDB table:</p>
<pre><code class="language-shell">aws dynamodb delete-table \
  --table-name your-dynamodb-lock-table
</code></pre>
<p>Confirm the deletion:</p>
<pre><code class="language-shell">aws dynamodb describe-table \
  --table-name your-dynamodb-lock-table
</code></pre>
<p>Expected output:</p>
<pre><code class="language-plaintext">An error occurred (ResourceNotFoundException) when calling the DescribeTable operation:
Requested resource not found
</code></pre>
<p>This error confirms that the table is gone. The migration is complete.</p>
<p>If you provisioned the DynamoDB table using Terraform (which is the recommended pattern), remove the resource from your Terraform configuration and run <code>terraform apply</code> to destroy it via Terraform rather than the CLI directly. This keeps your state clean:</p>
<pre><code class="language-hcl"># Remove this entire block from your Terraform configuration:
resource "aws_dynamodb_table" "terraform_state_lock" {
  name         = "terraform-state-lock"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "LockID"
 
  attribute {
    name = "LockID"
    type = "S"
  }
}
</code></pre>
<p>After removing the block, run:</p>
<pre><code class="language-bash">terraform apply
</code></pre>
<p>Terraform will detect that the DynamoDB table resource has been removed from configuration and will destroy the table.</p>
<h2 id="heading-how-to-verify-that-locking-is-working">How to Verify That Locking Is Working</h2>
<p>After completing either the fresh setup or the migration, use this procedure to independently verify that locking is functioning correctly.</p>
<h3 id="heading-method-1-observe-the-lock-file-during-an-operation">Method 1: Observe the lock file during an operation</h3>
<p>In one terminal, start a long-running plan against a configuration with many resources:</p>
<pre><code class="language-shell">terraform plan
</code></pre>
<p>While it's running, in a second terminal, check for the lock file in S3:</p>
<pre><code class="language-shell">aws s3 ls s3://your-bucket/path/to/ | grep tflock
</code></pre>
<p>You should see a file like:</p>
<pre><code class="language-plaintext">2026-05-06 14:22:01        512 terraform.tfstate.tflock
</code></pre>
<p>After the plan completes, run the same command again. The <code>.tflock</code> file should be gone.</p>
<h3 id="heading-method-2-read-the-lock-file-contents">Method 2: Read the lock file contents</h3>
<p>While a plan is running, download and read the lock file to see its contents:</p>
<pre><code class="language-shell">aws s3 cp \
  s3://your-bucket/path/to/terraform.tfstate.tflock \
  /tmp/current.lock &amp;&amp; cat /tmp/current.lock
</code></pre>
<p>Expected output (formatted for readability):</p>
<pre><code class="language-json">{
  "ID": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "Operation": "OperationTypePlan",
  "Info": "",
  "Who": "tolani@dev-machine",
  "Version": "1.10.0",
  "Created": "2026-05-06T14:22:01.123456789Z",
  "Path": "your-bucket/path/to/terraform.tfstate"
}
</code></pre>
<p>This is the same lock information that Terraform displays when a lock is held. It's now a JSON file in S3 rather than a record in DynamoDB.</p>
<h2 id="heading-how-to-handle-a-stuck-lock">How to Handle a Stuck Lock</h2>
<p>With the DynamoDB backend, resolving a stuck lock meant deleting a record from the DynamoDB table. With S3 native locking, it means deleting the <code>.tflock</code> file from S3.</p>
<p>A lock can get stuck if:</p>
<ul>
<li><p>A <code>terraform apply</code> or <code>plan</code> process was killed mid-execution</p>
</li>
<li><p>A CI/CD pipeline runner crashed during a Terraform operation</p>
</li>
<li><p>A network interruption prevented the lock release from completing</p>
</li>
</ul>
<p>Here's how you can check for a stuck lock:</p>
<pre><code class="language-shell">aws s3 ls s3://your-bucket/path/to/ | grep tflock
</code></pre>
<p>If a <code>.tflock</code> file exists and no Terraform operation is currently running, it is a stuck lock.</p>
<p>You can also read the lock to understand who held it:</p>
<pre><code class="language-shell">aws s3 cp \
  s3://your-bucket/path/to/terraform.tfstate.tflock \
  /tmp/stuck.lock &amp;&amp; cat /tmp/stuck.lock
</code></pre>
<p>This tells you who (<code>Who</code> field) was running the operation, what operation it was (<code>Operation</code> field), and when it was acquired (<code>Created</code> field).</p>
<p>And you can force-unlock using Terraform like this:</p>
<pre><code class="language-shell">terraform force-unlock LOCK-ID
</code></pre>
<p>Replace <code>LOCK-ID</code> with the <code>ID</code> value from the lock file contents. For example:</p>
<pre><code class="language-shell">terraform force-unlock a1b2c3d4-e5f6-7890-abcd-ef1234567890
</code></pre>
<p>Terraform will confirm:</p>
<pre><code class="language-plaintext">Do you really want to force-unlock?
  Terraform will remove the lock on the remote state.
  This will allow local Terraform commands to modify this state, even though it
  may be still be in use. Only 'yes' will be accepted to confirm.
 
  Enter a value: yes
 
Terraform state has been successfully unlocked!
</code></pre>
<p>An alternative is to delete the lock file directly via CLI. If <code>terraform force-unlock</code> doesn't work (for example, because you are running in a CI environment without Terraform available), delete the lock file directly:</p>
<pre><code class="language-shell">aws s3 rm s3://your-bucket/path/to/terraform.tfstate.tflock
</code></pre>
<p><strong>Only delete the lock file if you are certain no Terraform operation is currently running.</strong> Deleting a lock that is actively held by a running operation will allow a second concurrent operation to start, which is exactly the race condition locking is designed to prevent.</p>
<h2 id="heading-rollback-plan-if-something-goes-wrong">Rollback Plan: If Something Goes Wrong</h2>
<p>If you encounter problems after migrating, you can roll back to the S3 + DynamoDB setup with these steps.</p>
<p><strong>Step 1: Stop all Terraform operations</strong> in your team and CI/CD pipelines.</p>
<p><strong>Step 2: Recreate the DynamoDB table</strong> if you already deleted it:</p>
<pre><code class="language-shell">aws dynamodb create-table \
  --table-name terraform-state-lock \
  --attribute-definitions AttributeName=LockID,AttributeType=S \
  --key-schema AttributeName=LockID,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST
</code></pre>
<p><strong>Step 3: Revert</strong> <code>backend.tf</code> to the previous configuration:</p>
<pre><code class="language-hcl">terraform {
  backend "s3" {
    bucket         = "your-existing-bucket"
    key            = "path/to/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"   # restored
    # Remove: use_lockfile = true
  }
}
</code></pre>
<p><strong>Step 4: Reinitialize:</strong></p>
<pre><code class="language-shell">terraform init -reconfigure
</code></pre>
<p><strong>Step 5: Verify:</strong></p>
<pre><code class="language-shell">terraform plan
</code></pre>
<p>The state file hasn't moved, so there's no data loss during a rollback. The only change is which locking mechanism Terraform uses.</p>
<p><strong>Note:</strong> Object Lock being enabled on the S3 bucket doesn't prevent the rollback. Object Lock and DynamoDB locking can coexist, Object Lock simply adds a capability to the bucket. Using <code>dynamodb_table</code> in your backend config tells Terraform to use DynamoDB regardless of whether Object Lock is enabled on the bucket.</p>
<h2 id="heading-security-best-practices-for-your-state-bucket">Security Best Practices for Your State Bucket</h2>
<p>Migrating to S3 native locking is a good opportunity to review the overall security configuration of your state bucket. Here are the practices every production Terraform state bucket should implement:</p>
<h3 id="heading-enable-versioning-required">Enable Versioning (Required)</h3>
<p>Versioning is a hard requirement for S3 native locking to work safely. It ensures that if a state file is accidentally overwritten or corrupted, you can restore a previous version.</p>
<pre><code class="language-shell">aws s3api put-bucket-versioning \
  --bucket your-state-bucket \
  --versioning-configuration Status=Enabled
</code></pre>
<h3 id="heading-block-all-public-access-non-negotiable">Block All Public Access (Non-Negotiable)</h3>
<p>Your state file contains resource ARNs, IP addresses, and may contain sensitive values passed through Terraform variables. It must never be publicly accessible.</p>
<pre><code class="language-shell">aws s3api put-public-access-block \
  --bucket your-state-bucket \
  --public-access-block-configuration \
    "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
</code></pre>
<h3 id="heading-enable-server-side-encryption">Enable Server-Side Encryption</h3>
<p>Always encrypt state files at rest. AES256 is the minimum. If your organization requires KMS key management:</p>
<pre><code class="language-shell">aws s3api put-bucket-encryption \
  --bucket your-state-bucket \
  --server-side-encryption-configuration '{
    "Rules": [
      {
        "ApplyServerSideEncryptionByDefault": {
          "SSEAlgorithm": "aws:kms",
          "KMSMasterKeyID": "arn:aws:kms:us-east-1:123456789012:key/your-kms-key-id"
        },
        "BucketKeyEnabled": true
      }
    ]
  }'
</code></pre>
<h3 id="heading-apply-least-privilege-iam-permissions">Apply Least-Privilege IAM Permissions</h3>
<p>The role or user that Terraform uses to access the state bucket should have only the permissions it needs. Here's a minimal IAM policy for S3 native locking:</p>
<pre><code class="language-json">{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "TerraformStateAccess",
      "Effect": "Allow",
      "Action": [
        "s3:ListBucket",
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject"
      ],
      "Resource": [
        "arn:aws:s3:::your-state-bucket",
        "arn:aws:s3:::your-state-bucket/*"
      ]
    },
    {
      "Sid": "TerraformStateLocking",
      "Effect": "Allow",
      "Action": [
        "s3:GetObjectLegalHold",
        "s3:PutObjectLegalHold",
        "s3:GetObjectRetention",
        "s3:PutObjectRetention"
      ],
      "Resource": "arn:aws:s3:::your-state-bucket/*.tflock"
    }
  ]
}
</code></pre>
<p>Notice what is absent: there are no DynamoDB permissions. This is a cleaner, smaller permission set than the old approach required.</p>
<h3 id="heading-enable-access-logging">Enable Access Logging</h3>
<p>Log all access to your state bucket in CloudTrail or S3 server access logs. This gives you an audit trail of every time state was read, written, or locked:</p>
<pre><code class="language-shell">aws s3api put-bucket-logging \
  --bucket your-state-bucket \
  --bucket-logging-status '{
    "LoggingEnabled": {
      "TargetBucket": "your-logging-bucket",
      "TargetPrefix": "terraform-state-access/"
    }
  }'
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>AWS S3 native state locking removes the need for a DynamoDB table from your Terraform backend setup. The result is simpler infrastructure, a smaller IAM permission surface, and one fewer service to provision, monitor, and pay for across every environment your team manages.</p>
<p>Here's a summary of what you accomplished:</p>
<ul>
<li><p>Understood what state locking is and why it's required for safe Terraform operations</p>
</li>
<li><p>Compared S3 native locking to the existing S3 + DynamoDB approach</p>
</li>
<li><p>Set up a fresh Terraform backend using S3 native locking with correct bucket configuration</p>
</li>
<li><p>Migrated an existing backend from S3 + DynamoDB to S3 native locking safely</p>
</li>
<li><p>Learned how to verify locking, handle stuck locks, and roll back if needed</p>
</li>
<li><p>Applied security best practices to the state bucket</p>
</li>
</ul>
<p>This pattern – using S3 native locking – is the recommended approach for all new Terraform projects on AWS going forward. If you're managing a large estate with multiple Terraform backends, consider automating the migration using a script or Terraform module that applies the pattern across all your state buckets.</p>
<p><em>If you are building or optimizing cloud infrastructure for a startup and want a complete reference for production-ready Terraform modules, CI/CD pipeline patterns, and infrastructure runbooks, check out</em> <a href="https://coachli.co/tolani-akintayo/PR-H4oQS">The Startup DevOps Field Guide</a><em>. It covers the full lifecycle of AWS infrastructure from initial setup to production reliability.</em></p>
<h2 id="heading-references">References</h2>
<ul>
<li><p><a href="https://developer.hashicorp.com/terraform/language/backend/s3#use_lockfile">HashiCorp - S3 Backend Configuration: use_lockfile</a></p>
</li>
<li><p><a href="https://github.com/hashicorp/terraform/releases/tag/v1.10.0">HashiCorp: Terraform 1.10 Release Notes</a></p>
</li>
<li><p><a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lock.html">AWS Docs: S3 Object Lock Overview</a></p>
</li>
<li><p><a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectLockConfiguration.html">AWS Docs: PutObjectLockConfiguration API</a></p>
</li>
<li><p><a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/conditional-requests.html">AWS Docs: S3 Conditional Writes</a></p>
</li>
<li><p><a href="https://developer.hashicorp.com/terraform/language/state/locking">HashiCorp: Backend State Locking</a></p>
</li>
<li><p><a href="https://developer.hashicorp.com/terraform/cli/commands/force-unlock">HashiCorp: terraform force-unlock Command</a></p>
</li>
<li><p><a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/manage-versioning-examples.html">AWS Docs: Enabling S3 Versioning</a></p>
</li>
<li><p><a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/serv-side-encryption.html">AWS Docs: S3 Server-Side Encryption</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Get Started with Terraform ]]>
                </title>
                <description>
                    <![CDATA[ Infrastructure has undergone a fundamental shift over the past decade. What was once configured manually through dashboards and shell access is now defined declaratively in code. This shift isn't just ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-get-started-with-terraform/</link>
                <guid isPermaLink="false">69dfbc0c46ad31000be2f6ea</guid>
                
                    <category>
                        <![CDATA[ Terraform ]]>
                    </category>
                
                    <category>
                        <![CDATA[ infrastructure ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Manish Shivanandhan ]]>
                </dc:creator>
                <pubDate>Wed, 15 Apr 2026 16:25:48 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/84d7cd53-510f-4a62-9e73-10f476a95c4a.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Infrastructure has undergone a fundamental shift over the past decade.</p>
<p>What was once configured manually through dashboards and shell access is now defined declaratively in code. This shift isn't just about convenience. It's about repeatability, auditability, and control.</p>
<p><a href="https://developer.hashicorp.com/terraform">Terraform</a> sits at the centre of this transformation. It allows you to define infrastructure using configuration files, apply those configurations consistently across environments, and evolve systems safely over time.</p>
<p>For teams building modern applications, especially on platform abstractions, Terraform becomes the control plane for everything from application deployment to databases and networking.</p>
<p>The open source Terraform provider from <a href="https://sevalla.com/">Sevalla</a> extends this model by allowing teams to manage the entire application platform as code, not just underlying infrastructure. It enables you to define applications, databases, networking, storage, and deployment workflows in a single, unified configuration.</p>
<p>Instead of stitching together multiple tools or relying on manual setup, everything from code deployment to traffic routing and environment configuration can be expressed declaratively. This creates a consistent, repeatable system where environments can be replicated easily, changes are version-controlled, and production setups can evolve safely over time.</p>
<p>This article walks through how to go from zero to a production-ready setup using Terraform and <a href="https://github.com/sevalla-hosting/terraform-provider-sevalla/">the Sevalla Terraform Provider</a>, focusing on practical concepts rather than theory.</p>
<h3 id="heading-what-well-cover">What We'll Cover:</h3>
<ul>
<li><p><a href="#heading-what-terraform-actually-does">What Terraform Actually Does</a></p>
</li>
<li><p><a href="#heading-setting-up-terraform-for-the-first-time">Setting Up Terraform for the First Time</a></p>
</li>
<li><p><a href="#heading-understanding-providers-resources-and-data-sources">Understanding Providers, Resources, and Data Sources</a></p>
</li>
<li><p><a href="#heading-building-a-real-application-stack">Building a Real Application Stack</a></p>
</li>
<li><p><a href="#heading-managing-configuration-and-secrets">Managing Configuration and Secrets</a></p>
</li>
<li><p><a href="#heading-scaling-and-process-configuration">Scaling and Process Configuration</a></p>
</li>
<li><p><a href="#heading-adding-networking-and-traffic-management">Adding Networking and Traffic Management</a></p>
</li>
<li><p><a href="#heading-pipelines-and-continuous-deployment">Pipelines and Continuous Deployment</a></p>
</li>
<li><p><a href="#heading-from-configuration-to-production">From Configuration to Production</a></p>
</li>
<li><p><a href="#heading-why-terraform-scales-with-you">Why Terraform Scales with You</a></p>
</li>
</ul>
<h2 id="heading-what-terraform-actually-does"><strong>What Terraform Actually Does</strong></h2>
<p>Terraform is an infrastructure-as-code tool that translates configuration files into real infrastructure. You describe the desired state of your system, and Terraform figures out how to achieve it.</p>
<p>At a high level, Terraform operates in three phases.</p>
<p>First, it initializes the working directory and downloads required providers. Providers are plugins that allow Terraform to interact with specific platforms.</p>
<p>Next, it creates an execution plan. This plan shows what resources will be created, modified, or destroyed to match your configuration.</p>
<p>Finally, it applies the plan, making the necessary API calls to bring your infrastructure into the desired state.</p>
<p>The key idea is that Terraform is declarative. You define what you want, not how to do it. Terraform handles the orchestration.</p>
<p>This abstraction becomes extremely powerful as systems grow more complex.</p>
<h2 id="heading-setting-up-terraform-for-the-first-time"><strong>Setting Up Terraform for the First Time</strong></h2>
<p>Getting started with Terraform requires very little setup. You install the CLI, create a working directory, and define a basic configuration.</p>
<p>A <a href="https://developer.hashicorp.com/terraform/language/syntax/configuration">Terraform configuration</a> is written in HCL, a domain-specific language designed to be human-readable. Even a simple configuration establishes the core concepts.</p>
<p>You define the required provider, configure authentication, and declare resources.</p>
<p>Here's a minimal example that provisions an application using a managed platform provider.</p>
<pre><code class="language-plaintext">terraform {
 required_providers {
   sevalla = {
     source  = "sevalla-hosting/sevalla"
     version = "~&gt; 1.0"
   }
 }
}

provider "sevalla" {
}
data "sevalla_clusters" "all" {}
resource "sevalla_application" "web" {
 display_name = "my-web-app"
 cluster_id   = data.sevalla_clusters.all.clusters[0].id
 source       = "publicGit"
 repo_url     = "https://github.com/example/app"
}
</code></pre>
<p>This configuration does several things.</p>
<p>First, it declares the provider, which tells Terraform how to communicate with the platform. It also fetches available clusters using a data source. It then defines an application resource that points to a Git repository.</p>
<p>Even at this stage, you're already defining infrastructure in a reproducible way.</p>
<p>To execute this configuration, you run three commands.</p>
<p>You initialize the project, generate a plan, and apply it.</p>
<pre><code class="language-plaintext">export SEVALLA_API_KEY="your-api-key"
terraform init
terraform plan
terraform apply
</code></pre>
<p>After applying, your application is deployed without manual steps.</p>
<h2 id="heading-understanding-providers-resources-and-data-sources"><strong>Understanding Providers, Resources, and Data Sources</strong></h2>
<p>Terraform revolves around three core constructs.</p>
<p>Providers act as the bridge between Terraform and external systems. They expose APIs in a structured way that Terraform can use.</p>
<p>Resources represent the infrastructure you want to create. These are the building blocks of your system. Applications, databases, load balancers, and storage buckets are all modeled as resources.</p>
<p>Data sources allow you to query existing infrastructure. Instead of creating something new, you retrieve information that can be used elsewhere in your configuration.</p>
<p>The combination of these constructs allows you to build flexible and composable systems.</p>
<p>For example, you can fetch a list of available clusters using a data source and then dynamically assign your application to one of them. This reduces hardcoding and improves portability.</p>
<p>As your configuration grows, these abstractions help you maintain clarity and structure.</p>
<h2 id="heading-building-a-real-application-stack"><strong>Building a Real Application Stack</strong></h2>
<p>A production system is rarely just a single application. It typically includes multiple components that need to work together.</p>
<p>With Terraform, you can define the entire stack in one place.</p>
<p>You might start with an application, then add a managed database, connect them internally, and expose the application through a load balancer.</p>
<p>A simplified flow looks like this.</p>
<p>You define the application resource that pulls code from a repository. You provision a database resource, such as PostgreSQL or Redis. You establish an internal connection between the application and the database. You configure environment variables for credentials. You optionally add a custom domain or routing layer.</p>
<p>Each of these components is a resource, and Terraform ensures they're created in the correct order.</p>
<p>This approach eliminates configuration drift. Instead of manually setting up each component, everything is defined in code and version-controlled.</p>
<p>It also makes environments consistent. Your staging and production setups can be identical except for a few variables.</p>
<h2 id="heading-managing-configuration-and-secrets"><strong>Managing Configuration and Secrets</strong></h2>
<p>Production systems require configuration. This includes environment variables, API keys, and connection strings.</p>
<p>Terraform provides multiple ways to handle this.</p>
<p>You can define variables in your configuration and pass values at runtime. Sensitive values, such as API keys, are typically injected via environment variables.</p>
<p>For example, authentication is handled through an API key that can be set as an environment variable.</p>
<pre><code class="language-plaintext">export SEVALLA_API_KEY="your-api-key"
</code></pre>
<p>This avoids hardcoding credentials in configuration files.</p>
<p>You can also define environment variables as part of your infrastructure. This allows you to configure applications consistently across environments.</p>
<p>The important principle is separation of concerns. Infrastructure definitions should remain clean, while sensitive data is managed securely.</p>
<h2 id="heading-scaling-and-process-configuration"><strong>Scaling and Process Configuration</strong></h2>
<p>Modern applications often consist of multiple processes. A web server handles incoming requests, background workers process jobs, and scheduled tasks run periodically.</p>
<p>Terraform allows you to define these processes explicitly.</p>
<p>You can configure different process types, allocate resources, and scale them independently. This is particularly useful for handling variable workloads.</p>
<p>For example, you might scale web processes based on incoming traffic while keeping background workers at a steady level.</p>
<p>By defining this in code, scaling becomes predictable and repeatable.</p>
<p>You avoid manual intervention and ensure that your system behaves consistently under load.</p>
<h2 id="heading-adding-networking-and-traffic-management"><strong>Adding Networking and Traffic Management</strong></h2>
<p>As systems grow, managing traffic becomes more important.</p>
<p>Terraform enables you to define networking components such as load balancers and routing rules. You can map domains to applications, distribute traffic across multiple services, and control access.</p>
<p>This is essential for production readiness.</p>
<p>A load balancer can improve availability by distributing traffic across instances. Domain configuration ensures that users can access your application through a stable endpoint.</p>
<p>You can also define restrictions, such as IP allowlists, to enhance security.</p>
<p>All of this is managed declaratively, which reduces the risk of misconfiguration.</p>
<h2 id="heading-pipelines-and-continuous-deployment"><strong>Pipelines and Continuous Deployment</strong></h2>
<p>Production systems require reliable deployment workflows.</p>
<p>You can use Terraform to define deployment pipelines and stages. This allows you to model how code moves from development to production.</p>
<p>You can define multiple stages, associate applications with each stage, and control how deployments are triggered.</p>
<p>This brings infrastructure and deployment logic into a single system.</p>
<p>Instead of relying on external scripts or manual processes, everything is defined in a structured and version-controlled way.</p>
<p>It also improves traceability. You can see exactly how a system is configured and how changes are applied over time.</p>
<h2 id="heading-from-configuration-to-production"><strong>From Configuration to Production</strong></h2>
<p>Moving from a simple setup to production involves more than just adding resources. It requires discipline in how you manage infrastructure.</p>
<p>Version control becomes critical. Every change to your infrastructure should go through code review. This reduces the risk of introducing breaking changes.</p>
<p>State management is another key aspect. Terraform keeps track of the current state of your infrastructure. This state must be stored securely and consistently, especially in team environments.</p>
<p>You also need to think about environment separation. Development, staging, and production should be isolated but defined using similar configurations.</p>
<p>Finally, observability should be integrated from the start. While Terraform provisions infrastructure, you need monitoring and logging to understand how it behaves in production.</p>
<h2 id="heading-why-terraform-scales-with-you"><strong>Why Terraform Scales with You</strong></h2>
<p>Terraform works well for small projects, but its real value becomes apparent as systems grow.</p>
<p>As you add more services, environments, and dependencies, manual management becomes unsustainable. Terraform provides a structured way to manage this complexity.</p>
<p>It enforces consistency. It enables automation. It creates a single source of truth for your infrastructure.</p>
<p>Most importantly, it allows teams to move faster without sacrificing reliability.</p>
<p>By defining infrastructure as code, you reduce ambiguity. You make systems easier to understand, easier to debug, and easier to evolve.</p>
<p>That is what takes you from zero to production in a way that actually scales.</p>
<hr>
<p><em><strong>Want to build like a 10x developer? Learn through real projects, simple explanations, and tools that help you ship faster.</strong></em> <a href="https://manishmshiva.substack.com/"><em><strong>Join my newsletter</strong></em></a> <em><strong>and start levelling up every week.</strong></em></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Sync AWS Secrets Manager Secrets into Kubernetes with the External Secrets Operator ]]>
                </title>
                <description>
                    <![CDATA[ If someone asked you how secrets flow from AWS Secrets Manager into a running pod, could you explain it confidently? Storing them is straightforward. But handling rotation, stale env vars, and the gap ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-sync-aws-secrets-manager-secrets-into-kubernetes-with-the-external-secrets-operator/</link>
                <guid isPermaLink="false">69c541f010e664c5dadc877e</guid>
                
                    <category>
                        <![CDATA[ Kubernetes ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cybersecurity ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Devops ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Terraform ]]>
                    </category>
                
                    <category>
                        <![CDATA[ secrets management ]]>
                    </category>
                
                    <category>
                        <![CDATA[ SRE ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Osomudeya Zudonu ]]>
                </dc:creator>
                <pubDate>Thu, 26 Mar 2026 14:25:52 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/6cca126e-dd50-4400-ae9d-65449581345b.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If someone asked you how secrets flow from AWS Secrets Manager into a running pod, could you explain it confidently?</p>
<p>Storing them is straightforward. But handling rotation, stale env vars, and the gap between what your pod reads and what AWS actually holds is where many engineers go quiet.</p>
<p>In this guide, you'll build a complete secrets pipeline from AWS Secrets Manager into Kubernetes pods. You'll provision the infrastructure with Terraform, sync secrets using the External Secrets Operator, and run a sample application that reads the same credentials in two different ways: via environment variables and via a volume mount.</p>
<p>By the end, you'll be able to:</p>
<ul>
<li><p>Explain the full architecture from vault to pod</p>
</li>
<li><p>Run the lab locally in about 15 minutes</p>
</li>
<li><p>Prove why environment variables go stale after rotation, while mounted secret files stay fresh</p>
</li>
<li><p>Deploy the same pattern on Amazon Elastic Kubernetes Service with OpenID Connect-based CI/CD</p>
</li>
<li><p>Troubleshoot the most common failures</p>
</li>
</ul>
<p>Below is an architecture diagram showing secrets flowing from AWS Secrets Manager through the External Secrets Operator into a Kubernetes Secret, then splitting into environment variables set at pod start and a volume mount that updates within 60 seconds.</p>
<img src="https://cdn.hashnode.com/uploads/covers/698d563262d4ce66226a844a/ac8bfc9e-304e-41b8-b6a3-7ce1795b29a9.png" alt="Architecture diagram showing secrets flowing from AWS Secrets Manager through the External Secrets Operator into a Kubernetes Secret, then splitting into environment variables set at pod start and a volume mount that updates within 60 seconds." style="display:block;margin:0 auto" width="1024" height="1536" loading="lazy">

<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-how-to-understand-the-secret-flow">How to Understand the Secret Flow</a></p>
</li>
<li><p><a href="#heading-how-to-run-the-local-lab">How to Run the Local Lab</a></p>
</li>
<li><p><a href="#heading-how-to-inspect-the-externalsecret-and-the-application">How to Inspect the ExternalSecret and the Application</a></p>
</li>
<li><p><a href="#heading-how-to-test-secret-rotation">How to Test Secret Rotation</a></p>
</li>
<li><p><a href="#heading-how-to-choose-between-external-secrets-operator-and-the-csi-driver">How to Choose Between External Secrets Operator and the CSI Driver</a></p>
</li>
<li><p><a href="#heading-how-to-deploy-the-pattern-on-amazon-elastic-kubernetes-service">How to Deploy the Pattern on Amazon Elastic Kubernetes Service</a></p>
</li>
<li><p><a href="#heading-how-to-configure-github-actions-without-stored-aws-credentials">How to Configure GitHub Actions Without Stored AWS Credentials</a></p>
</li>
<li><p><a href="#heading-how-to-troubleshoot-the-most-common-failures">How to Troubleshoot the Most Common Failures</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before you begin, make sure you have the following tools installed and configured.</p>
<p><strong>For the local lab:</strong></p>
<ul>
<li><p>An AWS account with access to AWS Secrets Manager</p>
</li>
<li><p>The AWS CLI installed and configured. Run <code>aws configure</code> and provide your access key, secret key, default region, and output format. The credentials need permission to read and write secrets in AWS Secrets Manager.</p>
</li>
<li><p><code>kubectl</code> installed. For Microk8s, run <code>microk8s kubectl config view --raw &gt; ~/.kube/config</code> after installation to connect kubectl to your local cluster.</p>
</li>
<li><p>Terraform installed</p>
</li>
<li><p>Helm installed</p>
</li>
<li><p>Docker installed</p>
</li>
<li><p>A local Kubernetes cluster: the lab supports Microk8s and kind. If you do not have either installed, follow the <a href="https://microk8s.io/">Microk8s install guide</a> before continuing.</p>
</li>
</ul>
<p><strong>For the Amazon Elastic Kubernetes Service sections:</strong></p>
<ul>
<li><p>An Amazon Elastic Kubernetes Service cluster you can create or manage</p>
</li>
<li><p>A GitHub repository you can configure for workflows and secrets</p>
</li>
</ul>
<p>The lab repository includes two deployment paths: a local path for fast learning and an Amazon Elastic Kubernetes Service path for a production-like setup. All the exact commands for each path live in the repo's <a href="https://github.com/Osomudeya/k8s-secret-lab/blob/main/docs/DEPLOY-LOCAL.md"><code>docs/DEPLOY-LOCAL.md</code></a> and <a href="https://github.com/Osomudeya/k8s-secret-lab/blob/main/docs/DEPLOY-EKS.md"><code>docs/DEPLOY-EKS.md</code></a>.</p>
<h2 id="heading-how-to-understand-the-secret-flow">How to Understand the Secret Flow</h2>
<p>Before you run any command, you need to understand how the pieces connect.</p>
<p>The flow has four stages:</p>
<ol>
<li><p>A developer or automated system updates a secret in AWS Secrets Manager.</p>
</li>
<li><p>The External Secrets Operator polls AWS Secrets Manager on a schedule and creates or updates a Kubernetes Secret.</p>
</li>
<li><p>Your pod reads that Kubernetes Secret.</p>
</li>
<li><p>During rotation, the Kubernetes Secret updates, but your two consumption modes behave differently.</p>
</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/698d563262d4ce66226a844a/9dc52f99-add4-490a-ad86-25a30d0ae306.png" alt="A step-by-step flow diagram showing the four stages of secret flow above" style="display:block;margin:0 auto" width="1536" height="1024" loading="lazy">

<h3 id="heading-how-the-external-secrets-operator-sync-works">How the External Secrets Operator Sync Works</h3>
<p>The External Secrets Operator reads a custom Kubernetes resource called <code>ExternalSecret</code>. That resource tells the operator three things:</p>
<ul>
<li><p>Which secret store to connect to</p>
</li>
<li><p>Which Kubernetes Secret name to create or update</p>
</li>
<li><p>How often to refresh</p>
</li>
</ul>
<p>In this lab, the <code>ExternalSecret</code> creates a Kubernetes Secret named <code>myapp-database-creds</code>. The operator also adds a template annotation that can trigger a pod restart when the secret rotates.</p>
<h3 id="heading-how-the-app-consumes-secrets">How the App Consumes Secrets</h3>
<p>The sample application exposes three endpoints so you can validate behavior at any time.</p>
<ul>
<li><p><code>/secrets/env</code> shows what environment variables the pod sees</p>
</li>
<li><p><code>/secrets/volume</code> shows what files in the mounted secret directory look like</p>
</li>
<li><p><code>/secrets/compare</code> compares both and reports whether rotation has been detected</p>
</li>
</ul>
<p>The app checks four keys: <code>DB_USERNAME</code>, <code>DB_PASSWORD</code>, <code>DB_HOST</code>, and <code>DB_PORT</code>.</p>
<h2 id="heading-how-to-run-the-local-lab">How to Run the Local Lab</h2>
<p>The local lab gives you a fast learning loop. You can see the full pipeline working and test rotation without waiting for a cloud deployment.</p>
<h3 id="heading-step-1-clone-the-repo">Step 1: Clone the Repo</h3>
<pre><code class="language-bash">git clone https://github.com/Osomudeya/k8s-secret-lab
cd k8s-secret-lab
</code></pre>
<h3 id="heading-step-2-run-the-spin-up-script">Step 2: Run the Spin-Up Script</h3>
<pre><code class="language-bash">bash spinup.sh
</code></pre>
<p>The script will ask you to choose a local cluster type. Pick Microk8s or kind, depending on what you have installed. The script installs the External Secrets Operator via Helm, applies the Terraform configuration, and deploys the sample application.</p>
<p>If the script fails at any point, check <a href="https://github.com/Osomudeya/k8s-secret-lab/blob/main/docs/TROUBLESHOOTING.md"><code>docs/TROUBLESHOOTING.md</code></a> before retrying. The most common causes are missing AWS credentials, a misconfigured kubeconfig, or a Microk8s storage add-on that is not enabled.</p>
<h3 id="heading-important-run-the-lab-ui">Important: Run the Lab UI</h3>
<p>The lab ships with a separate guided tutorial interface that runs on your laptop. This is not the in-cluster application, it's a React-based checklist at <code>lab-ui/</code> that walks you through each concept and checkpoint as you work through the lab.</p>
<p>To start it, open a second terminal and run:</p>
<pre><code class="language-bash">cd lab-ui &amp;&amp; npm install &amp;&amp; npm run dev
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/698d563262d4ce66226a844a/873166e9-6bff-4e56-a18d-e58b9e9a5af9.png" alt="Screenshot of npm run dev lab ui terminal" style="display:block;margin:0 auto" width="849" height="435" loading="lazy">

<p>Then open <a href="http://localhost:5173"><code>http://localhost:5173</code></a>. You'll see a module-by-module guide covering the full flow from external secrets to rotation to CI/CD.</p>
<img src="https://cdn.hashnode.com/uploads/covers/698d563262d4ce66226a844a/5a5b220b-3f23-4c7c-8388-f2e23d122e2c.png" alt="Screenshot of The Lab UI, a guided tutorial interface that runs alongside the lab and walks you through each concept and checkpoint." style="display:block;margin:0 auto" width="1399" height="1287" loading="lazy">

<p>Keep this terminal running alongside your lab. The Lab UI and the in-cluster app (<code>localhost:3000</code>) are two separate things, the UI guides you through the steps, the app shows you the live secrets.</p>
<h3 id="heading-step-3-access-the-application">Step 3: Access the Application</h3>
<p>Once the lab finishes, port-forward the service.</p>
<pre><code class="language-bash">kubectl port-forward svc/myapp 3000:80 -n default
</code></pre>
<p>Open <code>http://localhost:3000</code>. You should see a table showing each secret key and whether the environment variable value matches the volume mount value.</p>
<img src="https://cdn.hashnode.com/uploads/covers/698d563262d4ce66226a844a/dbe122ac-b787-40d0-96f4-4b1276bab017.png" alt="Screenshot of the running application at localhost:3000. Every row in the table should show &quot;Match ✓" style="display:block;margin:0 auto" width="1213" height="902" loading="lazy">

<h3 id="heading-step-4-validate-that-secrets-match">Step 4: Validate That Secrets Match</h3>
<p>Run the compare endpoint directly from the terminal.</p>
<pre><code class="language-bash">curl -s http://localhost:3000/secrets/compare | python3 -m json.tool
</code></pre>
<p>When everything is working, the response will include <code>"all_match": true</code>.</p>
<h2 id="heading-how-to-inspect-the-externalsecret-and-the-application">How to Inspect the ExternalSecret and the Application</h2>
<p>At this point the lab is running. Now you'll want to inspect the manifests so you understand what each part does.</p>
<h3 id="heading-step-1-read-the-externalsecret-manifest">Step 1: Read the ExternalSecret Manifest</h3>
<p>Open <a href="https://github.com/Osomudeya/k8s-secret-lab/blob/main/k8s/aws/external-secret.yaml"><code>k8s/aws/external-secret.yaml</code></a>. Focus on these four fields:</p>
<ul>
<li><p><code>refreshInterval</code>: how often the operator polls AWS Secrets Manager</p>
</li>
<li><p><code>secretStoreRef</code>: which store the operator authenticates against</p>
</li>
<li><p><code>target</code>: the name of the Kubernetes Secret to create</p>
</li>
<li><p><code>data</code>: the mapping from AWS Secrets Manager JSON keys to Kubernetes Secret keys</p>
</li>
</ul>
<p>Here is what that mapping looks like in this lab:</p>
<pre><code class="language-yaml">spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secrets-manager
    kind: ClusterSecretStore
  target:
    name: myapp-database-creds
    creationPolicy: Owner
  data:
    - secretKey: DB_USERNAME
      remoteRef:
        key: prod/myapp/database
        property: username
</code></pre>
<p>The <code>property</code> field tells the operator which JSON key inside the AWS secret to extract. If your secret in AWS Secrets Manager is a JSON object, each field gets its own entry here.</p>
<p>Two fields here are worth understanding before you move on. <code>creationPolicy: Owner</code> means the operator owns the Kubernetes Secret it creates. If you delete the <code>ExternalSecret</code>, the Secret is deleted too. <code>ClusterSecretStore</code> is a cluster-scoped store, meaning any namespace in the cluster can use it. A plain <code>SecretStore</code> is namespace-scoped. For this lab, cluster-scoped is the right choice because it keeps the setup simple.</p>
<h3 id="heading-step-2-read-the-deployment-manifest">Step 2: Read the Deployment Manifest</h3>
<p>Open <a href="http://github.com/Osomudeya/k8s-secret-lab/blob/main/k8s/aws/deployment.yaml"><code>k8s/aws/deployment.yaml</code></a>. You are looking for two sections: <code>envFrom</code> and <code>volumeMounts</code>.</p>
<pre><code class="language-yaml">envFrom:
  - secretRef:
      name: myapp-database-creds

volumeMounts:
  - name: db-secret-vol
    mountPath: /etc/secrets
    readOnly: true
</code></pre>
<p>Both paths read from the same Kubernetes Secret, <code>myapp-database-creds</code>. The <code>envFrom</code> block injects all keys as environment variables at pod start.<br>The <code>volumeMounts</code> block mounts the same secret as files under <code>/etc/secrets</code>.</p>
<p>This is the core of the rotation lesson. Both paths read the same source. But they behave differently after that source changes.</p>
<h3 id="heading-step-3-read-the-app-comparison-logic">Step 3: Read the App Comparison Logic</h3>
<p>Open <a href="https://github.com/Osomudeya/k8s-secret-lab/blob/main/app/server.js"><code>app/server.js</code></a>. The comparison logic reads environment variables from <code>process.env</code> and reads mounted secret files from <code>/etc/secrets/&lt;key&gt;</code>. Then it computes a per-key match and a global <code>all_match</code> value.</p>
<p>The <code>/secrets/compare</code> endpoint sets <code>rotation_detected: true</code> when any key differs between env and volume.</p>
<h2 id="heading-how-to-test-secret-rotation">How to Test Secret Rotation</h2>
<p>Secret rotation is where real teams feel pain. This lab makes that pain visible so you can explain it clearly and fix it confidently.</p>
<h3 id="heading-how-the-rotation-gap-works"><strong>How the Rotation Gap Works</strong></h3>
<p>When a pod starts, Kubernetes gives it two ways to read a secret.</p>
<p>The first way is environment variables. Think of these like sticky notes written on the wall of the container the moment it boots up. The value gets written once, at startup, and never changes. Even if the secret in AWS gets updated ten minutes later, the sticky note still says the old value. The container cannot see the update because nobody rewrote the note.</p>
<p>The second way is a volume mount. Think of this like a shared folder that someone else can update remotely. Kubernetes creates a small folder inside the container and puts the secret value in a file there. When the secret changes in AWS and ESO syncs it into Kubernetes, the kubelet quietly updates that file within about 60 seconds. The container reads the file fresh every time it needs the value, so it sees the new password automatically.</p>
<p>Same secret, two paths. One goes stale while one stays fresh.</p>
<p>The problem happens when your app reads the database password from the environment variable, the sticky note, and someone rotates the password in AWS. ESO updates Kubernetes. The file gets the new password. But your app is still reading the sticky note, which has the old one. Connection fails.</p>
<p>That difference isn't a bug. It's how the Linux process model and the kubelet work. Understanding it is the difference between knowing Kubernetes secrets and actually operating them.</p>
<p>Here is what you're about to observe in the lab:</p>
<ul>
<li><p>The rotation script updates the secret in AWS</p>
</li>
<li><p>ESO syncs the new value into Kubernetes within seconds</p>
</li>
<li><p>The volume file updates automatically</p>
</li>
<li><p>The environment variable stays stale until the pod restarts</p>
</li>
<li><p>The <code>/secrets/compare</code> endpoint shows both values side by side so you can see the gap live</p>
</li>
</ul>
<h3 id="heading-step-1-confirm-the-lab-is-ready">Step 1: Confirm the Lab Is Ready</h3>
<p>Make sure your pod and the External Secrets Operator are both running before you start.</p>
<pre><code class="language-bash">kubectl get pods -n external-secrets
kubectl get pods -n default
</code></pre>
<p>Both should show <code>Running</code>.</p>
<h3 id="heading-step-2-run-the-rotation-test-script">Step 2: Run the Rotation Test Script</h3>
<pre><code class="language-bash">bash rotation/test-rotation.sh
</code></pre>
<p>The script performs these actions in order:</p>
<ol>
<li><p>Reads the current <code>DB_PASSWORD</code> from the volume mount at <code>/etc/secrets/DB_PASSWORD</code></p>
</li>
<li><p>Reads the current <code>DB_PASSWORD</code> from the environment variable</p>
</li>
<li><p>Updates AWS Secrets Manager with a new password using <code>put-secret-value</code></p>
</li>
<li><p>Forces an immediate ESO sync by annotating the <code>ExternalSecret</code> with <code>force-sync</code></p>
</li>
<li><p>Reads the volume value again</p>
</li>
<li><p>Reads the environment variable again</p>
</li>
</ol>
<p>After the script runs, the volume and the env var will show different values.</p>
<h3 id="heading-step-3-validate-with-the-compare-endpoint">Step 3: Validate With the Compare Endpoint</h3>
<p>Hit the compare endpoint and look at the output.</p>
<pre><code class="language-bash">curl -s http://localhost:3000/secrets/compare | python3 -m json.tool
</code></pre>
<p>You'll see something like this:</p>
<pre><code class="language-json">{
  "comparison": {
    "DB_PASSWORD": {
      "env": "old-password-value",
      "volume": "new-password-value",
      "match": false
    }
  },
  "all_match": false,
  "rotation_detected": true,
  "message": "Volume has new value; env still has old value."
}
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/698d563262d4ce66226a844a/c4ebb09f-e605-4f68-8e12-1361d94199b2.png" alt="Rotation mismatch, the volume file updated with the new password but the env var still holds the old value from pod startup." style="display:block;margin:0 auto" width="832" height="290" loading="lazy">

<h3 id="heading-step-4-restart-the-deployment-to-sync-env-vars">Step 4: Restart the Deployment to Sync Env Vars</h3>
<p>Env vars don't update in place. You need a pod restart so new containers start with the updated Kubernetes Secret.</p>
<pre><code class="language-bash">kubectl rollout restart deployment/myapp -n default
kubectl rollout status deployment/myapp -n default
</code></pre>
<p>Then hit <code>/secrets/compare</code> again. All rows should now show <code>"all_match": true</code>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/698d563262d4ce66226a844a/0040274d-a398-408c-9486-ce0a9e527479.png" alt="After a rolling restart, new pods pick up fresh env vars and all keys match." style="display:block;margin:0 auto" width="821" height="436" loading="lazy">

<h3 id="heading-how-to-automate-restarts-with-reloader">How to Automate Restarts With Reloader</h3>
<p>If you don't want to restart deployments manually after every rotation, you can install <a href="https://github.com/stakater/reloader"><strong>Stakater Reloader</strong></a>. It watches an annotation on the <code>Deployment</code> and triggers a rolling restart automatically when the referenced Kubernetes Secret changes. New pods start with fresh env vars, while old pods drain cleanly. The repo's local deployment guide includes the install steps.</p>
<h2 id="heading-how-to-choose-between-external-secrets-operator-and-the-csi-driver">How to Choose Between External Secrets Operator and the CSI Driver</h2>
<p>Two patterns dominate when it comes to pulling external secrets into Kubernetes: the External Secrets Operator and the <a href="https://secrets-store-csi-driver.sigs.k8s.io/">Secrets Store CSI Driver</a>.</p>
<p>Both get cloud secrets into pods, but they do it differently. Here's a plain comparison:</p>
<table>
<thead>
<tr>
<th>Feature</th>
<th>External Secrets Operator</th>
<th>Secrets Store CSI Driver</th>
</tr>
</thead>
<tbody><tr>
<td>Creates a Kubernetes Secret</td>
<td>Yes</td>
<td>No by default</td>
</tr>
<tr>
<td>Supports <code>envFrom</code></td>
<td>Yes</td>
<td>No (workaround only)</td>
</tr>
<tr>
<td>Secret stored in etcd</td>
<td>Yes (base64)</td>
<td>No, if you skip sync</td>
</tr>
<tr>
<td>Rotation</td>
<td>ESO updates the Secret, Reloader restarts pods</td>
<td>Volume file can update in place</td>
</tr>
<tr>
<td>Best for</td>
<td>Most teams. Multi-cloud, env var support</td>
<td>Security policies that prohibit secrets in etcd</td>
</tr>
</tbody></table>
<p>This lab uses the External Secrets Operator for two reasons. First, it produces a native Kubernetes Secret, which means your application and deployment patterns match standard Kubernetes workflows. Second, having both <code>envFrom</code> and a volume mount point to the same Secret makes the rotation behavior easy to observe side by side.</p>
<p>Use the CSI Driver when your security team prohibits storing secrets in etcd through a Kubernetes Secret. The driver mounts secret data directly into the pod file system without creating a Kubernetes Secret. The tradeoff is that you lose the native <code>envFrom</code> model.</p>
<h2 id="heading-how-to-deploy-the-pattern-on-amazon-elastic-kubernetes-service">How to Deploy the Pattern on Amazon Elastic Kubernetes Service</h2>
<p>The local lab is ideal for learning. The Amazon Elastic Kubernetes Service path adds the production-like pieces: IAM role-based permissions for the operator, a load balancer for the app, and a full CI/CD workflow.</p>
<h3 id="heading-step-1-prepare-terraform-and-openid-connect-access">Step 1: Prepare Terraform and OpenID Connect Access</h3>
<p>The repository includes a one-time setup guide for OpenID Connect-based access from GitHub Actions to AWS. Run these commands in the <a href="https://github.com/Osomudeya/k8s-secret-lab/tree/main/terraform/github-oidc"><code>terraform/github-oidc</code></a> folder.</p>
<pre><code class="language-bash">cd terraform/github-oidc
terraform init
terraform plan -var="github_repo=YOUR_ORG/YOUR_REPO"
terraform apply -var="github_repo=YOUR_ORG/YOUR_REPO"
terraform output role_arn
</code></pre>
<p>Copy the role ARN from the output. You'll need it in the next step.</p>
<h3 id="heading-step-2-set-the-required-environment-variable">Step 2: Set the Required Environment Variable</h3>
<p>The Amazon Elastic Kubernetes Service spin-up path needs your GitHub Actions role ARN so Terraform can grant the CI/CD runner access to the cluster.</p>
<p>To find your AWS account ID, run:</p>
<pre><code class="language-bash">aws sts get-caller-identity --query Account --output text
</code></pre>
<p>Then set the variable, replacing <code>ACCOUNT</code> with the number that command returns.</p>
<pre><code class="language-bash">export GITHUB_ACTIONS_ROLE_ARN=arn:aws:iam::ACCOUNT:role/your-github-oidc-role
</code></pre>
<h3 id="heading-step-3-run-the-spin-up-script-for-amazon-elastic-kubernetes-service">Step 3: Run the Spin-Up Script for Amazon Elastic Kubernetes Service</h3>
<pre><code class="language-bash">bash spinup.sh --cluster eks
</code></pre>
<p>When the script finishes, it prints the application URL. Open that URL in a browser and confirm that you see the same secrets table you saw locally, with all keys showing <code>Match ✓</code>.</p>
<h3 id="heading-step-4-test-rotation-on-the-deployed-app">Step 4: Test Rotation on the Deployed App</h3>
<p>After you confirm normal operation, run the rotation test the same way you did locally.</p>
<pre><code class="language-bash">bash rotation/test-rotation.sh
</code></pre>
<p>Then use <code>/secrets/compare</code> on the Amazon Elastic Kubernetes Service load balancer URL to validate behavior in the cloud environment.</p>
<p>⚠️ <strong>Cost warning:</strong> Amazon Elastic Kubernetes Service runs at approximately $0.16 per hour. When you're done with the lab, run <a href="https://github.com/Osomudeya/k8s-secret-lab/blob/main/teardown.sh"><code>bash teardown.sh</code></a> from the repo root to destroy all AWS resources and stop charges.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/56f05ace-9ab6-4b67-ade6-a0bd1fa3962c.png" alt="Screenshot of the app running on the ALB URL, showing all keys matched" style="display:block;margin:0 auto" width="912" height="891" loading="lazy">

<h2 id="heading-how-to-configure-github-actions-without-stored-aws-credentials">How to Configure GitHub Actions Without Stored AWS Credentials</h2>
<p>The typical CI/CD setup stores <code>AWS_ACCESS_KEY_ID</code> and <code>AWS_SECRET_ACCESS_KEY</code> in GitHub repository secrets. Those keys never rotate. Anyone with repo access can read them. When someone leaves the team, you have to revoke keys and update every workflow.</p>
<p>OpenID Connect eliminates that problem entirely.</p>
<h3 id="heading-how-openid-connect-works-for-github-actions">How OpenID Connect Works for GitHub Actions</h3>
<p>GitHub can issue a short-lived token for each workflow run. That token identifies the run: the repository, branch, and workflow name. You create an IAM role in AWS whose trust policy says: only accept requests that come from this specific GitHub repository and branch. The GitHub Actions runner exchanges that token for temporary AWS credentials via <code>AssumeRoleWithWebIdentity</code>. No long-lived keys are ever stored anywhere.</p>
<img src="https://cdn.hashnode.com/uploads/covers/698d563262d4ce66226a844a/48e72210-a669-440e-b42e-81b0c15746ec.png" alt="The full OIDC authentication flow for GitHub Actions deploying to EKS — from minting the JWT token through AssumeRoleWithWebIdentity to temporary credentials, kubeconfig retrieval, and final kubectl apply steps." style="display:block;margin:0 auto" width="1536" height="1024" loading="lazy">

<h3 id="heading-step-1-create-the-iam-role-with-terraform">Step 1: Create the IAM Role With Terraform</h3>
<p>The <a href="https://github.com/Osomudeya/k8s-secret-lab/tree/main/terraform/github-oidc"><code>terraform/github-oidc</code></a> folder creates the OpenID Connect provider and the IAM role for you. You already ran this in the Amazon Elastic Kubernetes Service setup above. The role ARN is the only value you need to store.</p>
<h3 id="heading-step-2-add-the-role-arn-to-github-repository-secrets">Step 2: Add the Role ARN to GitHub Repository Secrets</h3>
<p>In your GitHub repository:</p>
<ol>
<li><p>Go to Settings → Secrets and variables → Actions</p>
</li>
<li><p>Click New repository secret</p>
</li>
<li><p>Name it <code>AWS_ROLE_ARN</code></p>
</li>
<li><p>Paste the role ARN from the Terraform output</p>
</li>
</ol>
<p>That is the only secret you store. The role ARN isn't sensitive. It's an identifier, not a credential.</p>
<h3 id="heading-step-3-configure-terraform-state">Step 3: Configure Terraform State</h3>
<p>For CI/CD to work consistently across runs, Terraform needs a shared state backend. The lab stores Terraform state in an Amazon S3 bucket and uses an Amazon DynamoDB table for state locking. The Amazon Elastic Kubernetes Service deployment guide in the repo covers the backend setup in full.</p>
<h3 id="heading-step-4-push-to-main-and-let-workflows-run">Step 4: Push to Main and Let Workflows Run</h3>
<p>After your first spin-up, every push to the <code>main</code> branch drives the CI/CD pipeline. The repo includes separate workflow files for Terraform infrastructure changes and application deployment changes. Once your application is reachable, use <code>/secrets/compare</code> to validate rotation behavior on the live environment.</p>
<h2 id="heading-how-to-troubleshoot-the-most-common-failures">How to Troubleshoot the Most Common Failures</h2>
<p>Here's a shortlist of the most common symptoms and their fixes.</p>
<table>
<thead>
<tr>
<th>Symptom</th>
<th>Most Likely Cause</th>
<th>Fix</th>
</tr>
</thead>
<tbody><tr>
<td><code>ExternalSecret</code> is not syncing</td>
<td>Missing credentials or wrong store reference</td>
<td>Confirm the operator can access AWS Secrets Manager and that <code>secretStoreRef</code> points to the correct store</td>
</tr>
<tr>
<td>Pod is stuck in <code>Pending</code></td>
<td>Missing storage setup for local cluster</td>
<td>For Microk8s, enable the storage add-on</td>
</tr>
<tr>
<td>Env and volume still match after rotation</td>
<td>Rotation happened but the pod never restarted</td>
<td>Run <code>kubectl rollout restart</code> or install Reloader</td>
</tr>
<tr>
<td>CRD or API version mismatch</td>
<td>ESO version and manifest <code>apiVersion</code> don't match</td>
<td>Verify the <code>apiVersion</code> for <code>ClusterSecretStore</code> and <code>ExternalSecret</code> match your installed ESO version</td>
</tr>
<tr>
<td>Amazon Elastic Kubernetes Service node group never joins</td>
<td>Networking or IAM permissions for nodes are wrong</td>
<td>Fix internet routing and review the node IAM policy</td>
</tr>
</tbody></table>
<h3 id="heading-how-to-inspect-the-operator-and-the-externalsecret">How to Inspect the Operator and the ExternalSecret</h3>
<p>When something isn't syncing, start with these two commands.</p>
<pre><code class="language-bash"># Check the ExternalSecret status
kubectl describe externalsecret app-db-secret -n default

# Check the ESO operator logs
kubectl logs -n external-secrets -l app.kubernetes.io/name=external-secrets
</code></pre>
<p>The status conditions on the <code>ExternalSecret</code> resource will usually tell you exactly what failed.</p>
<h3 id="heading-how-to-validate-rotation-from-the-app-side">How to Validate Rotation From the App Side</h3>
<p>When you are debugging rotation, don't rely only on Kubernetes resource state. Use the <code>/secrets/compare</code> endpoint to see what the running application actually observes. The endpoint tells you whether env and volume match and whether rotation has been detected. That is the ground truth for your application's behavior.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>You now have a complete secrets pipeline from AWS Secrets Manager into Kubernetes pods using Terraform and the External Secrets Operator. You ran the local lab, inspected the <code>ExternalSecret</code> and <code>Deployment</code> manifests, and validated that the application sees the right credentials.</p>
<p>You also tested secret rotation and observed the key behavior firsthand: mounted secret files update within the kubelet sync period, while environment variables stay stale until the pod restarts. That single observation explains a large class of production incidents.</p>
<p>Finally, you saw how the same design extends to Amazon Elastic Kubernetes Service with OpenID Connect-based CI/CD, and you have a troubleshooting checklist for the failures most teams hit.</p>
<p>The lab repository is at <a href="https://github.com/Osomudeya/k8s-secret-lab">github.com/Osomudeya/k8s-secret-lab</a>. If you ran the local lab, the natural next step is phases 4 and 5 from the repo's staged learning path: try the CSI driver path on Microk8s, then follow the EKS setup to see the same pipeline with a real CI/CD workflow and no credentials stored in GitHub. Both are documented in the repo and take less than 30 minutes each.</p>
<p>If this helped you, star the repo and share it with someone who is learning Kubernetes.</p>
<p><em>I send weekly breakdowns of real production incidents and how engineers actually fix them, not tutorials but real failures<br>→</em> <a href="https://osomudeya.gumroad.com/subscribe"><em>Join the newsletter</em></a></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Production-Ready DevOps Pipeline with Free Tools ]]>
                </title>
                <description>
                    <![CDATA[ A few months ago, I dove into DevOps, expecting it to be an expensive journey requiring costly tools and infrastructure. But I discovered you can build professional-grade pipelines using entirely free resources. If DevOps feels out of reach because y... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-production-ready-devops-pipeline-with-free-tools/</link>
                <guid isPermaLink="false">680fe1e69418a1165cb184a2</guid>
                
                    <category>
                        <![CDATA[ Devops ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Devops articles ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Beginner Developers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Kubernetes ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Terraform ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ YAML ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Opaluwa Emidowojo ]]>
                </dc:creator>
                <pubDate>Mon, 28 Apr 2025 20:15:34 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1745864420670/f36eb4a7-a24e-4d6e-859f-db7249ae0da0.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>A few months ago, I dove into DevOps, expecting it to be an expensive journey requiring costly tools and infrastructure. But I discovered you can build professional-grade pipelines using entirely free resources.</p>
<p>If DevOps feels out of reach because you’re also concerned about the cost, don't worry. I’ll guide you step-by-step through creating a production-ready pipeline without spending a dime. Let's get started!</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#introduction">Introduction</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-your-source-control-and-project-structure">How to Set Up Your Source Control and Project Structure</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-build-your-ci-pipeline-with-github-actions">How to Build Your CI Pipeline with GitHub Actions</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-optimize-docker-builds-for-ci">How to Optimize Docker Builds for CI</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-infrastructure-as-code-using-terraform-and-free-cloud-providers">Infrastructure as Code Using Terraform and Free Cloud Providers</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-container-orchestration-on-minimal-resources">How to Set Up Container Orchestration on Minimal Resources</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-a-free-deployment-pipeline">How to Create a Free Deployment Pipeline</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-build-a-comprehensive-monitoring-system">How to Build a Comprehensive Monitoring System</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-implement-security-testing-and-scanning">How to Implement Security Testing and Scanning</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-performance-optimization-and-scaling">Performance Optimization and Scaling</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-complete-cicd-pipeline-example">Putting it All Together</a></p>
</li>
<li><p><a class="post-section-overview" href="#conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-prerequisites">🛠 Prerequisites</h2>
<ul>
<li><p><strong>Basic Git knowledge</strong>: Cloning repos, creating branches, committing code, and creating PRs</p>
</li>
<li><p><strong>Familiarity with command line</strong>: For Docker, Terraform, and Kubernetes</p>
</li>
<li><p><strong>Basic understanding of CI/CD</strong>: Continuous integration/delivery concepts and pipelines</p>
</li>
</ul>
<h3 id="heading-accounts-needed">Accounts needed:</h3>
<ul>
<li><p>GitHub account</p>
</li>
<li><p>At least one cloud provider: AWS Free Tier (recommended), Oracle Cloud Free Tier, or Google Cloud/Azure with free credits</p>
</li>
<li><p>Terraform Cloud (free tier) for infrastructure state management</p>
</li>
<li><p>Grafana Cloud (free tier) for monitoring</p>
</li>
<li><p>UptimeRobot (free tier) for external availability checks</p>
</li>
</ul>
<h3 id="heading-tools-to-install-locally">Tools to Install Locally</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Tool</strong></td><td><strong>Purpose</strong></td><td><strong>Installation Link</strong></td></tr>
</thead>
<tbody>
<tr>
<td>Git</td><td>Version control</td><td><a target="_blank" href="https://git-scm.com/downloads"><strong>Install Git</strong></a></td></tr>
<tr>
<td>Docker</td><td>Containerization</td><td><a target="_blank" href="https://docs.docker.com/get-docker/"><strong>Install Docker</strong></a></td></tr>
<tr>
<td>Node.js &amp; npm</td><td>Sample app &amp; builds</td><td><a target="_blank" href="https://nodejs.org/"><strong>Install Node.js</strong></a></td></tr>
<tr>
<td>Terraform</td><td>Infrastructure as Code</td><td><a target="_blank" href="https://www.terraform.io/downloads"><strong>Install Terraform</strong></a></td></tr>
<tr>
<td>kubectl</td><td>Kubernetes CLI</td><td><a target="_blank" href="https://kubernetes.io/docs/tasks/tools/"><strong>Install kubectl</strong></a></td></tr>
<tr>
<td>k3d</td><td>Lightweight Kubernetes</td><td><a target="_blank" href="https://k3d.io/"><strong>Install k3d</strong></a></td></tr>
<tr>
<td>Trivy</td><td>Container security scanning</td><td><a target="_blank" href="https://aquasecurity.github.io/trivy/v0.18.3/"><strong>Install Trivy</strong></a></td></tr>
<tr>
<td>OWASP ZAP</td><td>Web security scanning</td><td><a target="_blank" href="https://www.zaproxy.org/download/"><strong>Install ZAP</strong></a></td></tr>
</tbody>
</table>
</div><p><strong>Optional but Helpful:</strong></p>
<ul>
<li><p><a target="_blank" href="https://code.visualstudio.com/"><strong>VS Code</strong></a> or any good code editor</p>
</li>
<li><p>Postman for testing APIs</p>
</li>
<li><p>Understanding of YAML and Dockerfiles</p>
</li>
</ul>
<h2 id="heading-introduction">Introduction</h2>
<p>When people hear "DevOps," they often picture complex enterprise systems powered by pricey tools and premium cloud services. But the truth is, you don't actually need a massive budget to build a solid, professional-grade DevOps pipeline. The foundations of good DevOps – automation, consistency, security, and visibility – can be built entirely with free tools.</p>
<p>In this guide, you will learn how to build a production-ready DevOps pipeline using zero-cost resources. We will use a simple CRUD (Create, Read, Update, Delete) app with frontend, backend API, and database as our example project to demonstrate every step of the process.</p>
<h2 id="heading-how-to-set-up-your-source-control-and-project-structure">How to Set Up Your Source Control and Project Structure</h2>
<h3 id="heading-1-create-a-well-structured-repository">1. Create a Well-Structured Repository</h3>
<p>A clean repo is the foundation of your pipeline. We will set up:</p>
<ul>
<li><p>Separate folders for <code>frontend</code>, <code>backend</code>, and <code>infrastructure</code></p>
</li>
<li><p>A <code>.github</code> folder to hold workflow configurations</p>
</li>
<li><p>Clear naming conventions and a well-written <code>README.md</code></p>
</li>
</ul>
<p>🛠 <strong>Tip</strong>: Use semantic commit messages and consider adopting <a target="_blank" href="https://www.conventionalcommits.org/"><strong>Conventional Commits</strong></a> for clarity in versioning and changelogs.</p>
<h3 id="heading-2-set-up-branch-protection-without-paid-features">2. Set Up Branch Protection Without Paid Features</h3>
<p>While GitHub's more advanced rules require Pro, you can still:</p>
<ul>
<li><p>Require pull requests before merging</p>
</li>
<li><p>Enable status checks to prevent broken code from landing in <code>main</code></p>
</li>
<li><p>Enforce linear history for cleaner version control</p>
</li>
</ul>
<p>💡 This makes your project safer and more collaborative, without needing GitHub Enterprise.</p>
<h3 id="heading-3-implement-pr-templates-and-automated-checks">3. Implement PR Templates and Automated Checks</h3>
<p>Make your reviews smoother:</p>
<ul>
<li><p>Add a <code>PULL_REQUEST_TEMPLATE.md</code> to guide contributors</p>
</li>
<li><p>Use GitHub Actions (which we'll set up in the next part) for linting, tests, and formatting checks</p>
</li>
</ul>
<p>✨ These tiny improvements add polish and professionalism.</p>
<h3 id="heading-4-configure-github-issue-templates-and-project-boards">4. Configure GitHub Issue Templates and Project Boards</h3>
<p>Even solo developers benefit from issue tracking:</p>
<ul>
<li><p>Add issue templates for bugs and features</p>
</li>
<li><p>Use GitHub Projects to manage work with a Kanban board, all free and native to GitHub</p>
</li>
</ul>
<p>📌 <strong>Bonus</strong>: This setup lays the groundwork for GitOps practices later on.</p>
<h3 id="heading-5-advanced-technique-set-up-custom-validation-scripts-as-pre-commit-hooks">5. Advanced Technique: Set Up Custom Validation Scripts as Pre-Commit Hooks</h3>
<p>Before code ever hits GitHub, you can catch issues locally with Git hooks. Using a tool like <a target="_blank" href="https://typicode.github.io/husky/"><strong>Husky</strong></a> or <a target="_blank" href="https://pre-commit.com/"><strong>pre-commit</strong></a>, you can:</p>
<ul>
<li><p>Lint code before it's committed</p>
</li>
<li><p>Run tests or formatters automatically</p>
</li>
<li><p>Prevent secrets from being accidentally committed</p>
</li>
</ul>
<pre><code class="lang-json"><span class="hljs-comment">// Initialize Husky and install needed dependencies</span>
<span class="hljs-comment">// Then add a pre-commit hook that runs tests before allowing the commit</span>
npx husky-init &amp;&amp; npm install
npx husky add .husky/pre-commit <span class="hljs-string">"npm test"</span>
</code></pre>
<h3 id="heading-6-sample-crud-app-setup"><strong>6. Sample CRUD App Setup:</strong></h3>
<p>Our CRUD app manages users (create, read, update, delete). Below is the minimal code with comments to explain each part:</p>
<p><strong>Backend</strong> <code>(backend/)</code>:</p>
<pre><code class="lang-json"><span class="hljs-comment">// backend/package.json</span>
{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"crud-backend"</span>, <span class="hljs-comment">// Name of the backend project</span>
  <span class="hljs-attr">"version"</span>: <span class="hljs-string">"1.0.0"</span>, <span class="hljs-comment">// Version for tracking changes</span>
  <span class="hljs-attr">"scripts"</span>: {
    <span class="hljs-attr">"start"</span>: <span class="hljs-string">"node index.js"</span>, <span class="hljs-comment">// Runs the server</span>
    <span class="hljs-attr">"test"</span>: <span class="hljs-string">"echo 'Add tests here'"</span>, <span class="hljs-comment">// Placeholder for tests (update with Jest later)</span>
    <span class="hljs-attr">"lint"</span>: <span class="hljs-string">"eslint ."</span> <span class="hljs-comment">// Checks code style with ESLint</span>
  },
  <span class="hljs-attr">"dependencies"</span>: {
    <span class="hljs-attr">"express"</span>: <span class="hljs-string">"^4.17.1"</span>, <span class="hljs-comment">// Web framework for API endpoints</span>
    <span class="hljs-attr">"pg"</span>: <span class="hljs-string">"^8.7.3"</span> <span class="hljs-comment">// PostgreSQL client to connect to the database</span>
  },
  <span class="hljs-attr">"devDependencies"</span>: {
    <span class="hljs-attr">"eslint"</span>: <span class="hljs-string">"^8.0.0"</span> <span class="hljs-comment">// Linting tool for code quality</span>
  }
}
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-comment">// backend/index.js</span>
<span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>); <span class="hljs-comment">// Import Express for building the API</span>
<span class="hljs-keyword">const</span> { Pool } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'pg'</span>); <span class="hljs-comment">// Import PostgreSQL client</span>
<span class="hljs-keyword">const</span> app = express(); <span class="hljs-comment">// Create an Express app</span>
app.use(express.json()); <span class="hljs-comment">// Parse JSON request bodies</span>

<span class="hljs-comment">// Connect to PostgreSQL using DATABASE_URL from environment variables</span>
<span class="hljs-keyword">const</span> pool = <span class="hljs-keyword">new</span> Pool({ <span class="hljs-attr">connectionString</span>: process.env.DATABASE_URL });

<span class="hljs-comment">// Health check endpoint for Kubernetes probes and monitoring</span>
app.get(<span class="hljs-string">'/healthz'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> res.json({ <span class="hljs-attr">status</span>: <span class="hljs-string">'ok'</span> }));

<span class="hljs-comment">// Get all users from the database</span>
app.get(<span class="hljs-string">'/users'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> { rows } = <span class="hljs-keyword">await</span> pool.query(<span class="hljs-string">'SELECT * FROM users'</span>); <span class="hljs-comment">// Query the users table</span>
  res.json(rows); <span class="hljs-comment">// Send users as JSON</span>
});

<span class="hljs-comment">// Add a new user to the database</span>
app.post(<span class="hljs-string">'/users'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> { name } = req.body; <span class="hljs-comment">// Get name from request body</span>
  <span class="hljs-comment">// Insert user and return the new record</span>
  <span class="hljs-keyword">const</span> { rows } = <span class="hljs-keyword">await</span> pool.query(<span class="hljs-string">'INSERT INTO users(name) VALUES($1) RETURNING *'</span>, [name]);
  res.json(rows[<span class="hljs-number">0</span>]); <span class="hljs-comment">// Send the new user as JSON</span>
});

<span class="hljs-comment">// Start the server on port 3000</span>
app.listen(<span class="hljs-number">3000</span>, <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Backend running on port 3000'</span>));
</code></pre>
<p><strong>Frontend</strong> <code>(frontend/)</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// frontend/package.json</span>
{
  <span class="hljs-string">"name"</span>: <span class="hljs-string">"crud-frontend"</span>, <span class="hljs-comment">// Name of the frontend project</span>
  <span class="hljs-string">"version"</span>: <span class="hljs-string">"1.0.0"</span>, <span class="hljs-comment">// Version for tracking changes</span>
  <span class="hljs-string">"scripts"</span>: {
    <span class="hljs-string">"start"</span>: <span class="hljs-string">"react-scripts start"</span>, <span class="hljs-comment">// Runs the dev server</span>
    <span class="hljs-string">"build"</span>: <span class="hljs-string">"react-scripts build"</span>, <span class="hljs-comment">// Builds for production</span>
    <span class="hljs-string">"test"</span>: <span class="hljs-string">"react-scripts test"</span>, <span class="hljs-comment">// Runs tests (placeholder for Jest)</span>
    <span class="hljs-string">"lint"</span>: <span class="hljs-string">"eslint ."</span> <span class="hljs-comment">// Checks code style with ESLint</span>
  },
  <span class="hljs-string">"dependencies"</span>: {
    <span class="hljs-string">"react"</span>: <span class="hljs-string">"^17.0.2"</span>, <span class="hljs-comment">// Core React library</span>
    <span class="hljs-string">"react-dom"</span>: <span class="hljs-string">"^17.0.2"</span>, <span class="hljs-comment">// Renders React to the DOM</span>
    <span class="hljs-string">"react-scripts"</span>: <span class="hljs-string">"^4.0.3"</span>, <span class="hljs-comment">// Scripts for React development</span>
    <span class="hljs-string">"axios"</span>: <span class="hljs-string">"^0.24.0"</span> <span class="hljs-comment">// HTTP client for API calls</span>
  },
  <span class="hljs-string">"devDependencies"</span>: {
    <span class="hljs-string">"eslint"</span>: <span class="hljs-string">"^8.0.0"</span> <span class="hljs-comment">// Linting tool for code quality</span>
  }
}
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-comment">// frontend/src/App.js</span>
<span class="hljs-keyword">import</span> React, { useState, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>; <span class="hljs-comment">// Import React and hooks</span>
<span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>; <span class="hljs-comment">// Import Axios for API requests</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// State for storing users fetched from the backend</span>
  <span class="hljs-keyword">const</span> [users, setUsers] = useState([]);
  <span class="hljs-comment">// State for the input field to add a new user</span>
  <span class="hljs-keyword">const</span> [name, setName] = useState(<span class="hljs-string">''</span>);

  <span class="hljs-comment">// Fetch users when the component mounts</span>
  useEffect(<span class="hljs-function">() =&gt;</span> {
    axios.get(<span class="hljs-string">'http://localhost:3000/users'</span>).then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> setUsers(res.data));
  }, []); <span class="hljs-comment">// Empty array means run once on mount</span>

  <span class="hljs-comment">// Add a new user via the API</span>
  <span class="hljs-keyword">const</span> addUser = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> axios.post(<span class="hljs-string">'http://localhost:3000/users'</span>, { name }); <span class="hljs-comment">// Post new user</span>
    setUsers([...users, res.data]); <span class="hljs-comment">// Update users list</span>
    setName(<span class="hljs-string">''</span>); <span class="hljs-comment">// Clear input field</span>
  };

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Users<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      {/* Input for new user name */}
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{name}</span> <span class="hljs-attr">onChange</span>=<span class="hljs-string">{e</span> =&gt;</span> setName(e.target.value)} /&gt;
      {/* Button to add user */}
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{addUser}</span>&gt;</span>Add User<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      {/* List all users */}
      <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>{users.map(user =&gt; <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{user.id}</span>&gt;</span>{user.name}<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>)}<span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App; <span class="hljs-comment">// Export the component</span>
</code></pre>
<p><strong>Database Setup</strong>:</p>
<pre><code class="lang-pgsql"><span class="hljs-comment">-- infra/db.sql</span>
<span class="hljs-comment">-- Create a table to store users</span>
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> users (
  id <span class="hljs-type">SERIAL</span> <span class="hljs-keyword">PRIMARY KEY</span>, <span class="hljs-comment">-- Auto-incrementing ID</span>
  <span class="hljs-type">name</span> <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">100</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">NULL</span> <span class="hljs-comment">-- User name, required</span>
);
</code></pre>
<pre><code class="lang-javascript">crud-app/
├── backend/
│   ├── package.json
│   └── index.js
├── frontend/
│   ├── package.json
│   └── src/App.js
├── infra/
│   └── db.sql
├── .github/
│   └── workflows/
└── README.md
</code></pre>
<p>This app provides a <code>/users</code> endpoint (GET/POST) and a frontend to list/add users, stored in PostgreSQL. The <code>/healthz</code> endpoint supports monitoring. Save this code in your repo to follow the pipeline steps.</p>
<h2 id="heading-how-to-build-your-ci-pipeline-with-github-actions">How to Build Your CI Pipeline with GitHub Actions</h2>
<h3 id="heading-1-set-up-your-first-github-actions-workflow">1. Set Up Your First GitHub Actions Workflow</h3>
<p>First, let’s create a basic workflow that automatically builds, tests, and lints your app every time you push code or open a pull request. This ensures your app stays healthy and any issues are caught early.</p>
<p>Create a file at <code>.github/workflows/ci.yml</code> and add the following:</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># CI workflow to build, test, and lint the CRUD app on push or pull request</span>
<span class="hljs-attr">name:</span> <span class="hljs-string">CI</span> <span class="hljs-string">Pipeline</span>
<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span> [<span class="hljs-string">main</span>] <span class="hljs-comment"># Trigger on pushes to main branch</span>
  <span class="hljs-attr">pull_request:</span>
    <span class="hljs-attr">branches:</span> [<span class="hljs-string">main</span>] <span class="hljs-comment"># Trigger on PRs to main branch</span>
<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">build:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span> <span class="hljs-comment"># Use GitHub's free Linux runner</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span> <span class="hljs-comment"># Check out the repository code</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Set</span> <span class="hljs-string">up</span> <span class="hljs-string">Node.js</span> <span class="hljs-comment"># Install Node.js environment</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-node@v3</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">node-version:</span> <span class="hljs-string">'18'</span> <span class="hljs-comment"># Use Node.js 18 for consistency</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Cache</span> <span class="hljs-string">dependencies</span> <span class="hljs-comment"># Cache node_modules to speed up builds</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/cache@v3</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">~/.npm</span> <span class="hljs-comment"># Cache npm’s global cache</span>
          <span class="hljs-attr">key:</span> <span class="hljs-string">${{</span> <span class="hljs-string">runner.os</span> <span class="hljs-string">}}-node-${{</span> <span class="hljs-string">hashFiles('**/package-lock.json')</span> <span class="hljs-string">}}</span> <span class="hljs-comment"># Key based on OS and package-lock.json</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">ci</span> <span class="hljs-comment"># Install dependencies reliably using package-lock.json</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">test</span> <span class="hljs-comment"># Run tests defined in package.json</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">lint</span> <span class="hljs-comment"># Run ESLint to ensure code quality</span>
</code></pre>
<p>This workflow automatically runs on every push and pull request to the <code>main</code> branch. It installs dependencies, runs tests, and performs code linting, with dependency caching to make builds faster over time.</p>
<p><strong>Common Issues and Fixes</strong>:</p>
<ul>
<li><p><strong>“Secret not found”</strong>: Ensure <code>AWS_ACCESS_KEY_ID</code> is in repository secrets (Settings → Secrets).</p>
</li>
<li><p><strong>Tests fail</strong>: Check <code>test/users.test.js</code> for database connectivity.</p>
</li>
</ul>
<h4 id="heading-understanding-github-actions-free-tier-limits">Understanding GitHub Actions' Free Tier Limits</h4>
<p>Before building more workflows, it is important to know what GitHub offers for free.</p>
<p>If you are working on private repositories, you get 2,000 free minutes per month. For public repositories, you get unlimited minutes.</p>
<p>To avoid hitting limits quickly:</p>
<ul>
<li><p>Cache your dependencies to cut down install times.</p>
</li>
<li><p>Only trigger workflows on meaningful branches (like <code>main</code> or <code>release</code>).</p>
</li>
<li><p>Skip unnecessary steps when you can.</p>
</li>
</ul>
<h3 id="heading-2-creating-a-multi-stage-build-pipeline">2. Creating a Multi-Stage Build Pipeline</h3>
<p>As your app grows, it is better to split your CI pipeline into clear stages like <strong>install</strong>, <strong>test</strong>, and <strong>lint</strong>. This structure makes workflows easier to maintain and speeds things up, because some jobs can run in parallel.</p>
<p>Here’s how you can split the work into multiple jobs for better clarity:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">install:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">ci</span>  <span class="hljs-comment"># Clean install of dependencies</span>

  <span class="hljs-attr">test:</span>
    <span class="hljs-attr">needs:</span> <span class="hljs-string">install</span>  <span class="hljs-comment"># This job depends on the install job finishing</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">test</span>  <span class="hljs-comment"># Run test suite</span>

  <span class="hljs-attr">lint:</span>
    <span class="hljs-attr">needs:</span> <span class="hljs-string">install</span>  <span class="hljs-comment"># This job also depends on install but runs in parallel with test</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">lint</span>  <span class="hljs-comment"># Run linting checks</span>
</code></pre>
<p>By breaking the pipeline into stages, you can quickly spot which step fails, and your test and lint jobs can run at the same time after dependencies are installed.</p>
<h3 id="heading-3-implement-matrix-builds-for-cross-environment-testing">3. Implement Matrix Builds for Cross-Environment Testing</h3>
<p>When you want your app to work across different Node.js versions or databases, matrix builds are your best bet. They let you test across multiple environments in parallel, without duplicating code.</p>
<p>Here’s how you can set up a matrix strategy, to test across multiple environments simultaneously:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">test:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">strategy:</span>
      <span class="hljs-attr">matrix:</span>
        <span class="hljs-attr">node-version:</span> [<span class="hljs-number">14.</span><span class="hljs-string">x</span>, <span class="hljs-number">16.</span><span class="hljs-string">x</span>, <span class="hljs-number">18.</span><span class="hljs-string">x</span>]  <span class="hljs-comment"># Test on multiple Node versions</span>
        <span class="hljs-attr">database:</span> [<span class="hljs-string">postgres</span>, <span class="hljs-string">mysql</span>]        <span class="hljs-comment"># Test against different databases</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Use</span> <span class="hljs-string">Node.js</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.node-version</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-node@v3</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">node-version:</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.node-version</span> <span class="hljs-string">}}</span>
      <span class="hljs-bullet">-</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">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">test</span>  <span class="hljs-comment"># This will run 6 different test combinations (3 Node versions × 2 databases)</span>
</code></pre>
<p>Matrix builds save time and help you catch environment-specific bugs early.</p>
<h3 id="heading-4-optimize-workflow-with-dependency-caching">4. Optimize Workflow with Dependency Caching</h3>
<p>Every second counts in CI. Dependency caching can help save minutes in your workflow by reusing previously installed packages instead of reinstalling them from scratch every time.</p>
<p>Here’s how to set up smart caching to speed up your builds:</p>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Cache</span> <span class="hljs-string">node</span> <span class="hljs-string">modules</span>
  <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/cache@v3</span>
  <span class="hljs-attr">with:</span>
    <span class="hljs-attr">path:</span> <span class="hljs-string">|</span>  <span class="hljs-comment"># Cache both global npm cache and local node_modules</span>
      <span class="hljs-string">~/.npm</span>
      <span class="hljs-string">node_modules</span>
    <span class="hljs-attr">key:</span> <span class="hljs-string">${{</span> <span class="hljs-string">runner.os</span> <span class="hljs-string">}}-node-${{</span> <span class="hljs-string">hashFiles('**/package-lock.json')</span> <span class="hljs-string">}}</span>  <span class="hljs-comment"># Cache key based on OS and dependencies</span>
    <span class="hljs-attr">restore-keys:</span> <span class="hljs-string">|</span>  <span class="hljs-comment"># Fallback keys if exact match isn't found</span>
      <span class="hljs-string">${{</span> <span class="hljs-string">runner.os</span> <span class="hljs-string">}}-node-</span>
</code></pre>
<p>This cache setup checks if your dependencies have changed. If not, it restores the cache, making builds significantly faster.</p>
<h2 id="heading-how-to-optimize-docker-builds-for-ci">How to Optimize Docker Builds for CI</h2>
<p>When you're building Docker images in CI, build time can quickly become a bottleneck. Especially if your images are large. Optimizing your Docker builds makes your pipelines much faster, saves bandwidth, and produces smaller, more efficient images ready for deployment.</p>
<p>In this section, I’ll walk through creating a basic Dockerfile, using multi-stage builds, caching layers, and enabling BuildKit for even faster builds.</p>
<h3 id="heading-1-create-a-baseline-dockerfile">1. Create a Baseline Dockerfile</h3>
<p>First, start with a simple Dockerfile that installs your app’s dependencies and runs it. This is what you’ll be optimizing later.</p>
<pre><code class="lang-dockerfile"><span class="hljs-comment"># Simple Dockerfile for a Node.js application</span>
<span class="hljs-keyword">FROM</span> node:<span class="hljs-number">18</span>-alpine  <span class="hljs-comment"># Use Alpine for a smaller base image</span>
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app         <span class="hljs-comment"># Set working directory</span></span>
<span class="hljs-keyword">COPY</span><span class="bash"> . .             <span class="hljs-comment"># Copy all files to container</span></span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm ci           <span class="hljs-comment"># Install dependencies (clean install)</span></span>
<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"npm"</span>, <span class="hljs-string">"start"</span>] <span class="hljs-comment"># Start the application</span></span>
</code></pre>
<p>Using an Alpine-based Node.js image helps keep your image small from the start.</p>
<h3 id="heading-2-multi-stage-docker-builds">2. Multi-Stage Docker Builds</h3>
<p>Next, let's separate the build process from the production image. Multi-stage builds let you compile or build your app in one stage and only copy over the final product to a clean, smaller image. This keeps production images lean:</p>
<pre><code class="lang-dockerfile"><span class="hljs-comment"># Stage 1: Build the application</span>
<span class="hljs-keyword">FROM</span> node:<span class="hljs-number">18</span>-alpine AS builder
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>
<span class="hljs-keyword">COPY</span><span class="bash"> package*.json ./  <span class="hljs-comment"># Copy package files first for better caching</span></span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm ci             <span class="hljs-comment"># Install all dependencies</span></span>
<span class="hljs-keyword">COPY</span><span class="bash"> . .               <span class="hljs-comment"># Then copy source code</span></span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm run build      <span class="hljs-comment"># Build the application</span></span>

<span class="hljs-comment"># Stage 2: Production image with minimal footprint</span>
<span class="hljs-keyword">FROM</span> node:<span class="hljs-number">18</span>-alpine
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>
<span class="hljs-comment"># Only copy built assets and production dependencies</span>
<span class="hljs-keyword">COPY</span><span class="bash"> --from=builder /app/dist ./dist</span>
<span class="hljs-keyword">COPY</span><span class="bash"> --from=builder /app/package*.json ./</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm ci --production  <span class="hljs-comment"># Install only production dependencies</span></span>
<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"node"</span>, <span class="hljs-string">"dist/server.js"</span>]  <span class="hljs-comment"># Run the built application</span></span>
</code></pre>
<p>This approach keeps your production images lightweight and secure by excluding unnecessary build tools and dev dependencies.</p>
<h3 id="heading-3-optimizing-layer-caching">3. Optimizing Layer Caching</h3>
<p>For even faster builds, order your <code>Dockerfile</code> instructions to maximize layer caching. Copy and install dependencies <em>before</em> copying your full source code.</p>
<p>This way, Docker reuses the cached npm install step if your dependencies haven't changed, even if you edit your app's code:</p>
<ul>
<li><p>First: <code>COPY package*.json ./</code></p>
</li>
<li><p>Then: <code>RUN npm ci</code></p>
</li>
<li><p>Finally: <code>COPY . .</code></p>
</li>
</ul>
<h3 id="heading-4-enable-buildkit-for-faster-builds">4. Enable BuildKit for Faster Builds</h3>
<p>Docker BuildKit is a newer build engine that enables features like better caching, parallel build steps, and overall faster builds.</p>
<p>To enable BuildKit during your CI, run:</p>
<pre><code class="lang-dockerfile">- name: Build Docker image
  <span class="hljs-keyword">run</span><span class="bash">: |</span>
    <span class="hljs-comment"># Enable BuildKit for parallel and more efficient builds</span>
    DOCKER_BUILDKIT=<span class="hljs-number">1</span> docker build -t myapp:latest .
</code></pre>
<p>Turning on BuildKit can significantly speed up complex Docker builds and is highly recommended for all CI pipelines.</p>
<h2 id="heading-infrastructure-as-code-using-terraform-and-free-cloud-providers">Infrastructure as Code Using Terraform and Free Cloud Providers</h2>
<h3 id="heading-why-infrastructure-as-code-iac-matters">Why Infrastructure as Code (IaC) Matters</h3>
<p>When you manage infrastructure manually – that is, clicking around cloud dashboards or setting things up by hand – it’s easy to lose track of what you did and how to repeat it.</p>
<p>Infrastructure as Code (IaC) solves this by letting you define your infrastructure with code, version it just like application code, and track every change over time. This makes your setups easy to replicate across environments (development, staging, production), ensures changes are declarative and auditable, and reduces human error.</p>
<p>Whether you are spinning up a single server or scaling a complex system, IaC lays the foundation for professional-grade infrastructure from day one, letting you automate, document, and grow your environment systematically.</p>
<h3 id="heading-how-to-provision-infrastructure-with-terraform">How to Provision Infrastructure with Terraform</h3>
<h4 id="heading-initialize-a-terraform-project">Initialize a Terraform Project</h4>
<p>First, define the providers and versions you need. Here, we’re using Render’s free cloud hosting service:</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># Define required providers and versions</span>
<span class="hljs-string">terraform</span> {
  <span class="hljs-string">required_providers</span> {
    <span class="hljs-string">render</span> <span class="hljs-string">=</span> {
      <span class="hljs-string">source</span>  <span class="hljs-string">=</span> <span class="hljs-string">"renderinc/render"</span>  <span class="hljs-comment"># Using Render's free tier</span>
      <span class="hljs-string">version</span> <span class="hljs-string">=</span> <span class="hljs-string">"0.1.0"</span>             <span class="hljs-comment"># Specify provider version for stability</span>
    }
  }
}

<span class="hljs-comment"># Configure the Render provider with authentication</span>
<span class="hljs-string">provider</span> <span class="hljs-string">"render"</span> {
  <span class="hljs-string">api_key</span> <span class="hljs-string">=</span> <span class="hljs-string">var.render_api_key</span>  <span class="hljs-comment"># Store API key as a variable</span>
}
</code></pre>
<p>Then, configure the provider by authenticating with your API key. It is best practice to store secrets like API keys in variables instead of hardcoding them. This setup tells Terraform what platform you’re working with (Render) and how to authenticate to manage resources automatically.</p>
<h4 id="heading-provision-a-web-app-on-render">Provision a Web App on Render</h4>
<p>Next, define the infrastructure you want – in this case, a web service hosted on Render:</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># Define a web service on Render's free tier</span>
<span class="hljs-string">resource</span> <span class="hljs-string">"render_service"</span> <span class="hljs-string">"web_app"</span> {
  <span class="hljs-string">name</span> <span class="hljs-string">=</span> <span class="hljs-string">"ci-demo-app"</span>                                 <span class="hljs-comment"># Service name</span>
  <span class="hljs-string">type</span> <span class="hljs-string">=</span> <span class="hljs-string">"web_service"</span>                                 <span class="hljs-comment"># Type of service</span>
  <span class="hljs-string">repo</span> <span class="hljs-string">=</span> <span class="hljs-string">"https://github.com/YOUR-USERNAME/YOUR-REPO"</span>  <span class="hljs-comment"># Source repo</span>
  <span class="hljs-string">env</span> <span class="hljs-string">=</span> <span class="hljs-string">"docker"</span>                                       <span class="hljs-comment"># Use Docker environment</span>
  <span class="hljs-string">plan</span> <span class="hljs-string">=</span> <span class="hljs-string">"starter"</span>                                     <span class="hljs-comment"># Free tier plan</span>
  <span class="hljs-string">branch</span> <span class="hljs-string">=</span> <span class="hljs-string">"main"</span>                                      <span class="hljs-comment"># Deploy from main branch</span>
  <span class="hljs-string">build_command</span> <span class="hljs-string">=</span> <span class="hljs-string">"docker build -t app ."</span>              <span class="hljs-comment"># Build command</span>
  <span class="hljs-string">start_command</span> <span class="hljs-string">=</span> <span class="hljs-string">"docker run -p 3000:3000 app"</span>        <span class="hljs-comment"># Start command</span>
  <span class="hljs-string">auto_deploy</span> <span class="hljs-string">=</span> <span class="hljs-literal">true</span>                                   <span class="hljs-comment"># Auto-deploy on commits</span>
}
</code></pre>
<p>This resource block describes exactly how your app should be deployed. Whenever you change this file and reapply, Terraform will update the infrastructure to match.</p>
<h4 id="heading-provision-postgresql-for-free">Provision PostgreSQL for Free</h4>
<p>Most applications need a database, but you don't have to pay for one when you're getting started. Platforms like <a target="_blank" href="https://railway.app/">Railway</a> offer free tiers that are perfect for development and small projects.</p>
<p>You can quickly create a free PostgreSQL instance by signing up on the platform and clicking <strong>"Create New Project"</strong>. At the end, you'll get a <code>DATABASE_URL</code> a connection string that your app will use to talk to the database.</p>
<h4 id="heading-connect-app-to-db">Connect App to DB</h4>
<p>In Render (or whatever platform you're using), set an environment variable called <code>DATABASE_URL</code> and paste in the connection string from your PostgreSQL provider. This lets your application securely access the database without hardcoding credentials into your codebase.</p>
<h4 id="heading-make-it-reproducible">Make it Reproducible</h4>
<p>Once everything is defined, use Terraform to create and apply an infrastructure plan:</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># Create execution plan and save it to a file</span>
<span class="hljs-string">terraform</span> <span class="hljs-string">plan</span> <span class="hljs-string">-out=infra.tfplan</span>
<span class="hljs-comment"># Apply the saved plan exactly as planned</span>
<span class="hljs-string">terraform</span> <span class="hljs-string">apply</span> <span class="hljs-string">infra.tfplan</span>
</code></pre>
<p>Saving the plan to a file (<code>infra.tfplan</code>) ensures you’re applying exactly what you reviewed, so there will be no surprises.</p>
<p><strong>Common Issues and Fixes</strong>:</p>
<ul>
<li><p><strong>Provider not found</strong>: Run <code>terraform init</code>.</p>
</li>
<li><p><strong>API key error</strong>: Check <code>render_api_key</code> in Terraform Cloud variables.</p>
</li>
</ul>
<h2 id="heading-how-to-set-up-container-orchestration-on-minimal-resources">How to Set Up Container Orchestration on Minimal Resources</h2>
<p>When you're working with limited resources like a laptop, a small server, or a lightweight cloud VM, setting up full Kubernetes can be overwhelming. Instead, you can use <strong>K3d</strong>, a lightweight Kubernetes distribution that runs inside Docker containers. Here's how to set up a minimal, efficient cluster for local development or testing.</p>
<h3 id="heading-1-install-k3d-for-local-kubernetes">1. Install K3d for Local Kubernetes</h3>
<p>First, install K3d. It's a super lightweight way to run Kubernetes clusters inside Docker without needing a heavy setup like Minikube.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Download and install K3d - a lightweight K8s distribution</span>
curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash
</code></pre>
<h3 id="heading-2-create-a-lightweight-k3d-cluster">2. Create a Lightweight K3d Cluster</h3>
<p>Once K3d is installed, you can spin up a cluster with minimal nodes to save resources.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Create a minimal K8s cluster with 1 server and 2 agent nodes</span>
k3d cluster create dev-cluster \
  --servers 1 \                        <span class="hljs-comment"># Single server node to minimize resource usage</span>
  --agents 2 \                         <span class="hljs-comment"># Two worker nodes for pod distribution</span>
  --volume /tmp/k3dvol:/tmp/k3dvol \   <span class="hljs-comment"># Mount local volume for persistence</span>
  --port 8080:80@loadbalancer \        <span class="hljs-comment"># Map port 8080 locally to 80 in the cluster</span>
  --api-port 6443                      <span class="hljs-comment"># Set the API port</span>
</code></pre>
<p>This setup gives you a <strong>tiny but real Kubernetes cluster</strong> that is perfect for experimentation.</p>
<h3 id="heading-3-deploy-with-optimized-kubernetes-manifests">3. Deploy with Optimized Kubernetes Manifests</h3>
<p>Now that your cluster is running, you can deploy your app. It's important to define resource requests and limits carefully so your pods don’t consume too much memory or CPU.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Resource-optimized deployment manifest</span>
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp  <span class="hljs-comment"># Name of the deployment</span>
spec:
  replicas: 1   <span class="hljs-comment"># Single replica to save resources</span>
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp
    spec:
      containers:
        - name: app
          image: myapp:latest
          resources:
            <span class="hljs-comment"># Set minimal resource requests</span>
            requests:
              memory: <span class="hljs-string">"64Mi"</span>   <span class="hljs-comment"># Request only 64MB memory</span>
              cpu: <span class="hljs-string">"50m"</span>       <span class="hljs-comment"># Request only 5% of a CPU core</span>
            <span class="hljs-comment"># Set reasonable limits</span>
            limits:
              memory: <span class="hljs-string">"128Mi"</span>  <span class="hljs-comment"># Limit to 128MB memory</span>
              cpu: <span class="hljs-string">"100m"</span>      <span class="hljs-comment"># Limit to 10% of a CPU core</span>
</code></pre>
<p>This ensures Kubernetes knows how much to allocate and avoid overloading your lightweight environment.</p>
<h3 id="heading-4-set-up-gitops-with-flux">4. Set up GitOps with Flux</h3>
<p>To manage deployments automatically from your GitHub repository, you can set up GitOps using Flux.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Install Flux CLI</span>
brew install fluxcd/tap/flux

<span class="hljs-comment"># Bootstrap Flux on your cluster connected to your GitHub repository</span>
flux bootstrap github \
  --owner=YOUR_GITHUB_USERNAME \    <span class="hljs-comment"># Your GitHub username</span>
  --repository=YOUR_REPO_NAME \     <span class="hljs-comment"># Repository to store Flux manifests</span>
  --branch=main \                   <span class="hljs-comment"># Branch to use</span>
  --path=clusters/dev-cluster \     <span class="hljs-comment"># Path within repo for cluster configs</span>
  --personal                        <span class="hljs-comment"># Flag for personal account</span>
</code></pre>
<p>Flux watches your repo and applies updates to your cluster, keeping everything declarative and reproducible.</p>
<p><strong>Common Issues and Fixes</strong>:</p>
<ul>
<li><p><strong>Pods crash</strong>: Run <code>kubectl logs pod-name</code> or increase resources.</p>
</li>
<li><p><strong>Flux sync fails</strong>: Check GitHub token permissions.</p>
</li>
</ul>
<h2 id="heading-how-to-create-a-free-deployment-pipeline">How to Create a Free Deployment Pipeline</h2>
<p>Like I said initially, not every project needs expensive infrastructure. If you're just getting started or building side projects, free tiers from cloud providers can cover a lot of ground.</p>
<h3 id="heading-1-understanding-free-tier-limitations">1. Understanding Free Tier Limitations</h3>
<p>Here’s a quick overview of popular cloud free tiers:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Provider</td><td>Free Tier Highlights</td></tr>
</thead>
<tbody>
<tr>
<td>AWS Free Tier</td><td>750 hours/month EC2, 5GB S3, 1M Lambda requests</td></tr>
<tr>
<td>Oracle Cloud Free Tier</td><td>2 always-free compute instances, 30GB storage</td></tr>
<tr>
<td>Google Cloud Free Tier</td><td>1 f1-micro instance, 5GB storage</td></tr>
</tbody>
</table>
</div><p>Knowing these limits helps you stay within budget.</p>
<h3 id="heading-2-set-up-deployment-workflows">2. Set Up Deployment Workflows</h3>
<p>You can automate deployments with GitHub Actions. Here's an example of a deployment workflow to AWS:</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># GitHub Action workflow for deploying to AWS</span>
<span class="hljs-attr">name:</span> <span class="hljs-string">AWS</span> <span class="hljs-string">Deployment</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"># Deploy on push to 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">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>  <span class="hljs-comment"># Check out code</span>

      <span class="hljs-comment"># Set up AWS credentials from GitHub secrets</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Set</span> <span class="hljs-string">up</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@v1</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">us-east-1</span>

      <span class="hljs-comment"># Build the Docker image</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">Docker</span> <span class="hljs-string">Image</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">docker</span> <span class="hljs-string">build</span> <span class="hljs-string">-t</span> <span class="hljs-string">myapp</span> <span class="hljs-string">.</span>

      <span class="hljs-comment"># Push the image to AWS ECR</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Push</span> <span class="hljs-string">Docker</span> <span class="hljs-string">Image</span> <span class="hljs-string">to</span> <span class="hljs-string">ECR</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          # Create repository if it doesn't exist (ignoring errors if it does)
          aws ecr create-repository --repository-name myapp || true
</span>
          <span class="hljs-comment"># Login to ECR</span>
          <span class="hljs-string">aws</span> <span class="hljs-string">ecr</span> <span class="hljs-string">get-login-password</span> <span class="hljs-string">|</span> <span class="hljs-string">docker</span> <span class="hljs-string">login</span> <span class="hljs-string">--username</span> <span class="hljs-string">AWS</span> <span class="hljs-string">--password-stdin</span> <span class="hljs-string">&lt;aws_account_id&gt;.dkr.ecr.us-east-1.amazonaws.com</span>

          <span class="hljs-comment"># Tag and push the image</span>
          <span class="hljs-string">docker</span> <span class="hljs-string">tag</span> <span class="hljs-string">myapp:latest</span> <span class="hljs-string">&lt;aws_account_id&gt;.dkr.ecr.us-east-1.amazonaws.com/myapp:latest</span>
          <span class="hljs-string">docker</span> <span class="hljs-string">push</span> <span class="hljs-string">&lt;aws_account_id&gt;.dkr.ecr.us-east-1.amazonaws.com/myapp:latest</span>
</code></pre>
<h3 id="heading-3-implement-zero-downtime-deployments">3. Implement Zero-Downtime Deployments</h3>
<p>Zero downtime is crucial. Kubernetes makes this easy with rolling updates:</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># Kubernetes deployment configured for zero-downtime updates</span>
<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">crud-app</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">3</span>  <span class="hljs-comment"># Multiple replicas for high availability</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">app:</span> <span class="hljs-string">crud-app</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">app:</span> <span class="hljs-string">crud-app</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">app</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">&lt;docker_registry&gt;/crud-app:latest</span>
        <span class="hljs-attr">ports:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">80</span>  <span class="hljs-comment"># Expose container port</span>
</code></pre>
<p>By having multiple replicas, you ensure that some pods stay live during updates.</p>
<h3 id="heading-4-create-cross-cloud-deployment-for-redundancy">4. Create Cross-Cloud Deployment for Redundancy</h3>
<p>If you want better reliability, you can deploy across different clouds in parallel:</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># Deploy to multiple cloud providers for redundancy</span>
<span class="hljs-attr">name:</span> <span class="hljs-string">Cross-Cloud</span> <span class="hljs-string">Deployment</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-comment"># Deploy to AWS</span>
  <span class="hljs-attr">aws-deploy:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">AWS</span> <span class="hljs-string">Setup</span> <span class="hljs-string">&amp;</span> <span class="hljs-string">Deploy</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          # Configure AWS CLI with credentials
          aws configure set aws_access_key_id ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws configure set aws_secret_access_key ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          # AWS deployment commands...
</span>
  <span class="hljs-comment"># Deploy to Oracle Cloud in parallel</span>
  <span class="hljs-attr">oracle-deploy:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Oracle</span> <span class="hljs-string">Setup</span> <span class="hljs-string">&amp;</span> <span class="hljs-string">Deploy</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          # Configure Oracle Cloud CLI
          oci setup config
          # Oracle Cloud deployment commands...</span>
</code></pre>
<p>Now if one cloud goes down, the other is still up.</p>
<h3 id="heading-5-implement-automated-rollbacks-with-health-checks">5. Implement Automated Rollbacks with Health Checks</h3>
<p>Set up health checks so Kubernetes can automatically rollback if something goes wrong:</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># Deployment with health checks for automated rollbacks</span>
<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">crud-app</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">3</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">app:</span> <span class="hljs-string">crud-app</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">app:</span> <span class="hljs-string">crud-app</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">crud-app</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">&lt;docker_registry&gt;/crud-app:latest</span>
        <span class="hljs-attr">ports:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">80</span>
        <span class="hljs-comment"># Check if the container is alive</span>
        <span class="hljs-attr">livenessProbe:</span>
          <span class="hljs-attr">httpGet:</span>
            <span class="hljs-attr">path:</span> <span class="hljs-string">/healthz</span>  <span class="hljs-comment"># Health check endpoint</span>
            <span class="hljs-attr">port:</span> <span class="hljs-number">80</span>
          <span class="hljs-attr">initialDelaySeconds:</span> <span class="hljs-number">5</span>  <span class="hljs-comment"># Wait before first check</span>
          <span class="hljs-attr">periodSeconds:</span> <span class="hljs-number">10</span>       <span class="hljs-comment"># Check every 10 seconds</span>
        <span class="hljs-comment"># Check if the container is ready to receive traffic</span>
        <span class="hljs-attr">readinessProbe:</span>
          <span class="hljs-attr">httpGet:</span>
            <span class="hljs-attr">path:</span> <span class="hljs-string">/readiness</span>  <span class="hljs-comment"># Readiness check endpoint</span>
            <span class="hljs-attr">port:</span> <span class="hljs-number">80</span>
          <span class="hljs-attr">initialDelaySeconds:</span> <span class="hljs-number">5</span>  <span class="hljs-comment"># Wait before first check</span>
          <span class="hljs-attr">periodSeconds:</span> <span class="hljs-number">10</span>       <span class="hljs-comment"># Check every 10 seconds</span>
</code></pre>
<h2 id="heading-how-to-build-a-comprehensive-monitoring-system">How to Build a Comprehensive Monitoring System</h2>
<p>Even with a small deployment, monitoring is key to spotting issues early. So now, I’ll walk through setting up a comprehensive monitoring system for your application.</p>
<p>You'll learn how to integrate Grafana Cloud for visualizing your metrics, use Prometheus for collecting data, and configure custom alerts to monitor your app's performance. I’ll also cover tracking Service Level Objectives (SLOs) and setting up external monitoring with UptimeRobot to make sure that your endpoints are always available.</p>
<h3 id="heading-1-set-up-grafana-clouds-free-tier">1. Set Up Grafana Cloud's Free Tier</h3>
<p>Create a Grafana Cloud account and connect Prometheus as a data source. They offer generous free usage, which is perfect for small teams.</p>
<h3 id="heading-2-configure-prometheus-for-metrics-collection">2. Configure Prometheus for Metrics Collection</h3>
<p>Prometheus collects metrics from your app.</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># prometheus.yml - Basic Prometheus configuration</span>
<span class="hljs-attr">global:</span>
  <span class="hljs-attr">scrape_interval:</span> <span class="hljs-string">15s</span>  <span class="hljs-comment"># Collect metrics every 15 seconds</span>
<span class="hljs-attr">scrape_configs:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">job_name:</span> <span class="hljs-string">'crud-app'</span>  <span class="hljs-comment"># Job name for the crud-app metrics</span>
    <span class="hljs-attr">static_configs:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">targets:</span> [<span class="hljs-string">'localhost:8080'</span>]  <span class="hljs-comment"># Where to collect metrics from</span>
</code></pre>
<p>This scrapes your app every 15 seconds for metrics.</p>
<h3 id="heading-3-create-monitoring-dashboards">3. Create Monitoring Dashboards</h3>
<p>Grafana visualizes Prometheus data. You can create dashboards using queries like:</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># Calculate average CPU usage rate per instance over 1 minute</span>
<span class="hljs-string">avg(rate(cpu_usage_seconds_total[1m]))</span> <span class="hljs-string">by</span> <span class="hljs-string">(instance)</span>
</code></pre>
<p>This calculates average CPU usage over the last minute per instance.</p>
<h3 id="heading-4-write-custom-promql-queries-for-alerts">4. Write Custom PromQL Queries for Alerts</h3>
<p>You can create smart alerts to detect increasing error rates, like the below:</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># Calculate error rate as a percentage of total requests</span>
<span class="hljs-comment"># Alert when error rate exceeds 5%</span>
<span class="hljs-string">sum(rate(http_requests_total{status=~"5.."}[5m]))</span> <span class="hljs-string">by</span> <span class="hljs-string">(service)</span>
  <span class="hljs-string">/</span> 
<span class="hljs-string">sum(rate(http_requests_total[5m]))</span> <span class="hljs-string">by</span> <span class="hljs-string">(service)</span> <span class="hljs-string">&gt;</span> <span class="hljs-number">0.05</span>
</code></pre>
<p>This alerts if more than 5% of your traffic results in errors.</p>
<h3 id="heading-5-implement-slo-tracking-on-a-budget">5. Implement SLO Tracking on a Budget</h3>
<p>You can track Service Level Objectives (SLOs) with Prometheus for free:</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># Calculate percentage of requests completed under 200ms</span>
<span class="hljs-comment"># Alert when it drops below 99%</span>
<span class="hljs-string">rate(http_request_duration_seconds_bucket{le="0.2"}[5m])</span> 
  <span class="hljs-string">/</span> <span class="hljs-string">rate(http_request_duration_seconds_count[5m])</span> 
<span class="hljs-string">&gt;</span> <span class="hljs-number">0.99</span>
</code></pre>
<p>This tracks if 99% of requests complete in under 200ms.</p>
<h3 id="heading-6-set-up-uptimerobot-for-external-monitoring">6. Set Up UptimeRobot for External Monitoring</h3>
<p>Finally, you can use UptimeRobot to check if your endpoints are reachable externally, and get alerts if anything goes down.</p>
<h2 id="heading-how-to-implement-security-testing-and-scanning">How to Implement Security Testing and Scanning</h2>
<p>Security should be integrated into your development pipeline from the start, not added as an afterthought. In this section, I’ll show you how to implement security testing and scanning at various stages of your workflow.</p>
<p>You’ll use GitHub CodeQL for static code analysis, OWASP ZAP for scanning web vulnerabilities, and Trivy for container image scanning. You’ll also learn how to enforce security thresholds directly in your CI pipeline.</p>
<h3 id="heading-1-enable-github-code-scanning-with-codeql">1. Enable GitHub Code Scanning with CodeQL</h3>
<p>GitHub has built-in code scanning with CodeQL<strong>.</strong> Here’s how to set it up:</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># GitHub workflow for CodeQL security scanning</span>
<span class="hljs-attr">name:</span> <span class="hljs-string">CodeQL</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">pull_request:</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">analyze:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">Analyze</span> <span class="hljs-string">code</span> <span class="hljs-string">with</span> <span class="hljs-string">CodeQL</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</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@v3</span>

      <span class="hljs-comment"># Initialize the CodeQL scanning tools</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Set</span> <span class="hljs-string">up</span> <span class="hljs-string">CodeQL</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">github/codeql-action/init@v2</span>

      <span class="hljs-comment"># Run the analysis and generate results</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Analyze</span> <span class="hljs-string">code</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">github/codeql-action/analyze@v2</span>
</code></pre>
<p>This automatically checks your code for security vulnerabilities.</p>
<h3 id="heading-2-integrate-owasp-zap-into-your-ci-pipeline">2. Integrate OWASP ZAP into Your CI Pipeline</h3>
<p>You can also scan your deployed app with OWASP ZAP like this:</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># Automated security scanning with OWASP ZAP</span>
<span class="hljs-attr">name:</span> <span class="hljs-string">ZAP</span> <span class="hljs-string">Scan</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">zap-scan:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</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@v3</span>

      <span class="hljs-comment"># Run the ZAP security scan against deployed application</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">ZAP</span> <span class="hljs-string">security</span> <span class="hljs-string">scan</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">zaproxy/action-full-scan@v0.3.0</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">target:</span> <span class="hljs-string">'https://yourapp.com'</span>  <span class="hljs-comment"># URL to scan</span>
</code></pre>
<p>This checks for common web vulnerabilities.</p>
<h3 id="heading-3-set-up-trivy-for-container-vulnerability-scanning">3. Set Up Trivy for Container Vulnerability Scanning</h3>
<p>You can also check your container images for vulnerabilities with Trivy<strong>:</strong></p>
<pre><code class="lang-yaml"><span class="hljs-comment"># Scan Docker images for vulnerabilities using Trivy</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">Trivy</span> <span class="hljs-string">vulnerability</span> <span class="hljs-string">scanner</span>
  <span class="hljs-attr">uses:</span> <span class="hljs-string">aquasecurity/trivy-action@master</span>
  <span class="hljs-attr">with:</span>
    <span class="hljs-attr">image-ref:</span> <span class="hljs-string">'crud-app:latest'</span>   <span class="hljs-comment"># Image to scan</span>
    <span class="hljs-attr">format:</span> <span class="hljs-string">'table'</span>             <span class="hljs-comment"># Output format</span>
    <span class="hljs-attr">exit-code:</span> <span class="hljs-string">'1'</span>              <span class="hljs-comment"># Fail the build if vulnerabilities found</span>
    <span class="hljs-attr">ignore-unfixed:</span> <span class="hljs-literal">true</span>        <span class="hljs-comment"># Skip vulnerabilities without fixes</span>
    <span class="hljs-attr">severity:</span> <span class="hljs-string">'CRITICAL,HIGH'</span>   <span class="hljs-comment"># Only alert on critical and high severity</span>
</code></pre>
<p>Your builds will fail if serious issues are found, keeping you safe by default.</p>
<h3 id="heading-4-create-threshold-based-pipeline-failures">4. Create Threshold-Based Pipeline Failures</h3>
<p>You can configure your pipelines to fail automatically if vulnerabilities exceed a set threshold, enforcing strong security practices without manual effort. Here’s how that should look:</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># Fail the pipeline if critical or high vulnerabilities are found</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">Trivy</span> <span class="hljs-string">vulnerability</span> <span class="hljs-string">scanner</span>
  <span class="hljs-attr">uses:</span> <span class="hljs-string">aquasecurity/trivy-action@master</span>
  <span class="hljs-attr">with:</span>
    <span class="hljs-attr">image-ref:</span> <span class="hljs-string">'crud-app:latest'</span>   <span class="hljs-comment"># Image to scan</span>
    <span class="hljs-attr">format:</span> <span class="hljs-string">'json'</span>              <span class="hljs-comment"># Output as JSON for parsing</span>
    <span class="hljs-attr">exit-code:</span> <span class="hljs-string">'1'</span>              <span class="hljs-comment"># Fail the build if vulnerabilities found</span>
    <span class="hljs-attr">severity:</span> <span class="hljs-string">'CRITICAL,HIGH'</span>   <span class="hljs-comment"># Check for critical and high severity issues</span>
    <span class="hljs-attr">ignore-unfixed:</span> <span class="hljs-literal">true</span>        <span class="hljs-comment"># Skip vulnerabilities without fixes</span>
</code></pre>
<p>This forces a no-compromise security posture – that is, if critical or high vulnerabilities are detected, the build stops immediately.</p>
<h3 id="heading-5-implement-custom-security-checks">5. Implement Custom Security Checks</h3>
<p>Sometimes you need to go beyond automated scanners. Here's a basic example of a custom security check you can add to your pipeline:</p>
<pre><code class="lang-yaml"><span class="hljs-comment">#!/bin/bash</span>

<span class="hljs-comment"># Custom script to check for hard-coded secrets in source code</span>
<span class="hljs-comment"># Check for hard-coded API keys in source files</span>
<span class="hljs-string">if</span> <span class="hljs-string">grep</span> <span class="hljs-string">-r</span> <span class="hljs-string">"API_KEY"</span> <span class="hljs-string">./src;</span> <span class="hljs-string">then</span>
  <span class="hljs-string">echo</span> <span class="hljs-string">"Security issue: Found hard-coded API keys."</span>
  <span class="hljs-string">exit</span> <span class="hljs-number">1</span>  <span class="hljs-comment"># Fail the build</span>
<span class="hljs-string">else</span>
  <span class="hljs-string">echo</span> <span class="hljs-string">"No hard-coded API keys found."</span>
<span class="hljs-string">fi</span>
</code></pre>
<p>You can extend this script to scan for patterns like private keys, passwords, or other sensitive information, helping catch issues before they ever reach production.</p>
<h2 id="heading-performance-optimization-and-scaling">Performance Optimization and Scaling</h2>
<p>Optimizing early saves you pain later. Here’s how to make your pipelines faster, smarter, and more scalable:</p>
<h3 id="heading-1-measure-pipeline-execution-times">1. Measure Pipeline Execution Times</h3>
<p>Understanding how long each step takes is the first step to improving it:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">build:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-comment"># Record the start time</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Start</span> <span class="hljs-string">timer</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">echo</span> <span class="hljs-string">"Start time: $(date)"</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">install</span>

      <span class="hljs-comment"># Record the end time to calculate duration</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">End</span> <span class="hljs-string">timer</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">echo</span> <span class="hljs-string">"End time: $(date)"</span>
</code></pre>
<p>Later, you can automate time tracking for full reports and alerts.</p>
<h3 id="heading-2-implement-parallelization-strategies">2. Implement Parallelization Strategies</h3>
<p>Split your jobs smartly to save time:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">jobs:</span>
  <span class="hljs-comment"># First job to install dependencies</span>
  <span class="hljs-attr">install:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">ci</span>

  <span class="hljs-comment"># Run tests in parallel with linting</span>
  <span class="hljs-attr">test:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">needs:</span> <span class="hljs-string">install</span>  <span class="hljs-comment"># Depends on install job</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">test</span>

  <span class="hljs-comment"># Run linting in parallel with tests</span>
  <span class="hljs-attr">lint:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">needs:</span> <span class="hljs-string">install</span>  <span class="hljs-comment"># Also depends on install job</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">lint</span>
</code></pre>
<p>Result: Testing and linting run in parallel after installing dependencies, cutting pipeline time significantly.</p>
<h3 id="heading-3-set-up-distributed-caching">3. Set Up Distributed Caching</h3>
<p>Caching saves your workflow from repeating expensive tasks:</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># Cache dependencies to speed up builds</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Cache</span> <span class="hljs-string">node</span> <span class="hljs-string">modules</span>
  <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/cache@v3</span>
  <span class="hljs-attr">with:</span>
    <span class="hljs-attr">path:</span> <span class="hljs-string">|
      ~/.npm           # Cache global npm cache
      node_modules     # Cache local dependencies
</span>    <span class="hljs-attr">key:</span> <span class="hljs-string">${{</span> <span class="hljs-string">runner.os</span> <span class="hljs-string">}}-node-${{</span> <span class="hljs-string">hashFiles('**/package-lock.json')</span> <span class="hljs-string">}}</span>  <span class="hljs-comment"># Key based on OS and dependency hash</span>
    <span class="hljs-attr">restore-keys:</span> <span class="hljs-string">|</span>    <span class="hljs-comment"># Fallback keys if exact match isn't found</span>
      <span class="hljs-string">${{</span> <span class="hljs-string">runner.os</span> <span class="hljs-string">}}-node-</span>
</code></pre>
<p><strong>Tip:</strong> Also cache build artifacts, Docker layers, and Terraform plans when possible.</p>
<h3 id="heading-4-create-performance-benchmarks">4. Create Performance Benchmarks</h3>
<p>Track your build times over time with benchmarks:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">build:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-comment"># Store the start time as an environment variable</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Start</span> <span class="hljs-string">timer</span>
        <span class="hljs-attr">id:</span> <span class="hljs-string">start_time</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">echo</span> <span class="hljs-string">"start_time=$(date +%s)"</span> <span class="hljs-string">&gt;&gt;</span> <span class="hljs-string">$GITHUB_ENV</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">install</span>

      <span class="hljs-comment"># Calculate and display the elapsed time</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">End</span> <span class="hljs-string">timer</span> <span class="hljs-string">and</span> <span class="hljs-string">calculate</span> <span class="hljs-string">elapsed</span> <span class="hljs-string">time</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          end_time=$(date +%s)
          elapsed_time=$((end_time - ${{ env.start_time }}))
          echo "Build time: $elapsed_time seconds"</span>
</code></pre>
<p>With benchmarks in place, you can monitor regressions and trigger optimizations automatically.</p>
<h3 id="heading-5-how-to-plan-for-growth-beyond-free-tiers">5. How to Plan for Growth Beyond Free Tiers</h3>
<ul>
<li><p><strong>Understand cloud pricing structures:</strong> AWS, Azure, GCP all offer generous free tiers, but know the limits to avoid surprise bills. <em>(I have been there and it wasn’t pretty.)</em></p>
</li>
<li><p><strong>Consider scaling to more advanced CI/CD tools:</strong> Jenkins, CircleCI, GitLab can offer better performance or self-hosted control as you grow.</p>
</li>
<li><p><strong>Automate resource provisioning:</strong> Use Infrastructure as Code (IaC) with Terraform, Pulumi, or AWS CDK to dynamically scale your infrastructure when your team or traffic grows.</p>
</li>
</ul>
<h2 id="heading-complete-cicd-pipeline-example">Complete CI/CD Pipeline Example</h2>
<p>Here’s a full example tying everything together:</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># Complete end-to-end CI/CD pipeline</span>
<span class="hljs-attr">name:</span> <span class="hljs-string">CI/CD</span> <span class="hljs-string">Pipeline</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-comment"># Initial setup job</span>
  <span class="hljs-attr">setup:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</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@v3</span>

  <span class="hljs-comment"># Build and test job</span>
  <span class="hljs-attr">build:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">needs:</span> <span class="hljs-string">setup</span>  <span class="hljs-comment"># Depends on setup job</span>
    <span class="hljs-attr">steps:</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@v3</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">node-version:</span> <span class="hljs-string">'16'</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">Run</span> <span class="hljs-string">security</span> <span class="hljs-string">scan</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">npx</span> <span class="hljs-string">eslint</span> <span class="hljs-string">.</span>  <span class="hljs-comment"># Run ESLint for security rules</span>

  <span class="hljs-comment"># Deploy to Kubernetes job</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">needs:</span> <span class="hljs-string">build</span>  <span class="hljs-comment"># Depends on successful build</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">K3d</span> <span class="hljs-string">cluster</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">k3d</span> <span class="hljs-string">cluster</span> <span class="hljs-string">create</span> <span class="hljs-string">dev-cluster</span> <span class="hljs-string">--servers</span> <span class="hljs-number">1</span> <span class="hljs-string">--agents</span> <span class="hljs-number">2</span> <span class="hljs-string">--port</span> <span class="hljs-number">8080</span><span class="hljs-string">:80@loadbalancer</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Apply</span> <span class="hljs-string">Kubernetes</span> <span class="hljs-string">manifests</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">kubectl</span> <span class="hljs-string">apply</span> <span class="hljs-string">-f</span> <span class="hljs-string">k8s/</span>  <span class="hljs-comment"># Apply all K8s manifests in the k8s directory</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">app</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">kubectl</span> <span class="hljs-string">rollout</span> <span class="hljs-string">restart</span> <span class="hljs-string">deployment/webapp</span>  <span class="hljs-comment"># Restart deployment for zero-downtime update</span>

  <span class="hljs-comment"># Infrastructure provisioning job</span>
  <span class="hljs-attr">terraform:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">needs:</span> <span class="hljs-string">deploy</span>  <span class="hljs-comment"># Run after deployment</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">Terraform</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">hashicorp/setup-terraform@v2</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Terraform</span> <span class="hljs-string">Init</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">terraform</span> <span class="hljs-string">init</span>  <span class="hljs-comment"># Initialize Terraform</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Terraform</span> <span class="hljs-string">Apply</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">terraform</span> <span class="hljs-string">apply</span> <span class="hljs-string">-auto-approve</span>  <span class="hljs-comment"># Apply infrastructure changes automatically</span>
</code></pre>
<h4 id="heading-runbook-failed-deployment"><strong>Runbook: Failed Deployment:</strong></h4>
<p><strong>Issue</strong>: Pods fail due to resource limits (for example, OOMKilled, CrashLoopBackOff).<br><strong>Fix</strong>:</p>
<pre><code class="lang-yaml">  <span class="hljs-string">kubectl</span> <span class="hljs-string">top</span> <span class="hljs-string">pod</span>
  <span class="hljs-string">kubectl</span> <span class="hljs-string">edit</span> <span class="hljs-string">deployment</span> <span class="hljs-string">crud-app</span>
  <span class="hljs-string">kubectl</span> <span class="hljs-string">apply</span> <span class="hljs-string">-f</span> <span class="hljs-string">deployment.yaml</span>
  <span class="hljs-string">kubectl</span> <span class="hljs-string">rollout</span> <span class="hljs-string">status</span> <span class="hljs-string">deployment/crud-app</span>
</code></pre>
<p><strong>Tip:</strong> Set realistic resource requests and limits early, it'll save you debugging time later.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>By following along with this tutorial, you now know how to build a production-ready DevOps pipeline using free tools:</p>
<ul>
<li><p><strong>CI/CD</strong>: GitHub Actions for testing, linting, and building.</p>
</li>
<li><p><strong>Infrastructure</strong>: Terraform for AWS/Render and PostgreSQL setup.</p>
</li>
<li><p><strong>Orchestration</strong>: K3d for local Kubernetes.</p>
</li>
<li><p><strong>Monitoring</strong>: Grafana, Prometheus, UptimeRobot.</p>
</li>
<li><p><strong>Security</strong>: CodeQL, OWASP ZAP, Trivy for vulnerability scanning.</p>
</li>
</ul>
<p>This pipeline is scalable and secure, and it’s perfect for small projects. As your app grows, you might want to consider paid plans for more resources (for example, AWS larger instances, Grafana unlimited metrics). You can check <a target="_blank" href="https://aws.amazon.com/free/">AWS Free Tier</a>, <a target="_blank" href="https://developer.hashicorp.com/terraform/docs">Terraform Docs</a>, and <a target="_blank" href="https://grafana.com/docs/">Grafana Docs</a> for more learning.</p>
<p><strong>PS:</strong> I’d love to see what you build. Share your pipeline on <a target="_blank" href="https://forum.freecodecamp.org/">FreeCodeCamp’s forum</a> or tag me on X <a target="_blank" href="https://x.com/Emidowojo">@Emidowojo</a> with #DevOpsOnABudget, and tell me about the challenges you faced. You can also connect with me on <a target="_blank" href="https://www.linkedin.com/in/emidowojo/">LinkedIn</a> if you’d like to stay in touch. If you made it to the end of this lengthy article, thanks for reading!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Automate Alert Provisioning with the SigNoz Terraform Provider ]]>
                </title>
                <description>
                    <![CDATA[ Modern infrastructure requires continuous monitoring and rapid incident response. However, manually configuring and managing alerts is not only labor-intensive but also susceptible to human error. Automating alert provisioning allows you to enforce c... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/automate-alert-provisioning-with-the-signoz-terraform-provider/</link>
                <guid isPermaLink="false">67d87353b13a6fd9fb559ada</guid>
                
                    <category>
                        <![CDATA[ observability ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Terraform ]]>
                    </category>
                
                    <category>
                        <![CDATA[ automation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ signoz ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Gursimar Singh ]]>
                </dc:creator>
                <pubDate>Mon, 17 Mar 2025 19:09:07 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742237716002/3e7d07f8-39f7-45ba-aac3-d421f61a8785.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Modern infrastructure requires continuous monitoring and rapid incident response. However, manually configuring and managing alerts is not only labor-intensive but also susceptible to human error.</p>
<p>Automating alert provisioning allows you to enforce consistency, secure sensitive credentials, and integrate monitoring into your deployment pipelines.</p>
<p>This guide dives deep into how you can use the SigNoz Terraform Provider to define and manage alert configurations as code, making your observability setup resilient and adaptable.</p>
<h3 id="heading-heres-what-well-cover">Here’s what we’ll cover:</h3>
<ol>
<li><p><a class="post-section-overview" href="#heading-why-automate-alert-provisioning">Why Automate Alert Provisioning?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-are-signoz-and-terraform">What are SigNoz and Terraform?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-overview-of-the-setup">Overview of the Setup</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-steps-to-setup-the-project">Steps to Set Up the Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-best-practices-and-security-considerations">Best Practices and Security Considerations</a></p>
</li>
<li><p><a class="post-section-overview" href="#integrating-with-cicd-pipelines">Integrating with CI/CD Pipelines</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-advanced-customizations-and-troubleshooting">Advanced Customizations and Troubleshooting</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-why-automate-alert-provisioning">Why Automate Alert Provisioning?</h2>
<p>It’s a good idea to automate your alert provisioning for various reasons.</p>
<p>First of all, configuring things manually often leads to discrepancies between environments (development, staging, production). Automating alerts ensures that all environments adhere to the same monitoring standards, reducing the likelihood of configuration drift and improving consistency and uniformity.</p>
<p>Also, when alerts are defined as code, every change is tracked in your version control system. This audit trail makes it easier to trace and review changes, collaborate with team members, and roll back configurations if issues arise.</p>
<p>Something else to consider is that as your infrastructure grows, manually managing alerts becomes unsustainable. Automation allows you to quickly and efficiently update your alerting rules across multiple services without the need for repetitive manual intervention.</p>
<p>Automation also helps improve security. Storing sensitive information like API tokens as environment variables or in secret management systems helps maintain security. Automating the process also minimizes human exposure to critical credentials.</p>
<p>And finally, defining alerts as code enables you to integrate monitoring configurations into your CI/CD pipelines. This leads to continuous testing, validation, and deployment of alert rules alongside application updates.</p>
<p>So as you can see, there are many compelling reasons to go the automation route. Now let’s see how you can do this in practice.</p>
<h2 id="heading-what-are-signoz-and-terraform">What Are SigNoz and Terraform?</h2>
<p>SigNoz is an open-source observability platform designed to collect, analyze, and visualize metrics, logs, and traces from your applications. Its most helpful features include:</p>
<ul>
<li><p>It has comprehensive monitoring abilities: Provides detailed insights into system performance, error rates, and user behaviors.</p>
</li>
<li><p>It comes equipped with real-time analytics: Enables proactive issue detection and performance optimization.</p>
</li>
<li><p>It’s community-driven: As an open-source solution, it benefits from community contributions, transparency, and customization.</p>
</li>
<li><p>It’s cost-effective: Offers powerful observability capabilities without the hefty licensing fees of proprietary solutions.</p>
</li>
</ul>
<p>Terraform is an Infrastructure as Code (IaC) tool developed by HashiCorp. It allows you to define and provision infrastructure using declarative configuration files. Terraform’s core advantages include:</p>
<ul>
<li><p>Its declarative syntax: You specify the desired state of your infrastructure, and Terraform handles the implementation.</p>
</li>
<li><p>Its version Control: Configuration files can be managed in Git repositories, enabling traceability and rollback of changes.</p>
</li>
<li><p>Powerful automation: Facilitates automated provisioning and updates, reducing manual effort and errors.</p>
</li>
<li><p>Multi-cloud support: Manages resources across different cloud providers with a consistent workflow.</p>
</li>
</ul>
<p>So you might be wondering: why should you use Terraform with SigNoz?</p>
<p>First of all, Terraform ensures that your infrastructure is managed consistently across different environments, reducing the risk of configuration drift. It also simplifies managing multiple alerts and resources, making it easier to scale your observability setup.</p>
<p>Beyond this, automating the provisioning process reduces manual setup efforts and minimizes the potential for human error.</p>
<p>And finally, Terraform configurations can be version-controlled, allowing teams to track changes over time and collaborate more effectively.</p>
<h2 id="heading-overview-of-the-setup">Overview of the Setup</h2>
<p>This setup utilizes the SigNoz Terraform Provider to manage alerts and notification channels within SigNoz Cloud. The configuration includes:</p>
<ul>
<li><p><strong>Provider configuration:</strong> Establishes the connection to SigNoz using the API endpoint and a securely provided API token.</p>
</li>
<li><p><strong>Notification channels:</strong> Defines where alerts are sent (for example, via email) to ensure the right teams are notified.</p>
</li>
<li><p><strong>Alert rules:</strong> Specifies the conditions under which alerts are triggered, including thresholds and evaluation windows.</p>
</li>
<li><p><strong>External variables:</strong> Enhances flexibility by allowing critical values (like CPU thresholds and email addresses) to be managed externally.</p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before diving into the setup, make sure you have the following:</p>
<ol>
<li><p><strong>SigNoz Cloud account</strong>: If you don't have one, sign up for SigNoz Cloud to host your observability data and configure alerts.</p>
</li>
<li><p><strong>Terraform installed</strong>: Install Terraform on your machine. Terraform is the tool you'll use to manage your infrastructure as code.</p>
</li>
<li><p><strong>SigNoz API token</strong>:</p>
<ul>
<li><p>Log in to your SigNoz Cloud dashboard.</p>
</li>
<li><p>Navigate to Settings &gt; API Tokens.</p>
</li>
<li><p>Click Generate API Token.</p>
</li>
<li><p>Copy the token, as you'll need it to authenticate Terraform with SigNoz.</p>
</li>
</ul>
</li>
<li><p><strong>Basic knowledge of Terraform</strong>: Familiarity with Terraform's syntax and concepts, including writing configuration files and running Terraform commands, is essential.</p>
</li>
<li><p><strong>Text editor</strong>: Use any code editor like Visual Studio Code or Sublime Text to write your Terraform configuration files.</p>
</li>
</ol>
<h2 id="heading-steps-to-set-up-the-project">Steps to Set Up the Project</h2>
<h3 id="heading-1-understand-the-signozalert-resource">1. Understand the <code>signoz_alert</code> Resource</h3>
<p>The <code>signoz_alert</code> resource allows you to create and manage alert rules in SigNoz via Terraform. It supports various alert types, conditions, and configurations. Understanding this resource is crucial as it forms the basis of your alert configuration.</p>
<h3 id="heading-2-set-up-your-terraform-configuration">2. Set Up Your Terraform Configuration</h3>
<p>Create a new directory for your Terraform configuration:</p>
<pre><code class="lang-bash">mkdir signoz-terraform
<span class="hljs-built_in">cd</span> signoz-terraform
</code></pre>
<p>Create a <a target="_blank" href="http://main.tf"><code>main.tf</code></a> file with the following content:</p>
<pre><code class="lang-json">terraform {
  required_providers {
    signoz = {
      source  = <span class="hljs-attr">"SigNoz/signoz"</span>
      version = <span class="hljs-attr">"0.1.3"</span> # Use the latest version from the Terraform Registry
    }
  }
}

provider <span class="hljs-string">"signoz"</span> {
  endpoint  = <span class="hljs-attr">"https://api.us.signoz.cloud"</span> # Replace with your SigNoz Cloud API endpoint
  api_token = var.signoz_api_token
}

variable <span class="hljs-string">"signoz_api_token"</span> {}
</code></pre>
<p>The <code>provider</code> block configures the SigNoz provider, where <code>endpoint</code> specifies the API endpoint and <code>api_token</code> is passed through a variable for security.</p>
<h3 id="heading-3-define-a-notification-channel-optional">3. Define a Notification Channel (Optional)</h3>
<p>If you plan to send alerts to specific channels, define them using <code>signoz_notification_channel</code>. For example, create a <a target="_blank" href="http://channels.tf"><code>channels.tf</code></a> file:</p>
<pre><code class="lang-json">resource <span class="hljs-string">"signoz_notification_channel"</span> <span class="hljs-string">"email_channel"</span> {
  name = <span class="hljs-attr">"Email Channel"</span>
  type = <span class="hljs-attr">"email"</span>

  receivers {
    email_config {
      to = [<span class="hljs-attr">"alerts@example.com"</span>]
    }
  }
}
</code></pre>
<p>Defining a notification channel ensures that alerts are sent to the correct recipients, enhancing the utility of your alerting system.</p>
<h3 id="heading-4-create-an-alert-using-the-signozalert-resource">4. Create an Alert Using the <code>signoz_alert</code> Resource</h3>
<p>Create an <a target="_blank" href="http://alerts.tf"><code>alerts.tf</code></a> file to define your alert:</p>
<pre><code class="lang-json">resource <span class="hljs-string">"signoz_alert"</span> <span class="hljs-string">"cpu_high_usage"</span> {
  alert            = <span class="hljs-attr">"High CPU Usage Alert"</span>
  alert_type       = <span class="hljs-attr">"METRIC_BASED_ALERT"</span>
  severity         = <span class="hljs-attr">"critical"</span>
  description      = <span class="hljs-attr">"Alert when CPU usage exceeds 80% over 5 minutes"</span>
  rule_type        = <span class="hljs-attr">"threshold_rule"</span>
  broadcast_to_all = false
  disabled         = false
  eval_window      = <span class="hljs-attr">"5m0s"</span>
  frequency        = <span class="hljs-attr">"1m0s"</span>
  version          = <span class="hljs-attr">"v4"</span>

  condition = jsonencode({
    compositeQuery = {
      builderQueries = {
        A = {
          aggregateOperator = <span class="hljs-attr">"avg"</span>
          dataSource        = <span class="hljs-attr">"metrics"</span>
          metricName        = <span class="hljs-attr">"cpu_usage_user"</span>
          reduceTo          = <span class="hljs-attr">"avg"</span>
          filters           = {
            items = []
            op    = <span class="hljs-attr">"AND"</span>
          }
          groupBy = []
        }
      }
      queryType = <span class="hljs-string">"builder"</span>
      panelType = <span class="hljs-string">"graph"</span>
      unit      = <span class="hljs-string">"%"</span>
    }
    op                = <span class="hljs-string">"&gt;"</span>
    target            = <span class="hljs-number">80</span>
    matchType         = <span class="hljs-string">"EQUALS"</span>
    selectedQueryName = <span class="hljs-string">"A"</span>
    targetUnit        = <span class="hljs-string">"%"</span>
  })

  preferred_channels = [signoz_notification_channel.email_channel.name]

  labels = {
    severity = <span class="hljs-attr">"critical"</span>
    team     = <span class="hljs-attr">"DevOps"</span>
  }
}
</code></pre>
<p>This configuration creates a high CPU usage alert with specific conditions and notifications. The <code>condition</code> parameter is crucial as it defines the alert triggering logic.</p>
<h3 id="heading-5-provide-the-api-token">5. Provide the API Token</h3>
<p>Set the <code>signoz_api_token</code> as an environment variable:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">export</span> TF_VAR_signoz_api_token=<span class="hljs-string">"YOUR_SIGNOZ_API_TOKEN"</span>
</code></pre>
<p>This ensures that your API token is securely used by Terraform without hardcoding it in your configuration files.</p>
<h3 id="heading-6-initialize-terraform">6. Initialize Terraform</h3>
<p>Run:</p>
<pre><code class="lang-bash">terraform init
</code></pre>
<p>This command initializes your Terraform working directory, downloading necessary plugins, and preparing the environment.</p>
<h3 id="heading-7-review-the-execution-plan">7. Review the Execution Plan</h3>
<p>Generate the execution plan:</p>
<pre><code class="lang-bash">terraform plan
</code></pre>
<p>This step previews the changes Terraform will make, allowing you to verify the configuration before applying it.</p>
<h3 id="heading-8-apply-the-configuration">8. Apply the Configuration</h3>
<p>Apply the changes:</p>
<pre><code class="lang-bash">terraform apply
</code></pre>
<p>Type <code>yes</code> when prompted. This command applies the configuration, creating or updating resources as specified.</p>
<h3 id="heading-9-verify-the-alert-in-signoz-cloud">9. Verify the Alert in SigNoz Cloud</h3>
<p>To do this, follow these steps:</p>
<ul>
<li><p>Log in to your SigNoz Cloud dashboard.</p>
</li>
<li><p>Navigate to Alerts.</p>
</li>
<li><p>Confirm that the "High CPU Usage Alert" is listed.</p>
</li>
<li><p>Click on the alert to view its details and ensure it matches your configuration.</p>
</li>
</ul>
<h3 id="heading-10-modify-the-alert-optional">10. Modify the Alert (Optional)</h3>
<p>To change the CPU usage threshold to 75%, follow these steps:</p>
<ul>
<li><p>Update the target in <a target="_blank" href="http://alerts.tf"><code>alerts.tf</code></a>:</p>
<pre><code class="lang-json">  target = <span class="hljs-number">75</span>
</code></pre>
</li>
<li><p>Apply the changes:</p>
<pre><code class="lang-bash">  terraform apply
</code></pre>
</li>
</ul>
<h3 id="heading-11-destroy-the-resources-optional">11. Destroy the Resources (Optional)</h3>
<p>To remove the alert and notification channel:</p>
<pre><code class="lang-bash">terraform destroy
</code></pre>
<p>Type <code>yes</code> to confirm. This command will delete the resources created by Terraform.</p>
<h2 id="heading-best-practices-and-security-considerations">Best Practices and Security Considerations</h2>
<p>In modern infrastructure automation, robust best practices and security measures are paramount.</p>
<h3 id="heading-use-version-pinning">Use version pinning</h3>
<p>To ensure your alert provisioning remains reliable and maintainable, start with strict version control. Avoid using the latest tag and instead specify an exact version number. This ensures your infrastructure configuration remains consistent and predictable.</p>
<p>By pinning your provider version (for example, use version = "0.1.3" instead of version = "&gt;= 0.1.3".), you eliminate unexpected behavior that can arise from upstream changes. This practice is critical for long-term stability, especially when your infrastructure scales across multiple environments.</p>
<h3 id="heading-externalize-credentials"><strong>Externalize Credentials</strong></h3>
<p>Security is non-negotiable. Instead of embedding sensitive details like API tokens in your codebase, leverage environment variables or dedicated secret management tools such as HashiCorp Vault or AWS Secrets Manager.</p>
<p>For instance, storing your SigNoz API token as an environment variable (TF_VAR_signoz_api_token) not only mitigates the risk of credential exposure but also simplifies the process of credential rotation. Also, enforce access control policies around your configuration repositories and CI/CD pipelines to further secure these secrets.</p>
<h3 id="heading-use-version-control"><strong>Use Version Control</strong></h3>
<p>A mature setup also demands rigorous infrastructure version control. Hosting your Terraform configuration in a Git repository with branch protection and code review policies allows you to track changes meticulously, roll back problematic updates, and maintain an audit trail. This traceability is essential when troubleshooting issues or validating compliance during audits.</p>
<p>You should also document your configuration decisions extensively—explain why a particular CPU threshold was chosen or why specific labels (like severity and team) are used. Such documentation becomes invaluable for onboarding new team members or when revisiting configurations months later.</p>
<h2 id="heading-integrating-with-cicd-pipelines">Integrating with CI/CD Pipelines</h2>
<p>Integrating Terraform with your CI/CD pipeline is a cornerstone of a modern, automated deployment strategy. A well-architected pipeline not only validates your infrastructure changes but also ensures that your alerting rules remain in sync with your evolving application environment.</p>
<p>Continuous Integration (CI) involves automatically merging code changes into a shared repository and running automated tests on each commit. In practice, embedding Terraform plan into your pull request workflow provides early feedback, catching misconfigurations before they reach production. For instance, a GitHub Actions workflow can automatically check your changes:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Terraform</span> <span class="hljs-string">CI/CD</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span> [<span class="hljs-string">main</span>]
  <span class="hljs-attr">pull_request:</span>
    <span class="hljs-attr">branches:</span> [<span class="hljs-string">main</span>]

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">terraform:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</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@v3</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">Terraform</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">hashicorp/setup-terraform@v2</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Terraform</span> <span class="hljs-string">Init</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">terraform</span> <span class="hljs-string">init</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Terraform</span> <span class="hljs-string">Plan</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">terraform</span> <span class="hljs-string">plan</span> <span class="hljs-string">-no-color</span>
        <span class="hljs-attr">env:</span>
          <span class="hljs-attr">TF_VAR_signoz_api_token:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.SIGNOZ_API_TOKEN</span> <span class="hljs-string">}}</span>
</code></pre>
<p>This workflow uses GitHub secrets to securely manage your API tokens while validating the configuration changes. Continuous Delivery (CD) takes this further by automating deployments. Once your plan is approved, an automated Terraform apply step (often scheduled during off-peak hours or coordinated with application deployments) ensures smooth, coordinated rollouts.</p>
<p>Advanced pipelines can also include automated rollback mechanisms. For example, if a deployment triggers an anomaly, scripts can automatically revert to a previous version using your version control history—minimizing downtime and reinforcing the feedback loop between application performance and infrastructure configuration.</p>
<h2 id="heading-advanced-customizations-and-troubleshooting">Advanced Customizations and Troubleshooting</h2>
<p>As your observability requirements evolve, you may need to implement advanced customizations. One powerful approach is using multi-metric composite alerts. Instead of triggering an alert on a single threshold, you can design rules that combine multiple conditions—for example, firing only when both CPU usage and memory consumption exceed critical levels. This nuanced alerting minimizes false positives and ensures alerts are issued only during genuine performance issues.</p>
<p>Terraform’s modular design is especially useful here. By creating reusable modules that encapsulate your alert configurations, you can parameterize key variables—such as thresholds, evaluation windows, and notification channels—across a microservices architecture. This modularity enforces consistency while simplifying management and scaling.</p>
<p>Troubleshooting advanced configurations starts with reviewing your <code>terraform plan</code> output to ensure every change aligns with expectations. If an alert isn’t triggering as expected, inspect the JSON structure generated by the <code>jsonencode</code> function. Even minor syntax errors can cause significant issues.</p>
<p>When integrating with incident management tools like PagerDuty or Opsgenie, run comprehensive end-to-end tests in a staging environment. For example, deploy a test alert to a dedicated channel to verify that the complete alerting pipeline—from condition detection to incident escalation—is functioning correctly.</p>
<p>In one real-world scenario, a misconfigured composite query in an alert’s JSON payload led to intermittent failures. By enabling detailed provider logs and iteratively validating the JSON output, the issue was rapidly isolated and resolved. Such experiences underscore the importance of rigorous logging, validation, and testing in production-grade setups.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Automating alert provisioning is a transformative approach to managing observability in modern infrastructures.</p>
<p>By defining alerts and notification channels as code, you make your systems more consistent, scalable, secure, and easily integratabtle with CI/CD. You can set up uniform alert rules across all environments, quickly update and deploy monitoring configs, easily handle secure credentials, and automate CI/CD workflows that stay in sync with application changes. They also become easier to integrate with CI/CD workflows.</p>
<p>I hope you’ve enjoyed this tutorial and have learned something new. I’m always open to suggestions and discussions on <a target="_blank" href="https://www.linkedin.com/in/gursimarsm">LinkedIn</a>. Hit me up with direct messages.</p>
<p>If you’ve enjoyed my writing and want to keep me motivated, consider leaving starts on <a target="_blank" href="https://github.com/gursimarsm">GitHub</a> and endorsing me for relevant skills on <a target="_blank" href="https://www.linkedin.com/in/gursimarsm">LinkedIn</a>.</p>
<p>Till the next one, happy coding!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ A Beginner's Guide to Terraform – Infrastructure-as-Code in Practice ]]>
                </title>
                <description>
                    <![CDATA[ Over the years, cloud development has seen a major paradigm shift. Newer and more complex applications are deployed rapidly to the cloud to minimize downtime. And through all of this, the concept of Infrastructure-as-Code and various tools have emerg... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/a-beginners-guide-to-terraform-infrastructure-as-code-in-practice/</link>
                <guid isPermaLink="false">67782a8fd3a89d514105e418</guid>
                
                    <category>
                        <![CDATA[ Terraform ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Infrastructure as code ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Beginner Developers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Terraweekchallenge ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Oluwatobi ]]>
                </dc:creator>
                <pubDate>Fri, 03 Jan 2025 18:21:03 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1735900327439/09832fb8-8cc0-4182-b70a-5f54ee6fce7d.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Over the years, cloud development has seen a major paradigm shift. Newer and more complex applications are deployed rapidly to the cloud to minimize downtime. And through all of this, the concept of Infrastructure-as-Code and various tools have emerged to simplify the process of application development.</p>
<p>You might be wondering: what is Infrastructure-as-Code? How does it improve the development process and experience, and where does Terraform come into the picture? Well, we’ll explore all this and more in this guide. But before we start, here are some pre-requisites:</p>
<ul>
<li><p>Basic knowledge of the cloud and cloud terminologies</p>
</li>
<li><p>Access to a PC to implement code examples</p>
</li>
<li><p>A GCP account</p>
</li>
</ul>
<p>With this, let's get started.</p>
<h3 id="heading-heres-what-well-cover">Here’s what we’ll cover:</h3>
<ol>
<li><p><a class="post-section-overview" href="#heading-overview-of-infrastructure-as-code-iac">Overview of Infrastructure as Code</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-terraform">What is Terraform?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-benefits-of-terraform">Benefits of Terraform</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-common-terms-used-in-terraform">Common Terms Used in Terraform</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-demo-project-how-to-write-a-terraform-configuration">Demo Project: How to Write a Terraform Configuration</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-overview-of-infrastructure-as-code-iac">Overview of Infrastructure as Code (IaC)</h2>
<p>Infrastructure as code refers to generating cloud infrastructure tools and applications with a code-based configuration document. This process, when running, automates the sequence and process of creating databases, virtual machines, and servers. This improves the user experience by reducing the frequency of manual cloud service deployments, especially for multiple identical services.</p>
<p>There are two distinct approaches to infrastructure as code: the <code>Imperative</code> approach and the <code>Declarative</code> approach.</p>
<p>When you’re using the Declarative approach to infrastructure generation, you simply detail your expected/desired outputs for the Infrastructure to be generated, and then the IaC tool you’re using figures out how to produce that output.</p>
<p>On the other hand, the Imperative approach involves specifying the exact steps needed to achieve the desired infrastructure state. While the Imperative approach seems more suited for complex infrastructure setups, the Declarative approach can work just as well. </p>
<p>Some tools are capable of both approaches while others are only suited to one or the other. Examples of some of the popular IaC tools used globally include <a target="_blank" href="https://www.terraform.io/">Terraform IaC</a>, <a target="_blank" href="https://aws.amazon.com/cloudformation/">AWS Cloud Formation</a>, <a target="_blank" href="https://www.redhat.com/en/ansible-collaborative">Ansible</a>, and <a target="_blank" href="https://www.pulumi.com/">Pulumi</a>, <a target="_blank" href="https://www.chef.io/glossary/what-is-infrastructure-as-code">Chef</a>, among others.</p>
<p>Like the name implies – infrastructure as <strong>code</strong> – the code creating the infrastructure is written in various template languages within the IaC space. Popular template languages include JSON, YAML, ARM template, HCL, Heat Scripts, and so on.</p>
<p>You can also use scripting tools to execute cloud infrastructure. Some popular ones include Bash and PowerShell. These sometimes come preinstalled on most personal computers.</p>
<p>Out of all these tools, though, Terraform is distinct for various reasons – and it’s the one we’ll be examining in this article.</p>
<h2 id="heading-what-is-terraform">What is Terraform?</h2>
<p>Terraform is an open source tool developed by HashiCorp in 2014. It has evolved over the years and now serves as a cloud agnostic infrastructure tool that allows you to create infrastructure across multiple cloud service providers.</p>
<p>Terraform also offers <a target="_blank" href="https://app.terraform.io/session">Terraform Cloud</a>, a cloud-based software as a service tool. It allows for cloud-based deployment of cloud tools, instead of using the old local-based methods we had in the defunct Terraform CLI tool.</p>
<p>Also, like other IaC tools which utilize template languages, the template framework used to create infrastructure in Terraform is the HashiCorp template language (HCL).</p>
<h2 id="heading-benefits-of-terraform">Benefits of Terraform</h2>
<p>Now I’ll highlight some of the benefits of using Terraform as a cloud engineer, along with the tool’s key role in the cloud ecosystem.</p>
<h3 id="heading-1-declarative-approach"><strong>1. Declarative Approach</strong></h3>
<p>This approach to cloud infrastructure automation ensures that all required infrastructure to be deployed (databases, servers, and so on) is stated explicitly and executed accordingly. This helps avoid conflicts.</p>
<h3 id="heading-2-conflict-handling"><strong>2. Conflict Handling</strong></h3>
<p>In addition to its efficient cloud tool automation capabilities, Terraform has some robust conflict detection and handling properties. One of the ways it handles conflicts is via the <code>Terraform plan</code> function. This function highlights any perceived or potential conflicts of infrastructure orchestration which allows for easy correction before deployment. I’ll discuss this further in subsequent sections.</p>
<h3 id="heading-3-cloud-agnostic"><strong>3. Cloud Agnostic</strong></h3>
<p>Terraform is a multipurpose, multi-cloud automation service provider with efficient infrastructure automation capabilities across the major cloud service providers (AWS, GCP and Azure). It also allows for hybrid and inter-provider automation.</p>
<h3 id="heading-4-user-friendly"><strong>4. User-friendly</strong></h3>
<p>Terraform is one of the largest cloud automation tools with the largest user communities out there. It has extensive beginner-friendly tutorials that help you get a quick hang of the tool. Here is a link to its <a target="_blank" href="https://developer.hashicorp.com/terraform/docs">documentation</a> so you can dive in deeper.</p>
<h3 id="heading-5-file-management-capabilities"><strong>5. File Management Capabilities</strong></h3>
<p>Terraform automatically creates a local backup of the automation states on your local computer to ensure immediate recall and file handling in case anything goes wrong. It also offers remote backup options to remote cloud service providers where necessary.</p>
<h3 id="heading-6-version-control"><strong>6. Version Control</strong></h3>
<p>Just like the Git version control system, Terraform has a built-in version control system which lets you track changes to a Terraform file. It also lets you go back to previous versions of your code if there are errors in the present version, for example.</p>
<h3 id="heading-7-code-reusability"><strong>7. Code Reusability</strong></h3>
<p>Terraform offers a wide variety of code templates for easy reuse on its developer documentation page.</p>
<p>Now that we’ve highlighted the benefits of Terraform, let’s learn some common terminologies used in Terraform and what they mean.</p>
<h2 id="heading-common-terms-used-in-terraform">Common Terms Used in Terraform</h2>
<p>Before you start using Terraform, you should be familiar with some key terms that come up a lot. Here’s what you need to know:</p>
<ol>
<li><p><strong>Providers</strong>: in Terraform, a Provider is a programming interface that lets Terraform interact with various APIs and cloud services. For example, you’d use a provider to interface with a cloud service provider like GCP or Azure.</p>
</li>
<li><p><strong>Modules:</strong> Modules are specifically created within the Terraform framework and serve as reusable components that let you easily orchestrate cloud services. You can also store key information regarding cloud services in a module, and then modify it to ensure uniqueness using module variables.</p>
</li>
<li><p><strong>Resources:</strong> Resources in Terraform refer to the cloud infrastructure components to be created. Examples include cloud networks, virtual machines, availability zones, and other infrastructures.</p>
</li>
<li><p><strong>State:</strong> The concept of state in Terraform forms the basis for its efficiency. State keeps track of the current configuration of your infrastructure resources, and contains details about every resource Terraform has created, modified, or deleted. Terraforms version control system uses it to track any changes you make to a code file and uses that information to destroy and provision infrastructure as necessary.</p>
</li>
<li><p><strong>Workspace:</strong> a Workspace functions sort of similarly to a version control system, as it creates a sort of constraint around a work file. Workspaces let you manage multiple instances of a single infrastructure configuration in a clean and isolated way within the same backend. You can use workspaces to separate environments like development, staging, and production while using the same Terraform configuration.</p>
</li>
</ol>
<h2 id="heading-demo-project-how-to-write-a-terraform-configuration">Demo Project: How to Write a Terraform Configuration</h2>
<p>In this section, we’ll be diving deeper into writing our first Terraform file to orchestrate a Google Cloud program virtual machine with just a few lines of code. But before we begin, we’ll discuss the various commands that you should understand before we implement the demo project.</p>
<h3 id="heading-common-terraform-commands">Common Terraform Commands</h3>
<ul>
<li><p><code>Terraform init</code><strong>:</strong> This command initializes the Terraform tool and downloads essential cloud provider-specific files. It also establishes a connection between Terraform and the cloud provider in question. In our case, it’s between GCP and the Terraform provider.</p>
</li>
<li><p><code>Terraform fmt</code><strong>:</strong> This command automatically ensures optimal code formatting and indentation. It ensures orderly execution of the code and minimizes any errors.</p>
</li>
<li><p><code>Terraform plan</code><strong>:</strong> This command outlines the steps of execution of the Terraform code, and detects any errors that may occur during the process of execution. It also highlights any errors in the Terraform code that may hinder execution. Lastly, it works alongside Terraform state management to detect any change of state and de-provision or generate any additional cloud services if necessary.</p>
</li>
<li><p><code>Terraform apply</code><strong>:</strong> This command executes the planned Terraform state implemented by the <code>Terraform plan</code> command.</p>
</li>
<li><p><code>Terraform destroy</code><strong>:</strong> This command is the final command in the Terraform scheme which is used to deactivate or destroy all the cloud services created using the Terraform apply command. It's important to note that you should execute the commands listed above sequentially to ensure that your infrastructure gets created properly.</p>
</li>
</ul>
<h3 id="heading-creating-an-iac-powered-gcp-virtual-machine">Creating an IaC-Powered GCP Virtual Machine</h3>
<p>Now that you’ve learned these important commands, let’s test them all out by creating our first-ever IaC-powered GCP virtual machine.</p>
<p>In your code editor, type the following code:</p>
<pre><code class="lang-plaintext">provider "google" {
  project = "your-gcp-project-id"  # Replace with your GCP Project ID
  region  = "us-central1"          
  zone    = "us-central1-a"        
}
</code></pre>
<p>This code highlights the cloud provider we’re using to generate the cloud resources we need. In our case, it’s the Google cloud program. The name assigned to it is just “google”. Other cloud providers like AWS and Azure are “aws” and “azure” respectively.</p>
<p>The second line identifies the GCP subscription identifier, which is unique to each GCP account (and helps facilitate accurate integration). <strong>You should use yours in the space provided.</strong></p>
<p>You’ll also need to include a suitable resource region and resource availability zone. This serves as the physical base for the virtual machine we’ll create so we can run it. In this scenario, I chose the USA central region and 1-a availability zone, respectively. You can read more <a target="_blank" href="https://cloud.google.com/compute/docs/regions-zones">here</a> about cloud regions and availability zones.</p>
<pre><code class="lang-plaintext">resource "google_compute_instance" "vm_instance" {
  name         = "example-vm"      
  machine_type = "e2-medium"          

  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-11" 
    }
  }
</code></pre>
<p>The code snippet above specifies the exact compute resource that’ll be orchestrated, which in our case is a virtual machine instance coded as “vm_instance”. <code>'example-vm’</code> is the name we want to assign to the virtual machine we will be creating for this project. It is important to note that the virtual machine name must be unique too. The type of the virtual machine we opted for was the E2 (General purpose)-medium type VM. You can get more information on Virtual machine types <a target="_blank" href="https://cloud.google.com/compute/docs/general-purpose-machines">here</a>.</p>
<p>Going further, we also specify the expected booted Operating system (“boot_disk”) which is an image of the Debian Linux Operating system version 11 in my case.</p>
<pre><code class="lang-plaintext">  network_interface {
    network = "default"  # Attach to the default VPC network
    access_config {

    }
  }

output "instance_ip" {
  value = google_compute_instance.vm_instance.network_interface[0].access_config[0].nat_ip
}
</code></pre>
<p>To complete the creation of our virtual machine, we need to set up a Virtual Network to allow remote access to the VM. The network interface block connects the virtual machine to the default VPC (Virtual Private Cloud) network provided by GCP. We won’t be able to interface with our virtual machine without the VPC network. The output block also displays the default access IP address in the terminal, which we can use to connect to the virtual machine.</p>
<p>Here is the final expected code:</p>
<pre><code class="lang-plaintext">
provider "google" {
  project = "your-gcp-project-id"  # Replace with your GCP Project ID
  region  = "us-central1"          
  zone    = "us-central1-a"       
}

resource "google_compute_instance" "vm_instance" {
  name         = "example-vm"         
  machine_type = "e2-medium"          

  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-11"  
    }
  }

  network_interface {
    network = "default"  # Attach to the default VPC network
    access_config {

    }
  }

output "instance_ip" {
  value = google_compute_instance.vm_instance.network_interface[0].access_config[0].nat_ip
}
</code></pre>
<p>Going on from there, we’ll now be executing this code using the commands highlighted in the image below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734796321588/29561c5c-3908-43d1-8579-53a3de33358a.png" alt="29561c5c-3908-43d1-8579-53a3de33358a" class="image--center mx-auto" width="958" height="110" loading="lazy"></p>
<p>The command <code>terraform -v</code> confirms that Terraform has been successfully installed on the terminal. The expected output will be the version of the Terraform tool installed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734796340539/149f5f24-90eb-4777-8ae3-18acdd3c758a.png" alt="149f5f24-90eb-4777-8ae3-18acdd3c758a" class="image--center mx-auto" width="723" height="347" loading="lazy"></p>
<p>The next command executed is the <code>terraform init</code> function which initializes a communication with the cloud service provider, which in our case is GCP. All needed dependencies are also installed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734796301342/bd886728-dfdb-49f7-bcbf-1e53ff203b35.png" alt="bd886728-dfdb-49f7-bcbf-1e53ff203b35" class="image--center mx-auto" width="707" height="36" loading="lazy"></p>
<p>The <code>terraform fmt</code> command is also run to ensure adequate code formatting and indentation. Then the <code>terraform plan</code> command is sequentially executed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734889731467/bb454ec4-47e4-40a4-84fc-91c580fb77bb.png" alt="bb454ec4-47e4-40a4-84fc-91c580fb77bb" class="image--center mx-auto" width="1174" height="473" loading="lazy"></p>
<p>From the image above, you can see the steps Terraform intends to use to generate the expected Virtual machine.</p>
<p>On successful execution of Terraform plan, we will then execute the <code>terraform apply</code> function to execute the steps outlined by Terraform plan.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734796355772/d1d8f9f9-98a9-4ab0-be00-60a9b2b993a9.png" alt="d1d8f9f9-98a9-4ab0-be00-60a9b2b993a9" class="image--center mx-auto" width="540" height="84" loading="lazy"></p>
<p>This will generate a prompt asking for a confirmation of the Terraform execution as shown above. Typing “Yes” will allow the operation to proceed smoothly.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734796361770/a08254b0-878a-4681-b6ce-f6b0a9a83bc6.png" alt="a08254b0-878a-4681-b6ce-f6b0a9a83bc6" class="image--center mx-auto" width="1185" height="252" loading="lazy"></p>
<p>On successful execution, a success message will be displayed as shown above. With that, we have created our Cloud infrastructure with just code. The <code>terraform destroy</code> command can then be called to remove the created Virtual machines.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this article, you’ve learned the basics about infrastructure as code. We discussed Terraform, its benefits, and some of its key features and commands. We also illustrated its use in a demo project.</p>
<p>To further enhance your knowledge, you can <a target="_blank" href="https://developer.hashicorp.com/terraform?product_intent=terraform">consult Terraform‘s documentation</a> for more code examples. I would also recommend utilizing your newly gained knowledge to automate a project with real-life uses.</p>
<p>Feel free to message me with any comments or questions. You can also check out my other articles <a target="_blank" href="http://portfolio-oluwatobi.netlify.app">here</a>. Till next time, keep on coding!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Simplify AWS Multi-Account Management with Terraform and GitOps ]]>
                </title>
                <description>
                    <![CDATA[ In the past, in the world of cloud computing, a company's journey often began with a single AWS account. In this unified space, development and testing environments coexisted, while the production environment lived in a separate account. This arrange... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/simplify-aws-multi-account-management-with-terraform-and-gitops/</link>
                <guid isPermaLink="false">6745e19265a8ceed4a65c3eb</guid>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Terraform ]]>
                    </category>
                
                    <category>
                        <![CDATA[ gitops ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Nitheesh Poojary ]]>
                </dc:creator>
                <pubDate>Tue, 26 Nov 2024 14:56:18 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1730239127065/317aa4dd-aba9-4a9e-8abb-7cacfbd0e672.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In the past, in the world of cloud computing, a company's journey often began with a single AWS account. In this unified space, development and testing environments coexisted, while the production environment lived in a separate account.</p>
<p>This arrangement might work well in early days, but as a company grows and their needs become more specialized, the simplicity of a single account might start to show its limitations. The demand for dedicated environments will start to increase, and soon, that company may need to create new AWS accounts for specific functions like security, DevOps, and billing.</p>
<p>With each new account, the complexity of managing security policies and logging across the entire infrastructure grows exponentially. The cloud architects for these companies will then realize that they need a more centralized and streamlined approach to manage this expanding digital presence.</p>
<h3 id="heading-enter-aws-organizations">Enter AWS Organizations</h3>
<p>AWS Organizations is a service designed to streamline AWS account management. This powerful tool allows you to group multiple AWS accounts under a single umbrella. With AWS Organizations, you can easily create organizational units, apply service control policies, and manage permissions across all accounts. This not only simplifies the process but also enhances security and compliance.</p>
<p>The billing processes of AWS Organizations have also been optimized through the centralization of payments and the generation of comprehensive expense reports for each account. This improved clarity in financial management makes it easier for companies to allocate resources in a more efficient manner and strategize for future expansion.</p>
<p>AWS Organizations can help your team consistently enforce security policies, enable logging across all accounts, and streamline administrative tasks. Cloud infrastructure is now a well-organized, secure, and efficient machine, ready to support a company's ambitions for years to come.</p>
<p>In this article, we’ll discuss what it means to have a multi-account setup and how it works. I’ll walk you through everything from the deployment architecture to creating an Organizational Unit and beyond.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-components-of-multi-account-setup">Components of Multi-Account Setup</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-automate-a-multi-account-strategy">How to Automate a Multi-Account Strategy</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-aws-organization-structure">AWS Organization Structure</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-deployment-architecture">Deployment Architecture</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-overview-of-cicd-components">OverView of CI/CD Components</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-cicd-deployment-process-explained">CI/CD Deployment Process Explained</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-automate-landing-zone-creation">How to Automate Landing Zone Creation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-an-organizational-unit">How to Create an Organizational Unit</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-automate-attaching-control-tower-control-to-the-ou">How to Automate Attaching Control Tower Control to the OU</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-components-of-multi-account-setup"><strong>Components of Multi-Account Setup</strong></h2>
<p>First, let's take a detailed look at the various components that make up an AWS multi-account strategy:</p>
<ul>
<li><p><strong>AWS Control Tower</strong></p>
</li>
<li><p><strong>Landing zone</strong></p>
</li>
<li><p><strong>AWS OU</strong></p>
</li>
<li><p><strong>AWS SSO</strong></p>
</li>
<li><p><strong>Control Tower Controls</strong></p>
</li>
<li><p><strong>Service control policies (SCPs)</strong></p>
</li>
</ul>
<h3 id="heading-what-is-aws-control-tower"><strong>What is AWS Control Tower?</strong></h3>
<p>AWS Control Tower is a comprehensive service that enables you to set up and manage a multi-account AWS environment efficiently. It’s designed based on best practices from AWS experts and adheres to industry standards and requirements.</p>
<p>By using AWS Control Tower, you can ensure that your AWS environment is secure, compliant, and well-organized, facilitating easier management and scalability.</p>
<h4 id="heading-features-of-aws-control-tower">Features of AWS Control Tower:</h4>
<ul>
<li><p>Cloud IT can be confident that all accounts are in line with company-wide regulations, and distributed teams may create new AWS accounts quickly.</p>
</li>
<li><p>You can enforce best practices, standards, and regulatory requirements with preconfigured controls.</p>
</li>
<li><p>You can automate your AWS environment setup with best-practice blueprints. These blueprints cover various aspects such as multi-account structure, identity and access management, as well as account provisioning workflow.</p>
</li>
<li><p>It lets you govern new or existing account configurations, gain visibility into compliance status, and enforce controls at scale.</p>
</li>
</ul>
<h3 id="heading-what-is-a-landing-zone-in-aws"><strong>What is a Landing Zone in AWS?</strong></h3>
<p>A landing zone helps you quickly set up a cloud environment using automation, including preconfigured settings that follow industry best practices for ensuring the security of your AWS accounts.</p>
<p>The starting point serves as a foundation for your company to efficiently initiate and implement workloads and applications, ensuring a secure and reliable infrastructure environment.</p>
<p>There are two choices for creating a landing zone. First, you can use the AWS Control Tower dashboard. Second, you can build a custom landing zone. If you are new to AWS, I recommend using AWS Control Tower to create a landing zone.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732137800622/f72dbf02-fa34-4004-999d-71a2af33f90b.png" alt="AWS Landing Zone" class="image--center mx-auto" width="1400" height="728" loading="lazy"></p>
<p>If you opt for creating a landing zone via the Control Tower dashboard, the following will be implemented in your landing zone:</p>
<ul>
<li><p>A multi-account environment with AWS organizations.</p>
</li>
<li><p>Identity management through the default directory in AWS IAM Identity Center.</p>
</li>
<li><p>Federated access to accounts using IAM Identity Center.</p>
</li>
<li><p>Centralized logging from AWS CloudTrail and AWS Config stored in Amazon Simple Storage Service (Amazon S3).</p>
</li>
<li><p>Enabled cross-account <a target="_blank" href="https://docs.aws.amazon.com/general/latest/gr/aws-security-audit-guide.html">security audits</a> using IAM Identity Center.</p>
</li>
</ul>
<h3 id="heading-what-is-an-aws-organizational-unit"><strong>What is an AWS Organizational Unit?</strong></h3>
<p>Using multiple accounts allows you to better support your security goals and company operations.</p>
<p>AWS Organizations enables policy-based management of multiple AWS accounts. When you create new accounts, you can arrange them in organizational units (OUs), which are groupings of accounts that provide the same application or service.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732137833615/6eebb3ab-94d0-4286-8dc4-9d3ae297e186.png" alt="AWS Organizational Units" class="image--center mx-auto" width="786" height="496" loading="lazy"></p>
<h4 id="heading-advantages-of-using-ous">Advantages of Using OUs:</h4>
<ul>
<li><p>Accounts are units of security protection. Potential hazards and security threats can be contained within one account without affecting others.</p>
</li>
<li><p>Teams have different assignments and resource needs. Setting up different accounts prevents teams from interfering with one another, as they might do if they used the same account.</p>
</li>
<li><p>Isolating data stores to an account reduces the number of people who have access to and can manage the data store.</p>
</li>
<li><p>The multi-account concept allows you to generate separate billable items for business divisions, functional teams, or individual users.</p>
</li>
<li><p>AWS quotas are set up per account. Separating workloads into different accounts gives each account an individual quota.</p>
</li>
</ul>
<h3 id="heading-what-is-aws-iam-identity-center"><strong>What is AWS IAM Identity Center?</strong></h3>
<p>The AWS IAM Identity Center provides a centralized solution for managing access to multiple AWS accounts and business applications.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732137875918/349673f8-1a09-4bcc-b1db-6a898d3d06b5.png" alt="AWS identity center" class="image--center mx-auto" width="1100" height="631" loading="lazy"></p>
<p>This method offers a single sign-on feature that allows employees to access all assigned accounts and applications from a single credential.</p>
<p>The personalized web user portal provides a centralized view of the user's assigned roles in AWS accounts.</p>
<p>For a uniformed authentication experience, users can sign in using the AWS Command Line Interface, AWS SDKs, or the AWS Console Mobile Application with their directory credentials.</p>
<p>You can also set up and oversee user IDs in IAM Identity Center's identity store, or you can connect to your existing identity provider, such as Microsoft Active Directory, Okta, and so on.</p>
<h3 id="heading-control-tower-controls-guardrails"><strong>Control Tower Controls (Guardrails)</strong></h3>
<p>Controls are predefined governance rules for security, operations, and compliance. You can select and apply them enterprise-wide or to specific groups of accounts.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732137911519/5dac3db6-15e6-476b-9b50-a1597a02fe84.png" alt="ControlTowerControls" class="image--center mx-auto" width="1322" height="843" loading="lazy"></p>
<p>Controls can be detective, preventive, or proactive and can be either mandatory or optional.</p>
<ul>
<li><p>First, we have detective controls (for example, detecting whether public read access to Amazon S3 buckets is allowed).</p>
</li>
<li><p>Next, preventive controls establish intent and prevent deployment of resources that don’t conform to your policies (for example, enabling AWS CloudTrail in all accounts).</p>
</li>
<li><p>Finally, proactive control capabilities use <a target="_blank" href="https://aws.amazon.com/blogs/mt/proactively-keep-resources-secure-and-compliant-with-aws-cloudformation-hooks/">AWS CloudFormation Hooks</a> to proactively identify and block the CloudFormation deployment of resources that are not compliant with the controls you have enabled. For example, developers cannot create S3 buckets that are capable of storing data in an unencrypted state at rest.</p>
</li>
</ul>
<h3 id="heading-service-control-policies-scp"><strong>Service Control Policies (SCP)</strong></h3>
<p>SCPs are a feature of the organization that allows you to set the maximum permissions for member accounts within the organization.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732137972306/80d0782c-0801-4548-9c0c-d4a11d43ecbe.png" alt="Service Control Policies" class="image--center mx-auto" width="1036" height="658" loading="lazy"></p>
<p>There are many functions and features of an SCP:</p>
<ul>
<li><p>If an SCP denies an action on an account, no entity in the account can perform that action, even if its IAM permissions allow it.</p>
</li>
<li><p>Prevents stopping or deletion of CloudTrail logging.</p>
</li>
<li><p>Prevents deletion of VPC flow logs.</p>
</li>
<li><p>Prohibits AWS accounts from leaving the organization.</p>
</li>
<li><p>Prevents AWS GuardDuty changes.</p>
</li>
<li><p>Prevents resource sharing using AWS Resource Access Manager (RAM) either externally or across environments.</p>
</li>
<li><p>Prevents disabling the default Amazon EBS encryption.</p>
</li>
<li><p>Prevents Amazon S3 unencrypted object uploads.</p>
</li>
<li><p>And prevents IAM users and roles in the affected accounts from creating certain resource types if the request doesn't include the specified tags.</p>
</li>
</ul>
<h2 id="heading-how-to-automate-a-multi-account-strategy"><strong>How to Automate a Multi-Account Strategy</strong></h2>
<p>Now that you’re familiar with the key concepts of a Multi-Account Strategy in AWS, let’s dive deeper into the practical parts.</p>
<p>In the coming subsections, we’ll cover how you can set up an AWS Control Tower, create a landing zone, and automatically create organizational units (OUs). I’ll also walk you through how to configure Control Tower controls—often known as guardrails—to uphold security, compliance, and governance over your AWS environment.</p>
<p>Once we finish this deployment, we will have a solution that includes the following components:</p>
<ul>
<li><p>Creates an AWS Organizations OU named Core within the organizational root structure.</p>
</li>
<li><p>Creates and adds two shared accounts to the Security OU: the Log Archive account and the Audit account.</p>
</li>
<li><p>Creates a cloud-native directory in IAM Identity Center, with ready-made groups and single sign-on access.</p>
</li>
<li><p>Applies all required preventive controls to enforce policies.</p>
</li>
<li><p>Applies required detective controls to identify configuration violations.</p>
</li>
</ul>
<h2 id="heading-aws-organization-structure"><strong>AWS Organization Structure</strong></h2>
<p>We will create and implement the following organizational structure. You can add or modify OUs as per your requirements.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732138006995/423e54cd-bf74-4aef-a2a3-d52294482ca0.png" alt="AWS Organization Structure" class="image--center mx-auto" width="1295" height="694" loading="lazy"></p>
<h2 id="heading-deployment-architecture"><strong>Deployment Architecture</strong></h2>
<p>I will be using Terraform Cloud and GitHub Actions for automating the entire process. This architecture applies to all three components, including core accounts, landing zones, and organizational unit (OU) creation and controls.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732138041912/0cba5af0-69ea-4ae9-986e-c1608d3d5c21.avif" alt="Deployment Architecture" width="1888" height="518" loading="lazy"></p>
<h3 id="heading-overview-of-cicd-components">Overview of CI/CD Components</h3>
<h4 id="heading-1-github-actions"><strong>1. GitHub Actions</strong></h4>
<p>GitHub Actions is a CI/CD platform that lets you automate your build, test, and deployment pipeline. You can create workflows that automatically build and test every pull request to your repository, ensuring code changes are verified before merging.</p>
<p>GitHub Actions also lets you deploy merged pull requests to production, streamlining the release process and reducing errors.</p>
<p>Using GitHub Actions enhances your development workflow, improves code quality, and speeds up the delivery of new features and updates.</p>
<h4 id="heading-2-terraform-cloud"><strong>2. Terraform Cloud</strong></h4>
<p>Terraform Cloud is a platform by HashiCorp for managing and executing your Terraform code. It offers tools and features that enhance collaboration between developers and DevOps engineers, making teamwork more efficient.</p>
<p>With Terraform Cloud, you can simplify and streamline your workflow, making it easier to handle complex infrastructure tasks and deployments. The platform also provides strong security features to protect your code and infrastructure, keeping your product secure throughout its lifecycle.</p>
<h3 id="heading-cicd-deployment-process-explained"><strong>CI/CD Deployment Process Explained</strong></h3>
<p>DevOps engineers are responsible for writing the Terraform code and then creating a pull request. I have added several test cases for my Terraform code in the <code>terraform-plan.yml</code> file, which runs only on the feature branch.</p>
<ul>
<li><p><strong>Check environment variables:</strong> Ensures all required environment variables are set.</p>
</li>
<li><p><strong>Checkout Code:</strong> Uses the <code>actions/checkout</code> action to check out the repository.</p>
</li>
<li><p><strong>Verify Checkout:</strong> Verifies that the checkout was successful.</p>
</li>
<li><p><strong>Validation:</strong> Verifies the Terraform code for any syntax errors. Pull requests contain proposed changes in code, allowing team members to review and merge them into the master branch. Once pull requests are merged with the master branch, all test cases are rerun, and the landing zone is created through Terraform Cloud</p>
</li>
</ul>
<h2 id="heading-what-to-know-before-setting-up-control-tower"><strong>What to Know Before Setting up Control Tower</strong></h2>
<p>Before beginning the process of setting up for AWS Control Tower, it is important to have a clear understanding of what limitations are associated with Control Tower and consider some key points.</p>
<ul>
<li><p>When setting up a landing zone, it is important to choose your home region. Once you have made a selection, you won’t be able to change your home region.</p>
</li>
<li><p>If you intend to establish a control tower on an existing AWS account that is already a part of an existing organizational unit (OU), you won’t be able to use it. In order to proceed, you’ll need to create a new AWS account that is not associated with any organizational Unit (OU).</p>
</li>
<li><p>As part of the Control Tower creation process, you’ll need to create mandatory accounts such as the Log Archive Account and Audit Accounts. Account-specific emails are required.</p>
</li>
<li><p>In order to set up the Landing Zone in the Management Account, it is essential to ensure that you have subscribed to the following services in the management account:</p>
<ul>
<li>S3, EC2, SNS, VPC, CloudFormation, CloudTrail, CloudWatch, AWS Config, IAM, AWS Lambda</li>
</ul>
</li>
<li><p>The AWS Control Tower baseline covers only a few services with limited customization options: IAM Identity Center, CloudTrail, Config, some configuration rules, and some SCPs in AWS Organizations.</p>
</li>
<li><p>Implementing IAM Identity Center is limited to the management account of an organization.</p>
</li>
<li><p>AWS Control Tower implements concurrency limitations, allowing only one operation to be performed at a time.</p>
</li>
<li><p>Note that certain AWS Regions do not support the operation of some controls in AWS Control Tower. This is because the specified Regions lack the necessary underlying functionality to support the required operations.</p>
</li>
</ul>
<h3 id="heading-how-to-create-a-control-tower"><strong>How to Create a Control Tower</strong></h3>
<p>Creating a Control Tower means setting up a landing zone. AWS landing zone requires creating two new member accounts: the Audit account and the Log Archive account. You will need two unique email addresses for these accounts.</p>
<p>We will manage this process using Terraform modules. To keep things simple and clear, we will divide the project into several modules. One module will create the two core accounts. Another module will handle the setup of the landing zone. The final module will create Organizational Units (OUs) and apply Control Tower controls to ensure governance and compliance.</p>
<h2 id="heading-how-to-automate-landing-zone-creation"><strong>How to Automate Landing Zone Creation</strong></h2>
<p>When you run this code, the Core OU and two accounts are created under the Core OU. I have mentioned two repositories for each component: one for deploying the AWS resources like the landing zone, OU, and Control Tower Controls and another for the Terraform module.</p>
<p>A <em>Terraform module</em> is a set of standard configuration files in a specific directory. Terraform modules group resources for a specific task, which reduces the amount of code needed for similar infrastructure components.</p>
<p>I have imported both the core account creation and landing zone creation modules into the same <a target="_blank" href="https://github.com/nitheeshp-irl/aws-landing-zone/blob/main/main.tf"><code>main.tf</code></a> file. This is necessary because the landing zone creation depends on the core account module. Including them together ensures all dependencies are managed properly and the deployment process is efficient.</p>
<p>This method also simplifies the project structure and helps avoid potential issues from managing these components separately.</p>
<p>The AWS Control Tower <a target="_blank" href="https://docs.aws.amazon.com/controltower/latest/APIReference/API_CreateLandingZone.html"><code>CreateLandingZone</code></a> API needs a landing zone version and a manifest file as input parameters. Below is an example <strong>LandingZoneManifest.json</strong> manifest.</p>
<pre><code class="lang-json">{
   <span class="hljs-attr">"governedRegions"</span>: [<span class="hljs-string">"us-west-2"</span>,<span class="hljs-string">"us-west-1"</span>],
   <span class="hljs-attr">"organizationStructure"</span>: {
       <span class="hljs-attr">"security"</span>: {
           <span class="hljs-attr">"name"</span>: <span class="hljs-string">"CORE"</span>
       },
       <span class="hljs-attr">"sandbox"</span>: {
           <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Sandbox"</span>
       }
   },
   <span class="hljs-attr">"centralizedLogging"</span>: {
        <span class="hljs-attr">"accountId"</span>: <span class="hljs-string">"222222222222"</span>,
        <span class="hljs-attr">"configurations"</span>: {
            <span class="hljs-attr">"loggingBucket"</span>: {
                <span class="hljs-attr">"retentionDays"</span>: <span class="hljs-number">60</span>
            },
            <span class="hljs-attr">"accessLoggingBucket"</span>: {
                <span class="hljs-attr">"retentionDays"</span>: <span class="hljs-number">60</span>
            },
            <span class="hljs-attr">"kmsKeyArn"</span>: <span class="hljs-string">"arn:aws:kms:us-west-1:123456789123:key/e84XXXXX-6bXX-49XX-9eXX-ecfXXXXXXXXX"</span>
        },
        <span class="hljs-attr">"enabled"</span>: <span class="hljs-literal">true</span>
   },
   <span class="hljs-attr">"securityRoles"</span>: {
        <span class="hljs-attr">"accountId"</span>: <span class="hljs-string">"333333333333"</span>
   },
   <span class="hljs-attr">"accessManagement"</span>: {
        <span class="hljs-attr">"enabled"</span>: <span class="hljs-literal">true</span>
   }
}
</code></pre>
<p>This module sets up the AWS landing zone using <code>landingzone_manifest_template</code>. The landing zone version and admin account ID are given through variables. This module also creates several IAM roles required for the landing zone setup.</p>
<p>I defined a local variable <code>landingzone_manifest_template</code>, which is a JSON template for setting up the landing zone. This JSON template has several important settings:</p>
<pre><code class="lang-yaml"><span class="hljs-string">provider</span> <span class="hljs-string">"aws"</span> {
  <span class="hljs-string">region</span> <span class="hljs-string">=</span> <span class="hljs-string">var.region</span>
}

<span class="hljs-string">locals</span> {
  <span class="hljs-string">landingzone_manifest_template</span> <span class="hljs-string">=</span> <span class="hljs-string">&lt;&lt;EOF</span>
{
    <span class="hljs-attr">"governedRegions":</span> <span class="hljs-string">$</span>{<span class="hljs-string">jsonencode(var.governed_regions)</span>},
    <span class="hljs-attr">"organizationStructure":</span> {
        <span class="hljs-attr">"security":</span> {
            <span class="hljs-attr">"name":</span> <span class="hljs-string">"Core"</span>
        }
    },
    <span class="hljs-attr">"centralizedLogging":</span> {
         <span class="hljs-attr">"accountId":</span> <span class="hljs-string">"${module.aws_core_accounts.log_account_id}"</span>,
         <span class="hljs-attr">"configurations":</span> {
             <span class="hljs-attr">"loggingBucket":</span> {
                 <span class="hljs-attr">"retentionDays":</span> <span class="hljs-string">$</span>{<span class="hljs-string">var.retention_days</span>}
             },
             <span class="hljs-attr">"accessLoggingBucket":</span> {
                 <span class="hljs-attr">"retentionDays":</span> <span class="hljs-string">$</span>{<span class="hljs-string">var.retention_days</span>}
             }
         },
         <span class="hljs-attr">"enabled":</span> <span class="hljs-literal">true</span>
    },
    <span class="hljs-attr">"securityRoles":</span> {
         <span class="hljs-attr">"accountId":</span> <span class="hljs-string">"${module.aws_core_accounts.security_account_id}"</span>
    },
    <span class="hljs-attr">"accessManagement":</span> {
         <span class="hljs-attr">"enabled":</span> <span class="hljs-literal">true</span>
    }
}
<span class="hljs-string">EOF</span>
}

<span class="hljs-string">module</span> <span class="hljs-string">"aws_core_accounts"</span> {
  <span class="hljs-string">source</span> <span class="hljs-string">=</span> <span class="hljs-string">"https://github.com/nitheeshp-irl/terraform_modules/aws_core_accounts_module"</span>

  <span class="hljs-string">logging_account_email</span>  <span class="hljs-string">=</span> <span class="hljs-string">var.logging_account_email</span>
  <span class="hljs-string">logging_account_name</span>   <span class="hljs-string">=</span> <span class="hljs-string">var.logging_account_name</span>
  <span class="hljs-string">security_account_email</span> <span class="hljs-string">=</span> <span class="hljs-string">var.security_account_email</span>
  <span class="hljs-string">security_account_name</span>  <span class="hljs-string">=</span> <span class="hljs-string">var.security_account_name</span>
}

<span class="hljs-string">module</span> <span class="hljs-string">"aws_landingzone"</span> {
  <span class="hljs-string">source</span>                  <span class="hljs-string">=</span> <span class="hljs-string">"https://github.com/nitheeshp-irl/blog_terraform_modules/aws_landingzone_module"</span>
  <span class="hljs-string">manifest_json</span>           <span class="hljs-string">=</span> <span class="hljs-string">local.landingzone_manifest_template</span>
  <span class="hljs-string">landingzone_version</span>     <span class="hljs-string">=</span> <span class="hljs-string">var.landingzone_version</span>
  <span class="hljs-string">administrator_account_id</span> <span class="hljs-string">=</span> <span class="hljs-string">var.administrator_account_id</span>
}
</code></pre>
<ul>
<li><p><strong>Governed Regions</strong>: Specifies the regions governed by the landing zone.</p>
</li>
<li><p><strong>Organization Structure</strong>: Defines the security structure with a dedicated security account.</p>
</li>
<li><p><strong>Centralized Logging</strong>: Configures logging, specifying the account ID and retention policies for logs.</p>
</li>
<li><p><strong>Security Roles</strong>: Specifies the account ID for security roles.</p>
</li>
<li><p><strong>Access Management</strong>: Enables access management.</p>
</li>
<li><p><strong>Core Accounts</strong>: The core accounts code, also defined in the same file, is what sets up essential AWS accounts for logging and security.</p>
</li>
</ul>
<p>You can find the full code here: <a target="_blank" href="https://github.com/nitheeshp-irl/aws-landing-zone">https://github.com/nitheeshp-irl/aws-landing-zone</a>.</p>
<h2 id="heading-how-to-create-an-organizational-unit"><strong>How to Create an Organizational Unit</strong></h2>
<p>When you run this code, different organizational units (OUs) are created according to the specifications in the <a target="_blank" href="https://github.com/nitheeshp-irl/aws-orgunits/blob/main/variables.auto.tfvars">variable</a> file.</p>
<p>Once the landing zone setup is finished, we can create an OU as per our business requirements. This will take the OU name from the variable file and create the OU.</p>
<pre><code class="lang-json">aws_region = <span class="hljs-string">"us-east-2"</span>

organizational_units = [
  {
    unit_name = <span class="hljs-attr">"apps"</span>
  },
  {
    unit_name = <span class="hljs-attr">"infra"</span>
  },
  {
    unit_name = <span class="hljs-attr">"stagingpolicy"</span>
  },
  {
    unit_name = <span class="hljs-attr">"sandbox"</span>
  },
  {
    unit_name = <span class="hljs-attr">"security"</span>
  }
]
</code></pre>
<p>You can see the code here:</p>
<ul>
<li><p><a target="_blank" href="https://github.com/nitheeshp-irl/aws-orgunits">AWS Organizational Units (OUs) Terraform Repo</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/nitheeshp-irl/blog-terraform-modules/tree/main/aws_org_module">AWS Organizational Units Terraform Module Path</a></p>
</li>
</ul>
<h2 id="heading-how-to-automate-attaching-control-tower-control-to-the-ou"><strong>How to Automate Attaching Control Tower Control to the OU</strong></h2>
<p>Once you have created the OU units using the above repository, this repository will apply Control Tower controls to the OUs.</p>
<p>After creating the required objects, you can attach controls to the OU if you need them. Here is the <a target="_blank" href="https://github.com/nitheeshp-irl/controltower_controls/blob/main/main.tf"><code>main.tf</code></a> file:</p>
<pre><code class="lang-yaml"><span class="hljs-string">provider</span> <span class="hljs-string">"aws"</span> {
  <span class="hljs-string">region</span> <span class="hljs-string">=</span> <span class="hljs-string">var.region</span>
}

<span class="hljs-string">module</span> <span class="hljs-string">"aws_controls"</span> {
  <span class="hljs-string">source</span> <span class="hljs-string">=</span> <span class="hljs-string">"https://github.com/nitheeshp-irl/blog_terraform_modules/awscontroltower-controls_module"</span>

  <span class="hljs-string">aws_region</span> <span class="hljs-string">=</span> <span class="hljs-string">var.aws_region</span>
  <span class="hljs-string">controls</span>   <span class="hljs-string">=</span> <span class="hljs-string">var.controls</span>
}
</code></pre>
<p>We used Terraform modules to create AWS resources.</p>
<p>Here are the control variables:</p>
<pre><code class="lang-json">aws_region = <span class="hljs-string">"us-east-2"</span>


controls = [
  {
    control_names = [
      <span class="hljs-attr">"AWS-GR_ENCRYPTED_VOLUMES"</span>,
      <span class="hljs-attr">"AWS-GR_EBS_OPTIMIZED_INSTANCE"</span>,
      <span class="hljs-attr">"AWS-GR_EC2_VOLUME_INUSE_CHECK"</span>,
      <span class="hljs-attr">"AWS-GR_RDS_INSTANCE_PUBLIC_ACCESS_CHECK"</span>,
      <span class="hljs-attr">"AWS-GR_RDS_SNAPSHOTS_PUBLIC_PROHIBITED"</span>,
      <span class="hljs-attr">"AWS-GR_RDS_STORAGE_ENCRYPTED"</span>,
      <span class="hljs-attr">"AWS-GR_RESTRICTED_COMMON_PORTS"</span>,
      <span class="hljs-attr">"AWS-GR_RESTRICTED_SSH"</span>,
      <span class="hljs-attr">"AWS-GR_RESTRICT_ROOT_USER"</span>,
      <span class="hljs-attr">"AWS-GR_RESTRICT_ROOT_USER_ACCESS_KEYS"</span>,
      <span class="hljs-attr">"AWS-GR_ROOT_ACCOUNT_MFA_ENABLED"</span>,
      <span class="hljs-attr">"AWS-GR_S3_BUCKET_PUBLIC_READ_PROHIBITED"</span>,
      <span class="hljs-attr">"AWS-GR_S3_BUCKET_PUBLIC_WRITE_PROHIBITED"</span>,
    ],
    organizational_unit_names = [<span class="hljs-attr">"infra"</span>, <span class="hljs-attr">"apps"</span>]
  }
]
</code></pre>
<p>You can see the code here:</p>
<ul>
<li><p><a target="_blank" href="https://github.com/nitheeshp-irl/controltower_controls">Terraform Repo for Creating control tower controls</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/nitheeshp-irl/blog-terraform-modules/tree/main/awscontroltower-controls_module">Terraform Module for creating Control Tower Controls</a></p>
</li>
</ul>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>Navigating a multi-account strategy in AWS can be challenging, but with AWS Control Tower and a structured approach, it becomes manageable.</p>
<p>Using AWS Control Tower, your team can ensure that their AWS environments are secure, compliant, and well-organized. The automated setup, governance at scale, and centralized management through AWS Organizations provide a strong foundation for cloud infrastructure.</p>
<p>Implementing a landing zone through AWS Control Tower offers a secure and standardized starting point, allowing for quicker deployment and better governance. Using organizational units (OUs) segregates accounts based on business needs, improving security and operational efficiency. AWS IAM Identity Center simplifies access management, providing a unified authentication experience across multiple accounts and applications.</p>
<p>Service Control Policies (SCPs) help keep things secure and compliant by making sure all resources follow the organization's rules. Terraform Cloud and GitHub Actions make it easier to deploy resources, offering a smooth CI/CD pipeline for managing infrastructure changes.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Terraform Security Best Practices ]]>
                </title>
                <description>
                    <![CDATA[ By Aaron Katz Terraform is a popular Infrastructure as Code (IaC) tool that allows users to define and manage cloud infrastructure in a declarative way.  However, like any tool, Terraform can introduce security risks if not used properly. In this art... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/terraform-security-best-practices/</link>
                <guid isPermaLink="false">66d45d6351f567b42d9f8427</guid>
                
                    <category>
                        <![CDATA[ Infrastructure as code ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Terraform ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 28 Sep 2023 05:12:03 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/09/1-_rE5zXXVTZ_R4u-2FZh0iw.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Aaron Katz</p>
<p>Terraform is a popular Infrastructure as Code (IaC) tool that allows users to define and manage cloud infrastructure in a declarative way.  However, like any tool, Terraform can introduce security risks if not used properly.</p>
<p>In this article, we will explore the most common security risks when using Terraform, the threat landscape and attack surface, how it can be exploited, and how users can stay secure by following Terraform security best practices.</p>
<h2 id="heading-what-is-terraform-and-why-should-i-use-it">What is Terraform and why Should I Use it?</h2>
<p>Terraform is an open-source tool developed by HashiCorp that enables users to define and provision infrastructure resources across multiple cloud providers and on-premises environments. It allows organizations to treat infrastructure as code, bringing the benefits of version control, collaboration, and automation to infrastructure management.</p>
<p>By adopting Terraform and embracing the principles of Infrastructure as Code, organizations can achieve several benefits:</p>
<ul>
<li><strong>Consistency and repeatability</strong>: Infrastructure deployments become consistent and repeatable, eliminating the risk of manual errors and ensuring that the same configuration can be applied across different environments.</li>
<li><strong>Version control and collaboration</strong>: Infrastructure code can be stored in version control systems, enabling teams to collaborate, track changes, and roll back to previous versions if needed.</li>
<li><strong>Automation and scalability</strong>: Infrastructure deployments can be automated, allowing organizations to scale their infrastructure quickly and efficiently based on demand.</li>
<li><strong>Auditing and compliance</strong>: Infrastructure code can be audited and reviewed for compliance with security and regulatory standards, ensuring that best practices are followed.</li>
</ul>
<p>While Terraform offers a powerful and flexible solution for managing infrastructure, it is essential to address the security considerations associated with using the tool effectively.</p>
<h2 id="heading-security-considerations">Security considerations</h2>
<p>As with any tool or technology, Terraform is not immune to security threats and challenges. It is crucial to understand the threat landscape and the potential risks associated with using Terraform. </p>
<p>By identifying these risks, organizations can implement appropriate security measures to mitigate them effectively.</p>
<h3 id="heading-configuration-errors">Configuration errors</h3>
<p>One of the primary security risks associated with Terraform is misconfigurations in the infrastructure code. </p>
<p>Misconfigurations can lead to vulnerabilities and expose critical resources to unauthorized access or compromise. Common misconfigurations include weak access controls, open network ports, and incorrect permission settings on cloud resources.</p>
<h3 id="heading-secrets-management">Secrets management</h3>
<p>Terraform relies on access keys and secret keys to authenticate with cloud providers and provision resources. </p>
<p>Storing these credentials insecurely can lead to security vulnerabilities such as unauthorized access and data breaches. </p>
<p>Handling access credentials securely is crucial to the overall security posture of Terraform deployments.</p>
<h3 id="heading-state-security">State security</h3>
<p>Terraform uses state files to keep track of the resources it has created or modified.</p>
<p>These state files contain sensitive information, such as resource IDs, metadata, and secrets as referenced above. Inadequate management of state files can lead to security risks, including unauthorized access and data exposure. </p>
<p>State files can be stored either locally on a machine (suitable only for solo testing) or on a remote backend, such as a cloud storage resource, which should be encrypted and locked down.</p>
<h3 id="heading-supply-chain-security">Supply chain security</h3>
<p>As with any software development process, the supply chain of Terraform modules and associated dependencies can introduce security risks.</p>
<p>Organizations must assess the trustworthiness and security of the modules they use and ensure they are regularly updated to address any vulnerabilities.</p>
<h3 id="heading-permissions-management">Permissions management</h3>
<p>Appropriate permissions and access controls are essential to prevent unauthorized changes to infrastructure resources. </p>
<p>Managing permissions effectively can help reduce the risk of accidental or malicious actions that could compromise the security of the infrastructure.</p>
<h2 id="heading-recommendations">Recommendations</h2>
<p>To stay secure while using Terraform, users should follow these best practices:</p>
<ol>
<li>Execute Terraform programmatically to minimize human error and enforce security policies.</li>
<li>Use safe Terraform modules and avoid using untrusted or vulnerable third-party components.</li>
<li>Secure the data store when remotely storing state data to prevent unauthorized access (for example, through encryption and restrictive access permissions).</li>
<li>Avoid storing secrets in state files; instead, use secret management solutions like AWS Secrets Manager, Azure Key Vault, or Google Cloud Secret Manager.</li>
<li>Use Terraform security scanners to identify and remediate potential vulnerabilities.</li>
<li>Implement centralized security policy and governance within Terraform code to improve visibility and enforce least privilege.</li>
<li>Require multi-factor authentication for collaborators to improve security posture.</li>
<li>Keep Terraform and all modules up to date.</li>
<li>Regularly audit terraform configurations for security vulnerabilities and misconfigurations, and build in appropriate automated tooling to detect violations prior to deployment.</li>
<li>Conduct regular drift detection to determine if resources deployed in your cloud provider match what should have been deployed in the terraform state file.</li>
</ol>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Terraform is a powerful tool for managing cloud infrastructure, but it can introduce security risks if not used properly. </p>
<p>By following Terraform security best practices, users can minimize the risk of security incidents and maintain a secure Terraform environment. </p>
<p>It's essential to keep Terraform configurations and infrastructure up-to-date, monitor for security threats, and adapt to the evolving threat landscape. By doing so, users can ensure that their cloud infrastructure is secure and compliant with industry standards.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Terraform Certified Associate (003) – How to Study for the Exam ]]>
                </title>
                <description>
                    <![CDATA[ By Chris Williams I've been meaning to get my Terraform associates certification for some time now, but something always got in the way.  Finally I was able to sit down and work my way through the study materials. Currently Andrew Brown and I are cre... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/terraform-certified-associate-003-study-notes/</link>
                <guid isPermaLink="false">66d45e033a8352b6c5a2aa11</guid>
                
                    <category>
                        <![CDATA[ Certification ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Cloud Computing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Terraform ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 11 Sep 2023 19:17:49 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/09/terraform-course.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Chris Williams</p>
<p>I've been meaning to get my Terraform associates certification for some time now, but something always got in the way. </p>
<p>Finally I was able to sit down and work my way through the study materials.</p>
<p>Currently Andrew Brown and I are creating two Terraform Bootcamps: <a target="_blank" href="https://terraform.cloudprojectbootcamp.com/">one for beginners</a> and the other one for intermediate practitioners. These bootcamps will be similar to Andrew's <a target="_blank" href="https://aws.cloudprojectbootcamp.com/">AWS Cloud Project Bootcamp</a> (YouTube playlist <a target="_blank" href="https://youtube.com/playlist?list=PLBfufR7vyJJ7k25byhRXJldB5AiwgNnWv">here</a>). </p>
<p>In this guide, I've compiled my live study notes that I've used for prepping to sit the Terraform Certified Associate Exam to help you know what to study.</p>
<p>Here's what I'll cover:</p>
<ul>
<li><a class="post-section-overview" href="#heading-preparation-materials">Preparation Materials</a></li>
<li><a class="post-section-overview" href="#heading-how-to-use-this-guide">How to Use This Guide</a></li>
<li><a class="post-section-overview" href="#heading-understand-infrastructure-as-code-iac-concepts">Understand Infrastructure as Code (IaC) concepts</a></li>
<li><a class="post-section-overview" href="#heading-understand-the-purpose-of-terraform-vs-other-iac-tools">Understand the purpose of Terraform (vs other IaC tools)</a></li>
<li><a class="post-section-overview" href="#heading-understand-terraform-basics">Understand Terraform Basics</a></li>
<li><a class="post-section-overview" href="#heading-use-terraform-outside-the-core-workflow">Use Terraform outside the core workflow</a></li>
<li><a class="post-section-overview" href="#heading-interact-with-terraform-modules">Interact with Terraform modules</a></li>
<li><a class="post-section-overview" href="#heading-use-the-core-terraform-workflow">Use the core Terraform workflow</a></li>
<li><a class="post-section-overview" href="#heading-implement-and-maintain-state">Implement and maintain state</a></li>
<li><a class="post-section-overview" href="#heading-read-generate-and-modify-configuration">Read, generate, and modify configuration</a></li>
<li><a class="post-section-overview" href="#heading-understand-terraform-cloud-capabilities">Understand Terraform Cloud capabilities</a></li>
</ul>
<h2 id="heading-preparation-materials">Preparation Materials</h2>
<p>In getting ready for any exam, I like to list out the study materials and reference resources that I'm going to be using ahead of time. This allows me to schedule my study time with a bit more discipline.</p>
<p>Here are the materials I've used:</p>
<ol>
<li><a target="_blank" href="https://developer.hashicorp.com/certifications">HashiCorp Cloud Engineer Certifications</a> (Free) </li>
</ol>
<p>This site has a wealth of information:</p>
<p><img src="https://mistwire.com/wp-content/uploads/2023/05/CleanShot-2023-05-30-at-13.48.29-2-1024x548.png" alt="Image" width="1024" height="548" loading="lazy">
<em>https://developer.hashicorp.com/certifications</em></p>
<ol start="2">
<li>The <a target="_blank" href="https://developer.hashicorp.com/terraform/tutorials/certification-003">Terraform Associate Prep Tutorials</a> (Free)</li>
</ol>
<p><img src="https://mistwire.com/wp-content/uploads/2023/06/CleanShot-2023-06-05-at-12.22.00-1024x683.png" alt="Image" width="1024" height="683" loading="lazy">
<em>https://developer.hashicorp.com/terraform/tutorials/certification-003</em></p>
<ol start="3">
<li><p>The newly updated <a target="_blank" href="https://youtu.be/SPcwo0Gq9T8">freeCodeCamp course</a> by Andrew Brown (Free) 😁</p>
</li>
<li><p><a target="_blank" href="https://jumppad.dev/">Jumppad.dev</a> and their <a target="_blank" href="https://github.com/jumppad-labs/terraform-workshop">Terraform-workshop</a> repository (Free)</p>
</li>
<li><p><a target="_blank" href="https://www.udemy.com/course/terraform-hands-on-labs/">The Terraform Hands On Labs</a> Udemy course by Bryan Krausen (Paid)</p>
</li>
</ol>
<h2 id="heading-how-to-use-this-guide">How to Use this Guide</h2>
<p>Each of the below sections will cover one of the nine domains specified in the <a target="_blank" href="https://developer.hashicorp.com/terraform/tutorials/certification-003/associate-review-003">Terraform Review Guide</a>. Read through the documentation, complete the tutorials, and dig into the additional links I've provided. </p>
<p>The sections below are the large, important bits of information that I've culled for each domain, but this study guide is <em>not comprehensive.</em> Depending on how comfortable you are with the domain-specific knowledge, you will need to dive into the links provided in each section to round out your understanding of the material. </p>
<h2 id="heading-understand-infrastructure-as-code-iac-concepts">Understand Infrastructure as Code (IaC) concepts</h2>
<p>Domain 1 covers the broad concepts of IaC. Why do we want to use it? What is it good for? Are there any areas where you would NOT want to use it? What are the different kinds of languages that can be used for IaC and how are the approaches different?</p>
<h3 id="heading-explain-what-iac-is">Explain what IaC is:</h3>
<p>Manually configuring your infrastructure is fine for prototyping, but is prone to human error at scale (or when you need to provision the same env repeatedly). IaC is a blueprint of your infra and allows you to share/version/inventory/document your infra.</p>
<p>There are two main types of infrastructure:</p>
<p><strong>Declarative</strong> = What you see is what you get. It's explicit with 0 chance of misconfiguration:</p>
<ul>
<li>Azure only -&gt; ARM Templates, Azure Blueprints</li>
<li>AWS only -&gt; CloudFormation</li>
<li>GCP only -&gt; Cloud Deployment Manager</li>
<li>All of the above (&amp; many others) -&gt; Terraform</li>
</ul>
<p><strong>Imperative</strong> = Uses existing programming languages like Python, JS or Ruby:</p>
<ul>
<li>AWS only -&gt; AWS CDK</li>
<li>AWS, Azure, GCP, K8s -&gt; Pulumi</li>
</ul>
<p>Terraform supports For loops, dynamic blocks, complex data structures – so it's declarative with some imperative benefits.</p>
<p>The Infrastructure Lifecycle is having clearly defined work phases for planning, designing, building, testing, maintaining and retiring your infrastructure.</p>
<p><strong>Idempotent</strong>: a property of some operations such that no matter how many times you execute them, you achieve the same result. Terraform is idempotent because, no matter how many times you run the same configuration file, you will end up with the same expected state.</p>
<p><strong>Configuration Drift</strong>: an unexpected configuration change away from what is stated in the config file. Can be due to manual adjustment (console access in prod = BAD  😂), evil h@xx0rs, etc... How do we fix it?</p>
<ul>
<li><p>Detect: use a compliance tool like AWS Config, or built-in support e.g. AWS CF Drift Detection, TF statefiles</p>
</li>
<li><p>Correct:</p>
<ul>
<li>TF refresh &amp; plan commands</li>
<li>Manually correct (try not to do this)</li>
<li>Reprovision (comes with it's own risks)</li>
</ul>
</li>
<li><p>Prevent:</p>
<ul>
<li>use immutable infrastructure</li>
<li>always create &amp; destroy, never reuse</li>
<li>use GitOps to version control IaC:<ul>
<li>Create tf file</li>
<li>commit</li>
<li>Pull Request</li>
<li>peer review</li>
<li>commit to main</li>
</ul>
</li>
<li>GitHub action triggers build</li>
</ul>
</li>
</ul>
<h3 id="heading-mutable-vs-immutable-infrastructure">Mutable vs Immutable infrastructure</h3>
<p>Think of mutable infrastructure as (1) building a base image (2) Deploying that base image then (3) configuring the software after deploy. </p>
<p>Think if immutable infrastructure as (1) building a fully installed base image (2) deploying then (3) if a change needs to be made, tearing down that infra and rebuilding it with a new fully installed base image</p>
<ul>
<li>Mutable = Develop -&gt; Deploy (VM) -&gt; Configure (e.g. cloud-init)</li>
<li>Immutable = Develop -&gt; Configure (Packer) -&gt; Deploy</li>
</ul>
<h3 id="heading-describe-advantages-of-iac-patterns">Describe advantages of IaC patterns:</h3>
<p>Why is Infrastructure as Code important? It allows you to:</p>
<ul>
<li>Build &amp; manage your infra in (relatively 😅) safe, consistent &amp; repeatable ways</li>
<li>Share &amp; reuse your configurations more easily</li>
<li>Manage infra on multiple cloud platforms</li>
<li>Track resource changes</li>
<li>Use version control (Git, GitHub, etc..) to collaborate with team members</li>
</ul>
<h2 id="heading-understand-the-purpose-of-terraform-vs-other-iac-tools">Understand the Purpose of Terraform (vs other IaC tools)</h2>
<p>Domain 2 spells out the differences between Terraform and the other IaC offerings available in the market. Agnostic vs Cloud Specific IaC tools each have their own place in the market and you will choose between them based upon your (and your companies) needs.</p>
<h3 id="heading-explain-multi-cloud-and-provider-agnostic-benefits">Explain multi-cloud and provider agnostic benefits:</h3>
<ul>
<li>Increases fault-tolerance</li>
<li>Allows for more graceful recovery from cloud provider outages</li>
<li>Reduces complexity because each provider has its own interfaces, tools, and workflows that Terraform abstracts for you</li>
<li>Use the same workflow to manage multiple providers and handle cross-cloud dependencies</li>
<li>Unified resource view</li>
<li>A technology agnostic approach/workflow</li>
</ul>
<h3 id="heading-explain-the-benefits-of-state">Explain the benefits of state</h3>
<ul>
<li>State (a statefile) is necessary for Terraform to function</li>
<li>It is a map referencing a resource in the tf file to an actual resource that is deployed<ul>
<li>For example resource <code>"aws_instance" "webserver" {}</code> mapping to known instance <code>"i-0dfcf96cceba9bc77"</code></li>
</ul>
</li>
<li>Metadata tracking<ul>
<li>resource dependencies</li>
<li>build/delete order tracking</li>
<li>Ordering within one provider and across multiple providers -&gt; complexity quickly ramps up</li>
</ul>
</li>
<li>For larger environments use <code>-refresh=false</code> and <code>-target</code> flags<ul>
<li>querying every resource can take too long</li>
<li>cached state is treated as record of truth</li>
</ul>
</li>
<li>Use remote state when working in teams<ul>
<li>remote locking prevents 2 admins making simultaneous changes</li>
</ul>
</li>
</ul>
<h2 id="heading-understand-terraform-basics">Understand Terraform Basics</h2>
<p>Domain 3 gets into the commands and processes that you will need to understand to leverage Terraform. It covers the installation of Terraform itself, the providers, what modules are, and the basic workflow that you will do when building IaC environments.</p>
<p>Some helpful Terraform CLI cheat codes: <code>terraform -help</code> and <code>terraform (command) -help</code>.</p>
<h3 id="heading-terraform-lifecycle">Terraform lifecycle:</h3>
<ul>
<li>code - create or edit your terraform config file</li>
<li><code>terraform init</code> - Initialize workspace, pull providers and modules</li>
<li><code>terraform plan</code> - see what changes will be made (or generate an execution plan) also known as a "dry-run"</li>
<li><code>terraform validate</code> - ensure types, values, and required attributes are valid and present</li>
<li><code>terraform apply</code> - make the things!</li>
<li><code>terraform destroy</code> - unmake the things! 😱</li>
</ul>
<p><img src="https://mistwire.com/wp-content/uploads/2023/06/CleanShot-2023-06-05-at-11.53.35.png" alt="Image" width="1019" height="547" loading="lazy">
<em>Diagram showing a basic Terraform workflow</em></p>
<h3 id="heading-hcl-syntax">HCL Syntax:</h3>
<p>The syntax of the Terraform language consists of a few standard elements:</p>
<p><img src="https://mistwire.com/wp-content/uploads/2023/07/image-2.png" alt="Image" width="720" height="194" loading="lazy">
<em>Standard Elements of HCL</em></p>
<p>For example, this is a basic resource block that will spin up an EC2 instance:</p>
<p><img src="https://mistwire.com/wp-content/uploads/2023/07/image-1-1024x380.png" alt="Image" width="1024" height="380" loading="lazy">
<em>HCL Example with standard elements highlighted</em></p>
<pre><code class="lang-hcl">resource "aws_instance" "terraform_101_server"{
  ami            = "ami-0b5eea76982371e91"
  instance_type  = "t2.micro"
</code></pre>
<ul>
<li>Blocks are containers for other content and usually represent the configuration of some kind of object (like a resource). Blocks have a block type, can have zero or more labels, and have a body that contains any number of arguments and nested blocks. There are several types of blocks:<ul>
<li>Terraform block - settings for the execution environment of Terraform itself (required terraform version, backend settings, and so on.)</li>
<li>Provider block - details of the provider(s) being used. Includes information like access mechanisms, regional options, profile to use, and so on...</li>
<li>Resource block - specifies a single uniquely named resource managed by terraform. Includes resource type, name, and config options
Data block - data sources that can be queried (cloud provider, local list, etc.)</li>
<li>Module block - reusable set of resources that can be leveraged across multiple terraform configs</li>
<li>Output block - Resources managed by Terraform each export attributes whose values can be used elsewhere in configuration. Output values are a way to expose some of that information to the user of your module. (For example, the IP address of an EC2 instance).</li>
<li>Variable block - Defines variables to be used in the Terraform config. Input variables let you customize aspects of Terraform modules without altering the module's own source code. This functionality allows you to share modules across different Terraform configs, making your module composable and reusable. Variable names have to be unique 😉<ul>
<li>order of precedence: defaults &lt; env vars &lt; terraform.tfvars file &lt; terraform.tfvars.json file &lt; .auto.tfvars &lt; command line (-var &amp; -var-file)</li>
</ul>
</li>
<li>Locals block - A local value assigns a name to an <a target="_blank" href="https://developer.hashicorp.com/terraform/language/expressions">expression</a>, so you can use the name multiple times within a module instead of repeating the expression.</li>
</ul>
</li>
</ul>
<h3 id="heading-install-and-version-terraform-providers">Install and version Terraform providers</h3>
<p>Terraform relies on <a target="_blank" href="https://registry.terraform.io/browse/providers">Providers</a> to allow Terraform to interact with remote systems (CSPs, SaaSs, APIs, and so on).</p>
<p>Some providers require additional config info (endpoints, regions used, etc..) to work. </p>
<p>You must declare which providers are needed in your Terraform configs. They go in the root module (child modules get their provider configs from the root module) in a required_providers block (see <a target="_blank" href="https://developer.hashicorp.com/terraform/language/providers/requirements">Requiring Providers</a> for more details)</p>
<p>Use the alias meta-argument to define multiple configs for the same provider (that is, to support multiple regions for a cloud platform)</p>
<p>The required_providers block defines all of the providers needed by the current module</p>
<p>To ensure that multiple users run the same Terraform config (with the same provider versions), you:</p>
<ul>
<li>Specify provider version constraints</li>
<li>Use the <a target="_blank" href="https://developer.hashicorp.com/terraform/language/files/dependency-lock">dependency lock file</a>:<ul>
<li>named .terraform.lock.hcl</li>
<li>updates when you run the <code>terraform init</code> command </li>
<li>should be included in version control repo!</li>
<li>if a provider is in the lock file, TF will always use that version unless you <code>terraform init -upgrade</code></li>
</ul>
</li>
<li>If it does upgrade, review the changes 😉:</li>
</ul>
<p><img src="https://mistwire.com/wp-content/uploads/2023/07/CleanShot-2023-07-10-at-14.49.18-1024x684.png" alt="Image" width="1024" height="684" loading="lazy">
<em>Example of a lockfile change of the AWS provider version</em></p>
<h3 id="heading-describe-plugin-based-architecture">Describe plugin-based architecture</h3>
<p>Terraform is split into 2 main parts:</p>
<ul>
<li>Terraform <strong>Core</strong>: a statically compiled binary (written in Go). When you type <code>terraform</code> in the CLI you are invoking the core functionality:<ul>
<li>reading and interpolating config files and modules</li>
<li>state mgmt</li>
<li>building a resource graph</li>
<li>plan execution</li>
<li>talking to plugins</li>
</ul>
</li>
</ul>
<p><a target="_blank" href="https://developer.hashicorp.com/terraform/plugin/how-terraform-works">Terraform <strong>Plugins</strong></a>: executable binaries invoked by Terraform Core over RPC.</p>
<ul>
<li>Each plugin is geared towards a specific service (like AWS).</li>
<li>All Providers and Provisioners used in Terraform configs are plugins</li>
<li>Provider plugin are responsible for:<ul>
<li>Initializing libraries for making API calls</li>
<li>Authentication with the infra provider</li>
<li>Defining resource maps to specific services</li>
</ul>
</li>
</ul>
<h3 id="heading-write-a-terraform-configuration-using-multiple-providers">Write a Terraform configuration using multiple providers</h3>
<p>Sometimes you'll need to reference the same provider for multiple reasons. In the below example we're using multiple regions within AWS, therefore we need a mechanism for distinguishing between the two providers. Enter the <code>alias</code> argument. With it you can assign resources to specific environments:</p>
<p><img src="https://mistwire.com/wp-content/uploads/2023/07/image.png" alt="Image" width="727" height="345" loading="lazy">
<em>Example of using multiple providers with the alias argument</em></p>
<pre><code class="lang-hcl">provider "aws" {
  profile = "prod"
  region  = "us-east-1"
 }

 provider "aws" {
  profile = "prod"
  region  = "us-west-2"
  alias   = "west"
 }
</code></pre>
<h3 id="heading-describe-how-terraform-finds-and-fetches-providers">Describe how Terraform finds and fetches providers</h3>
<p>Required providers are specified in the (surprise!) <strong><code>required_providers</code></strong> block nested inside the top-level <strong><code>terraform</code></strong> block:</p>
<p><img src="https://mistwire.com/wp-content/uploads/2023/07/image-3.png" alt="Image" width="791" height="211" loading="lazy">
<em>Example usage of the required providers block</em></p>
<pre><code class="lang-hcl">terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "5.8.0"
    }
  }
}
</code></pre>
<p>The <a target="_blank" href="https://developer.hashicorp.com/terraform/language/providers/requirements#source-addresses">source</a> value specifies the primary location where Terraform can download it (see link for specifics around syntax).</p>
<p>Use the commands <strong><code>terraform version</code></strong> (what version of core and what plugins are installed) and <strong><code>terraform providers</code></strong> (what providers are required by the configuration) to get more information about config requirements:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-185.png" alt="Image" width="600" height="400" loading="lazy">
<em>Output of the terraform version command</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-184.png" alt="Image" width="600" height="400" loading="lazy">
<em>Output of the terraform providers command</em></p>
<h2 id="heading-use-terraform-outside-the-core-workflow">Use Terraform Outside the Core Workflow</h2>
<p>Now that we've learned the basics of Terraform, Domain 4 gets into more workflows that you will see very often in the real world. </p>
<p>This domain answers questions like "If you built an environment without using Terraform, how would you move those resources over to a Terraform managed state?" and "What happens when everything breaks?" </p>
<h3 id="heading-describe-when-to-use-terraform-importhttpsdeveloperhashicorpcomterraformclicommandsimport-to-import-existing-infra-into-your-terraform-state-note-can-only-import-1-resource-at-a-time">Describe when to use <a target="_blank" href="https://developer.hashicorp.com/terraform/cli/commands/import">terraform import</a> to import existing infra into your Terraform state (note: can only import 1 resource at a time)</h3>
<ol>
<li>Write a resource block for it in your configuration<ul>
<li>resource name must be unique (like any regular resource block)</li>
</ul>
</li>
<li>Run terraform import with the syntax <code>terraform import [options] &lt;address id&gt;</code><ul>
<li>address id is the resource id of the provider</li>
<li>each remote object must be bound to only one resource block in the terraform config</li>
</ul>
</li>
<li>Run terraform plan &amp; review how the config compares to the imported resource</li>
<li>Make adjustments to the config to reach desired state</li>
</ol>
<h3 id="heading-use-terraform-state-to-view-terraform-state">Use terraform state to view Terraform state</h3>
<ul>
<li><strong>Use this instead of modifying the statefile directly</strong><ul>
<li>Never modify the statefile directly ever ever ever EVER</li>
</ul>
</li>
<li>Used for advanced state mgmt</li>
<li>Works with both local and remote statefiles</li>
<li>Creates a backup that cannot be disabled</li>
<li><code>terraform state -help</code> to get started</li>
<li><code>terraform state list</code> to get a less-cluttered view of resources under mgmt</li>
<li><code>terraform state list [resource]</code> to get granular resource data (very handy):</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/image-74.png" alt="Image" width="600" height="400" loading="lazy">
<em>Output of the terraform state command</em></p>
<h3 id="heading-describe-when-to-enable-verbose-logging-and-what-the-outcomevalue-is">Describe when to enable verbose logging and what the outcome/value is</h3>
<p>You can generate logs for Terraform Core and Providers separately.</p>
<ul>
<li>log levels = <code>TRACE</code> &gt; <code>DEBUG</code> &gt; <code>INFO</code> &gt; <code>WARN</code> &gt; <code>ERROR</code></li>
<li>To enable core logging, set env var <code>TF_LOG_CORE</code>=(log level)<ul>
<li>linux example <code>export TF_LOG_CORE=TRACE</code></li>
<li>powershell example <code>$env:TF_LOG_CORE=TRACE</code></li>
</ul>
</li>
<li>To enable provider logging, set env var <code>TF_LOG_PROVIDER</code>=(log level)<ul>
<li>linux example <code>export TF_LOG_PROVIDER=TRACE</code></li>
<li>powershell example <code>$env:TF_LOG_PROVIDER=TRACE</code></li>
</ul>
</li>
<li>To persist logs, set env var:<ul>
<li>linux <code>export TF_LOG_PATH=logs.txt</code></li>
<li>powershell <code>$env:TF_LOG_PATH=logs.txt</code></li>
</ul>
</li>
<li>To undo env var, reset values to null:<ul>
<li><code>export TF_LOG_CORE=""</code></li>
<li><code>export TF_LOG_PROVIDER=""</code></li>
<li><code>export TF_LOG_PATH=""</code></li>
</ul>
</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/CleanShot-2023-08-01-at-09.29.46-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Output of the terraform refresh command</em></p>
<h2 id="heading-interact-with-terraform-modules">Interact with Terraform Modules</h2>
<p>Domain 5 expands your knowledge of Terraform by introducing the concept of modules. A lot of people are using Terraform and they are creating modules to help make it easier for everyone to provision resources. Don't recreate the wheel, use modules!</p>
<h3 id="heading-contrast-amp-use-different-module-source-options-including-the-public-terraform-registry">Contrast &amp; use different module source options including the public Terraform Registry</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/CleanShot-2023-08-02-at-15.07.13.png" alt="Image" width="600" height="400" loading="lazy">
<em>Browse module section of the Terraform registry</em></p>
<p>A Terraform module is just a set of Terraform configuration files inside a folder – nothing to be afraid of. 😊</p>
<p>Every Terraform config has at least one module, the <em>root module</em>. If you have <em>child modules</em>, the root module can make calls to them. </p>
<p>Child modules are just files outside of the working directory. They can be a folder on your system, up in a GitHub repo, S3 bucket, and so on. Check out all the <a target="_blank" href="https://developer.hashicorp.com/terraform/language/modules/sources">options here</a></p>
<p>The <code>source</code> arg in the module block tells Terraform where to find the source code for the module. The xyntax for registry modules required <code>source</code> argument is <code>&lt;namespace&gt;/&lt;name&gt;/&lt;provider&gt;</code></p>
<ul>
<li>example:<code>terraform-aws-modules/vpc/aws</code> </li>
</ul>
<p>Use the <a target="_blank" href="https://registry.terraform.io/">Terraform Registry</a> to find and use 'public' modules</p>
<ul>
<li><code>terraform init</code> will download and cache modules ref'ed in a config</li>
<li>By default, only verified modules are shown in the Terraform registry. You can change that with filters.</li>
<li>Modules in the registry are versioned using the <code>version</code> argument</li>
<li>Private Registry Module Sources syntax = <code>&lt;hostname&gt;/&lt;namespace&gt;/&lt;name&gt;/&lt;provider&gt;</code>    </li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/CleanShot-2023-08-02-at-15.40.36.png" alt="Image" width="600" height="400" loading="lazy">
<em>Example of using a Terraform module</em></p>
<pre><code class="lang-hcl">module "ec2_instance" {
  source  = "terraform-aws-modules/ec2-instance/aws"
  version = "3.5.0"
  count   = 2

  name = "my-ec2-cluster"

  instance_type          = "t2.micro"
  vpc_security_group_ids = ["sg-12345678"]
  subnet_id              = module.vpc.public_subnets[0]

  tags = {
    Terraform   = "true"
    Environment = "dev"
  }
}
</code></pre>
<h3 id="heading-interact-with-module-inputs-and-outputs">Interact with module inputs and outputs</h3>
<p>Input variables let you customize aspects of Terraform modules without altering the module's own source code</p>
<ul>
<li>Each input variable accepted by a module must be declared using a <code>variable</code> block. As always, variable names must be unique w/in the module</li>
<li>If a variable doesn't have a default value assigned to it, it's required</li>
</ul>
<p>Output blocks provide back information from the resources generated by the module.</p>
<ul>
<li>to use output information in the 'calling module' (normally the root module), use interpolation syntax of the output block in the 'sending module'</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/image-75.png" alt="Image" width="600" height="400" loading="lazy">
<em>child module output being called by calling module</em></p>
<h3 id="heading-describe-variable-scope-win-moduleschild-modules">Describe variable scope w/in modules/child modules</h3>
<p>Deciding what is in and out of scope for a module can be challenging. Don't overuse a module by putting too many resources into one.</p>
<p>A good rule is to limit it to one resource type offered by a provider.</p>
<p>You can group infrastructure that is always deployed together. You can also group resources with the same set of privledges where possible to minimize blast radius.</p>
<p>Try to seperate long-running resources from short-lived resources: don't put your production database in the same module as your dev lambdas. 😂</p>
<p>Variables are declared in the child module. If you want information from the child module, you must create an output block for that info in the child module &amp; then call it using interpolation syntax. You cannot access child module resource information otherwise.</p>
<p>If you are building a module, it is good practice to create output blocks for all resource info availalbe, even if you don't see an immediate use for it:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/CleanShot-2023-08-07-at-14.19.52.png" alt="Image" width="600" height="400" loading="lazy">
<em>Example layout of root and child modules</em></p>
<h3 id="heading-set-module-version">Set module version</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/CleanShot-2023-08-07-at-16.23.19-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Where to check module versions</em></p>
<p>Much like other blocks, modules can be versioned. If you change the module version, you will need to run <code>terraform init</code> again.</p>
<ul>
<li>It is recommended to version constrain your module usage to prevent unexpected changes from occurring </li>
<li>Versioning is supported for the public Terraform Registry &amp; TFCs private module registry</li>
<li>Local file modules do not support versioning</li>
</ul>
<h2 id="heading-use-the-core-terraform-workflow">Use the Core Terraform Workflow</h2>
<p>Domain 6 gets much more granular into the core Terraform workflow. In the real world, this is the process that you will do again and again and again (and again!), so it's important that you understand all of the little details. </p>
<p>Especially pay attention to how the processes interact with each other and exactly WHAT is happening when you use a particular command. </p>
<h3 id="heading-describe-terraform-workflowhttpsdeveloperhashicorpcomterraformintrocore-workflow-write-gt-plan-gt-create">Describe <a target="_blank" href="https://developer.hashicorp.com/terraform/intro/core-workflow">Terraform workflow</a> (write -&gt; plan -&gt; create)</h3>
<p><strong>Write</strong>:</p>
<ul>
<li>Author your IaC in your editor of choice (like VS Code😉)</li>
<li>Written in Hashicorp Configuration Language (HCL)</li>
<li>Store your work in VCS (like Git + GitHub)</li>
</ul>
<p><strong>Plan</strong>:</p>
<ul>
<li>Initialize the working directory with <code>terraform init</code></li>
<li>Preview changes before you apply with <code>terraform plan</code></li>
<li>Do this repeatedly as you are building your config to fix errors and have a tight feedback loop</li>
<li>Can output and save for later with <code>terraform plan -out [plan name]</code></li>
</ul>
<p><strong>Create</strong>:</p>
<ul>
<li><code>terraform apply</code> to deploy the infra that you've written </li>
<li>Can apply a previous saved plan with <code>terraform apply [plan name]</code></li>
<li>Recommended: push your config to remote repo for redundancy/safekeeping</li>
</ul>
<h3 id="heading-initialize-a-terraform-directory-terraform-init">Initialize a Terraform directory (<code>terraform init</code>)</h3>
<ul>
<li><code>init</code>ializes (get it?) a working directory that contains .tf files</li>
<li>This is the first command that should be run after writing/cloning a new config. You can find <a target="_blank" href="https://developer.hashicorp.com/terraform/cli/commands/init#general-options">Command Options here</a></li>
<li>Root config directory is checked for backend config data<ul>
<li>to update backend, use <code>-reconfigure</code> or <code>-migrate-state</code></li>
</ul>
</li>
<li>Sources and downloads the providers and modules used in the config</li>
<li>Creates a lock file .terraform.lock.hcl to pin provider verions </li>
<li>Reasons to re-initialize a config:<ul>
<li>adding a new provider</li>
<li>upgrading/downgrading the version of a provider with <code>terraform init -upgrade</code></li>
<li>adding a new module</li>
<li>upgrading/downgrading the version of a module with <code>terraform init -upgrade</code></li>
<li>changing the location of the backend (statefile)</li>
</ul>
</li>
</ul>
<h3 id="heading-validate-a-terraform-config-terraform-validate">Validate a Terraform config (<code>terraform validate</code>)</h3>
<p>Know the limitations! It DOES:</p>
<ul>
<li>Validate local configuration files</li>
<li>Check syntactic validity</li>
<li>Check internal consistency</li>
<li>Check correctness of attribute names, value types, and expected argument types</li>
</ul>
<p>It DOES NOT:</p>
<ul>
<li>Access remote services (remote state, provider APIs, upstream dependencies, etc...)</li>
<li>Check with backend provider to insure external consistency</li>
</ul>
<h3 id="heading-generate-amp-review-an-execution-plan-terraform-planhttpsdeveloperhashicorpcomterraformclicommandsplan">Generate &amp; review an execution plan (<code>[terraform plan](https://developer.hashicorp.com/terraform/cli/commands/plan)</code>)</h3>
<p>This creates an execution plan to preview changes Terraform wants to make to your env.</p>
<p>Here are the steps:</p>
<ul>
<li>Reads the current state (if any) of remote objects under mgmt to make sure state is up to date</li>
<li>Compares current config to prior state &amp; notes changes </li>
<li>Proposes changes that will make the remote env match the config</li>
</ul>
<p>You can use <code>terraform plan -help</code> to see all the options, and <code>terraform plan -out [plan name]</code> to create a file to be reviewed/used later.</p>
<ul>
<li>Use <code>terraform plan -refresh-only</code> to detect drift between your config and the actual environment</li>
<li>Doesn't change the env, you can run multiple times</li>
<li><span>+</span> resource will be created</li>
<li><span>-</span> resource will be destroyed</li>
<li><span>~</span> resource will update in place</li>
<li><span>-/+</span> resource will be destroyed &amp; recreated</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/image-127.png" alt="Image" width="600" height="400" loading="lazy">
<em>creating 30 new resources</em></p>
<h3 id="heading-execute-changes-to-infra-terraform-apply">Execute changes to infra (<code>terraform apply</code>)</h3>
<ul>
<li>Provisions, changes, and destroys (recreates) resources in an environment</li>
<li>Executes the proposed actions of a <code>terraform plan</code></li>
<li>Will only affect the resources under mgmt</li>
<li>Running it without a plan causes it to automatically run a plan, then execute it</li>
<li>You can pass in a previously generated plan with <code>terraform apply -out=[plan name]</code></li>
<li><code>terraform apply -auto-approve</code> bypasses the post-plan manual 'yes' check</li>
<li><code>terrafrom apply [plan name]</code> to execute a previously saved plan</li>
</ul>
<h3 id="heading-destroy-terraform-managed-infra-terraform-destroy">Destroy Terraform managed infra (<code>terraform destroy</code>)</h3>
<ul>
<li>You'll never guess what this command does 😂</li>
<li>Destroys all infrastructure <strong>under management</strong> by Terraform (nothing else)</li>
<li>Automated cleanup is better because you <em>always</em> forget to delete something when you build it manually</li>
<li>Be careful with <code>terraform destroy -auto-approve</code></li>
<li><code>terraform apply -destroy</code> also does the same thing</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/CleanShot-2023-08-16-at-13.07.22.png" alt="Image" width="600" height="400" loading="lazy">
<em>Are you really REALLY sure?!?!</em></p>
<h3 id="heading-apply-formatting-amp-style-adjustments-to-a-config-terraform-fmt">Apply formatting &amp; style adjustments to a config (terraform fmt)</h3>
<ul>
<li>Formats and styles your code for better readability</li>
<li>Does NOT fix your errors</li>
<li>Lists which files it updates</li>
<li>Very useful 😁</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/CleanShot-2023-08-16-at-14.19.16.png" alt="Image" width="600" height="400" loading="lazy">
<em>How terraform fmt works</em></p>
<h2 id="heading-implement-and-maintain-state">Implement and Maintain State</h2>
<p>State is THE MOST IMPORTANT THING in any given Terraform managed environment. Without your statefile, you're going to have a bad day. </p>
<p>Domain 7 walks through the different types of state, how to move it around, and how to protect it.</p>
<h3 id="heading-describe-default-local-backendhttpsdeveloperhashicorpcomterraformlanguagev11xsettingsbackendslocal">Describe default <a target="_blank" href="https://developer.hashicorp.com/terraform/language/v1.1.x/settings/backends/local"><code>local</code> backend</a></h3>
<p>Terraform stores and references the <em>state</em> of all terraform managed environments.</p>
<p>The configuration file is what we want the environment to look like, the <em>statefile</em> one to one mapping of the resources provisioned to the config file.</p>
<ul>
<li>The <code>local</code> backend:<ul>
<li>stores state on the local filesystem</li>
<li>locks that state using system APIs</li>
<li>performs operations locally</li>
</ul>
</li>
</ul>
<p>Default statefile is named <code>terraform.tfstate</code> and lives in the working directory. If you don't specify a backend, terraform uses the default local backend.</p>
<p>You CAN explicitly specify local backend if you want to have greater control over statefile location, future state migration considerations, and so on.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/CleanShot-2023-08-17-at-11.01.06.png" alt="Image" width="600" height="400" loading="lazy">
<em>Explicitly defined default local state</em></p>
<pre><code class="lang-hcl">terraform {
  backend "local" {
    path = "terraform.tfstate"
  }
}
</code></pre>
<h3 id="heading-describe-state-lockinghttpsdeveloperhashicorpcomterraformlanguagestatelocking">Describe <a target="_blank" href="https://developer.hashicorp.com/terraform/language/state/locking">state locking</a></h3>
<p>State data (the statefile) is the source of truth for Terraform and therefore is <em>very</em> important. As such it needs to be protected from several file corruption and data loss scenarios.</p>
<p>Backends are responsible for storing state and providing an API for state locking. State locking is <em>optional</em> but highly recommended in multi-user environments.</p>
<ul>
<li>You can manually retrieve remote state with <code>terraform state pull</code></li>
<li>You can manually write state with <code>terraform state push</code>.... but don't ever ever <em>ever</em> do this without proper supervision and guidance and backups.  </li>
</ul>
<p>State locking prevents multiple users from making changes to a managed environment simultaneously (potentially corrupting state). Locking happens automatically on all potential state writing operations.</p>
<ul>
<li>You <em>can</em> ignore state locking with <code>-force</code> but don't 😅</li>
<li>You can also <code>terraform force-unlock [LOCK_ID]</code> if unlocking fails, but this is a break glass emergency use case</li>
</ul>
<p>Not all backends support locking! Local, TFC, AWS S3 (with some tweaks), and several others do (<a target="_blank" href="https://developer.hashicorp.com/terraform/language/settings/backends/configuration">see docs for which ones do/don't</a>).</p>
<h3 id="heading-handle-backend-and-cloud-integration-auth-methods">Handle backend and cloud integration auth methods</h3>
<p>When you have state backend stored somewhere other than <code>local</code>, you'll need to have some form of authentication - this is very sensitive information that needs to be protected!</p>
<p>Each backend has it's own auth mechanism (for example, access keys for AWS).</p>
<p>Some other things to keep in mind:</p>
<ul>
<li>Arguments used in the block body are specific to the chosen back end type</li>
<li>If you want to change backend location you'll need to start with <code>terraform init -reconfigure</code> </li>
<li><code>terraform login [hostname]</code> is used to obtain and save an API token from TFC, TFE, or other host that offers Terraform services</li>
<li>If you don't explicitly provide a hostname, cmd defaults to TFC at <code>app.terraform.io</code></li>
<li>By default Terraform pulls &amp; saves API token in plain text to <code>credentials.tfrc.json</code> - this can be modified for other secrets mgmt systems</li>
<li>To configure a backend block, add a nested <code>backend</code> into the top-level <code>terraform</code> block:</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/CleanShot-2023-08-19-at-15.15.46.png" alt="Image" width="600" height="400" loading="lazy">
<em>Defined remote state</em></p>
<pre><code class="lang-hcl">terraform {
  backend "remote" {
    organization = "example_corp"

    workspaces {
      name = "my-app-prod"
    }
  }
}
</code></pre>
<h3 id="heading-differentiate-remote-state-backend-options">Differentiate remote state backend options</h3>
<p>Terraform has a built-in selection of backends and the configured backend must be available in the version of Terraform you are using. This is why it's important to version everything in your configs!</p>
<p>You don't need to configure a backend when using TFC because it auto-manages state in the workspaces assoc. to the config. If your config includes a <code>cloud</code> block it cannot have a <code>backend</code> block.</p>
<h3 id="heading-manage-resource-drift-and-terraform-state">Manage resource drift and Terraform state</h3>
<p><code>terraform plan -refresh-only</code></p>
<ul>
<li>Creates a plan that updates state to match changes made outside of terraform</li>
<li>Good for drift detection</li>
<li>Does not propose any actions to undo changes</li>
</ul>
<p><code>terraform apply -refresh-only</code></p>
<ul>
<li>Updates the statefile to accept the changes made manually in the environment</li>
<li>Does <strong>NOT</strong> change the config file! If you don't update the config with the new changes, then the next <code>terraform apply</code> of that config will revert environment back to the original state</li>
</ul>
<h3 id="heading-describe-backend-blockhttpsdeveloperhashicorpcomterraformlanguagesettingsbackendsconfiguration-and-cloud-integrationhttpsdeveloperhashicorpcomterraformlanguagesettingsterraform-cloud-configuration">Describe <a target="_blank" href="https://developer.hashicorp.com/terraform/language/settings/backends/configuration"><code>backend</code> block</a> and <a target="_blank" href="https://developer.hashicorp.com/terraform/language/settings/terraform-cloud">cloud integration</a> configuration</h3>
<ul>
<li>Defines where terraform stores the statefile for a working directory</li>
<li>The statefile is the source of truth for resources under terraform management and therefore is extremely important (I might have said this before 😉)</li>
<li>By default terraform uses <code>local</code> which stores statefile on local disk</li>
<li><code>remote</code> is the other backend type which covers everything else (TFC, S3, and so on...) </li>
<li>Limitations:<ul>
<li>1 config, 1 <code>backend</code> block</li>
<li>Cannot use interpolation (so we can't use variables)</li>
</ul>
</li>
<li>Partial configurations<ul>
<li>Omit certain arguments to be supplied at runtime</li>
<li>Useful for automation scripting &amp; CI scenarios </li>
</ul>
</li>
<li>When using Terraform Cloud, you don't need to configure a backend because TFC manages state in the workspace associated with the config</li>
<li><code>cloud</code> block is nested in <code>terraform</code> block</li>
<li>Limitations:<ul>
<li>1 config, 1 <code>cloud</code> block</li>
<li>If the config includes a <code>cloud</code> block it cannot also have a <code>backend</code> block</li>
<li>Cannot use interpolation</li>
<li>See <a target="_blank" href="https://developer.hashicorp.com/terraform/cli/cloud/settings">cloud settings</a> for more information</li>
</ul>
</li>
</ul>
<h3 id="heading-understand-secret-management-in-state-files">Understand secret management in state files</h3>
<ul>
<li>The statefile can contain a lot of secrets!!!<ul>
<li>resource IDs</li>
<li>DB username/passwords</li>
<li>private keys</li>
<li>Andrew Brown's home phone number</li>
</ul>
</li>
</ul>
<p>So you should treat it like you would your company passwords. By default local state is stored in plaintext as JSON. You can mark sensitive information in your config files as such with the <code>sensitive = true</code> argument.</p>
<ul>
<li>redacted from the CLI output, but still is in the statefile as plaintext (that's why it's important to lock down the statefile)</li>
</ul>
<p>Use a backend that encrypts and protects your statefile from unauthorized access.</p>
<ul>
<li>TFC encrypts state at rest &amp; in transit</li>
<li>Turn on encryption if you are using S3 (&amp; use state locking!)</li>
<li>Terraform does not persist state to local disk when remote state is being used</li>
</ul>
<h2 id="heading-read-generate-and-modify-configuration">Read, Generate, and Modify Configuration</h2>
<p>Domain 8 teaches you more about the config files and how to leverage HCL fully. Like any programming language, HCL can be refactored for ease of understanding and keeping your code DRY (Don't Repeat Yourself). </p>
<h3 id="heading-demonstrate-use-of-variableshttpsdeveloperhashicorpcomterraformlanguagevaluesvariables-and-outputshttpsdeveloperhashicorpcomterraformlanguagevaluesoutputs">Demonstrate use of <a target="_blank" href="https://developer.hashicorp.com/terraform/language/values/variables">variables</a> and <a target="_blank" href="https://developer.hashicorp.com/terraform/language/values/outputs">outputs</a></h3>
<ul>
<li>Programming analogies:<ul>
<li><a target="_blank" href="https://developer.hashicorp.com/terraform/language/values/variables">Input variables</a> = function arguments</li>
<li><a target="_blank" href="https://developer.hashicorp.com/terraform/language/values/outputs">Output values</a>   = function return values </li>
<li><a target="_blank" href="https://developer.hashicorp.com/terraform/language/values/locals">Local values</a>    = function local variables</li>
</ul>
</li>
<li>Each input variable accepted by a module must be declared using a <code>variable</code> block:
<img src="https://www.freecodecamp.org/news/content/images/2023/08/Picture1.png" alt="Picture1" width="600" height="400" loading="lazy">
<img src="https://www.freecodecamp.org/news/content/images/2023/08/Picture2.png" alt="Picture2" width="600" height="400" loading="lazy">
<img src="https://www.freecodecamp.org/news/content/images/2023/08/Picture3.png" alt="Picture3" width="600" height="400" loading="lazy"><ul>
<li>Variable names can be any valid name <em>except</em> <code>source</code>, <code>version</code>, <code>providers</code>, <code>count</code>, <code>for_each</code>, <code>lifecycle</code>, <code>depends_on</code>, or <code>locals</code></li>
<li>Input order of precedence: defaults &lt; env vars &lt; terraform.tfvars file &lt; terraform.tfvars.json file &lt; .auto.tfvars &lt; command line (-var &amp; -var-file)<ul>
<li>side note: terraform.tfvars is the most popular way for manipulating variables used out in the wild </li>
</ul>
</li>
</ul>
</li>
<li>By the same token, each output value must be declared using an <code>output</code> block<ul>
<li>In the root module, the output is displayed to the user</li>
<li>In a child module, the output can be used to access a value by the root module (<code>module.&lt;MODULE NAME&gt;.&lt;OUTPUT NAME&gt;</code>) </li>
<li>Outputs only render on <code>terraform apply</code>, not <code>terraform plan</code></li>
<li><code>terraform output</code> will display your outputs without running an <code>apply</code></li>
<li><code>terraform output &lt;NAME&gt;</code> to pull a specific value</li>
<li>marking an output value as sensitive suppresses the value in the CLI during a <code>terraform apply</code>, but NOT in the statefile or a <code>terraform output &lt;NAME&gt;</code></li>
<li>if you mark an variable as <code>sensitive</code> but NOT an output for that variable, it will error out
<img src="https://www.freecodecamp.org/news/content/images/2023/08/CleanShot-2023-08-29-at-19.44.57.png" alt="CleanShot-2023-08-29-at-19.44.57" width="600" height="400" loading="lazy"></li>
</ul>
</li>
</ul>
<h3 id="heading-describe-secure-secret-injection-best-practices">Describe secure secret injection best practices</h3>
<ul>
<li>Mark sensitive values as <code>sensitive</code> </li>
<li>Never put actual secret values into a .tf file as they would be checked into source control<ul>
<li>Passwords, API tokens, access tokens, etc... must be obfuscated</li>
</ul>
</li>
<li>Never check your statefile to source control (same reason as above)</li>
<li>Use environment variables by setting <code>TF_VAR_&lt;NAME&gt;</code>
<img src="https://www.freecodecamp.org/news/content/images/2023/08/CleanShot-2023-08-29-at-20.04.06.png" alt="CleanShot-2023-08-29-at-20.04.06" width="600" height="400" loading="lazy"></li>
<li>If you are using Terraform Cloud use the environment variables for the appropriate workspace:
<img src="https://www.freecodecamp.org/news/content/images/2023/08/CleanShot-2023-08-29-at-20.08.55.png" alt="CleanShot-2023-08-29-at-20.08.55" width="600" height="400" loading="lazy">  </li>
<li>Use a secrets management solution like Vault<ul>
<li>Run through <a target="_blank" href="https://developer.hashicorp.com/terraform/tutorials/secrets/secrets-vault">this tutorial</a> to get a feel for injecting secrets into Terrafrom using Vault</li>
</ul>
</li>
</ul>
<h3 id="heading-understand-the-use-of-collection-and-structural-typeshttpsdeveloperhashicorpcomterraformlanguagev11xexpressionstype-constraintscomplex-types">Understand the use of <a target="_blank" href="https://developer.hashicorp.com/terraform/language/v1.1.x/expressions/type-constraints#complex-types">collection and structural types</a></h3>
<ul>
<li>Colletion types are a collection of <em>one type</em> of grouping<ul>
<li>All elements of a collection must be of the same type <code>list(string)</code> is different from <code>list(number)</code> </li>
<li>3 kinds of collection types:<ul>
<li><code>list(...)</code> sequence of ordered elements (starting at 0)
<img src="https://www.freecodecamp.org/news/content/images/2023/08/CleanShot-2023-08-30-at-15.43.39.png" alt="CleanShot-2023-08-30-at-15.43.39" width="600" height="400" loading="lazy"></li>
<li><code>map(...)</code> sequence of key/value pairs separated by a comma. Can confusingly use both = or : as the k/v separator
<img src="https://www.freecodecamp.org/news/content/images/2023/08/CleanShot-2023-08-30-at-15.42.37.png" alt="CleanShot-2023-08-30-at-15.42.37" width="600" height="400" loading="lazy"></li>
<li><code>set(...)</code> a collection of unique, unordered, unrepeating vaules</li>
</ul>
</li>
</ul>
</li>
<li>Structural types allow <em>multiple types</em> of elements to be grouped together<ul>
<li>2 kinds of structural types:<ul>
<li><code>object({&lt;KEY&gt; = &lt;TYPE&gt;, ...})</code> named attributes where each one has it's own type</li>
<li><code>tuple([&lt;TYPE&gt;, &lt;TYPE&gt;, ...])</code> sequence of ordered elements (starting at 0) 
<img src="https://www.freecodecamp.org/news/content/images/2023/08/CleanShot-2023-08-30-at-16.18.32.png" alt="CleanShot-2023-08-30-at-16.18.32" width="600" height="400" loading="lazy"></li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="heading-create-and-differentiate-resource-and-data-configuration">Create and differentiate <code>resource</code> and <code>data</code> configuration</h3>
<ul>
<li>Providers can access both Resources and Data Sources (examples from the AWS provider:
<img src="https://www.freecodecamp.org/news/content/images/2023/08/CleanShot-2023-08-31-at-14.29.32.png" alt="CleanShot-2023-08-31-at-14.29.32" width="600" height="400" loading="lazy"></li>
<li>You can query resources you've created in Terraform (via exporting Attribute References):
<img src="https://www.freecodecamp.org/news/content/images/2023/08/CleanShot-2023-08-31-at-14.31.19.png" alt="CleanShot-2023-08-31-at-14.31.19" width="600" height="400" loading="lazy"> </li>
<li>And also do data lookups for existing resources that haven't been made with Terraform
<img src="https://www.freecodecamp.org/news/content/images/2023/08/CleanShot-2023-08-31-at-14.33.19.png" alt="CleanShot-2023-08-31-at-14.33.19" width="600" height="400" loading="lazy"></li>
<li>Do the <a target="_blank" href="https://developer.hashicorp.com/terraform/tutorials/configuration-language/data-sources?variants=terraform-workflow%3Atfc">Query data sources</a> tutorial</li>
</ul>
<h3 id="heading-use-resource-addressing-and-resource-parameters-to-connect-resources-together">Use resource addressing and resource parameters to connect resources together</h3>
<ul>
<li>Resource path syntax<ul>
<li><code>[module path][resource info]</code></li>
</ul>
</li>
<li>Module path syntax<ul>
<li><code>module.&lt;MODULE NAME&gt;[optional module index]</code></li>
</ul>
</li>
<li>Resource spec syntax<ul>
<li><code>resource_type.user_defined_name[optional index]</code></li>
</ul>
</li>
<li>Types of named values:<ul>
<li>Resources <code>&lt;RESOURCE TYPE&gt;.&lt;NAME&gt;</code><ul>
<li>if <code>count</code> is used, ref is a list accessed with [N]</li>
<li>if <code>for_each</code> is used, ref is a map accessed with ["key"]</li>
</ul>
</li>
<li>Input Variables <code>var.&lt;NAME&gt;</code></li>
<li>Locals <code>local.&lt;NAME&gt;</code></li>
<li>Child module outputs <code>module.&lt;MODULE NAME&gt;</code> <ul>
<li>same <code>count</code> and <code>for_each</code> rules as resources</li>
</ul>
</li>
<li>Data blocks <code>data.&lt;DATA TYPE&gt;.&lt;NAME&gt;</code><ul>
<li>same <code>count</code> and <code>for_each</code> rules as resources</li>
</ul>
</li>
<li>Filesystem/workspace info<ul>
<li><code>path.module</code> location of expression (don't use in write operations)</li>
<li><code>path.root</code> root module location</li>
<li><code>terraform.workspace</code> currently selected workspace</li>
</ul>
</li>
<li>Block 'local' values <ul>
<li><code>count.index</code></li>
<li><code>each.key</code>/<code>each.value</code></li>
<li><code>self</code> </li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="heading-use-hcl-and-terraform-functions-to-write-configuration">Use HCL and Terraform functions to write configuration</h3>
<ul>
<li>Terraform has a number of built-in functions for manipulating values, strings etc...
<img src="https://www.freecodecamp.org/news/content/images/2023/08/CleanShot-2023-08-31-at-15.21.35.png" alt="CleanShot-2023-08-31-at-15.21.35" width="600" height="400" loading="lazy"><ul>
<li><a target="_blank" href="https://developer.hashicorp.com/terraform/language/functions">Review them here</a></li>
<li>Not necessary for the exam (I don't think?) but they will make your life easier when actually using HCL</li>
</ul>
</li>
<li>Do the <a target="_blank" href="https://developer.hashicorp.com/terraform/tutorials/configuration-language/functions">dynamic operations with functions</a> and <a target="_blank" href="https://developer.hashicorp.com/terraform/tutorials/configuration-language/expressions">create dynamic expressions</a> tutorials </li>
</ul>
<h3 id="heading-describe-built-in-dependency-management-order-of-execution-based">Describe built-in dependency management (order of execution based)</h3>
<ul>
<li>Terraform generates a dependency graph for determining which resources need to be built 1st, 2nd, 3rd, etc...<ul>
<li><code>depends_on</code> can be used to alter dependencies</li>
<li>The <code>lifecycle</code> block along with <code>create_before_destroy</code> and <code>prevent_destroy</code> are additional tools in the lifecycle toolbelt</li>
</ul>
</li>
<li>Items with no dependencies are built in parallel to speed up the provisioning process<ul>
<li>By default, up to 10 concurrent operations can be run at the same time</li>
<li>This can be changed with the <code>-parallelism</code> flag on <code>plan</code>, <code>apply</code>, &amp; <code>destroy</code> commands</li>
</ul>
</li>
<li>You can see this dependency map using the <code>terraform graph</code> command and a viewer like <a target="_blank" href="https://www.graphviz.org/">Graphviz</a> (or http://www.webgraphviz.com/ if you are lazy like me)</li>
</ul>
<h2 id="heading-understand-terraform-cloud-capabilities">Understand Terraform Cloud Capabilities</h2>
<p>Domain 9 (the last domain!) is all about Terraform Cloud. This is the HashiCorp managed remote backend and offers a free tier (up to 500 managed resources when I wrote this article). </p>
<p>Every production-level environment will use a state-locking remote back end, so knowing how Terraform Cloud works is great not only for the exam, but for real world job experience as well. </p>
<h3 id="heading-explain-how-terraform-cloud-helps-to-manage-infra">Explain how Terraform Cloud helps to manage infra</h3>
<p>Terraform Cloud - is a SaaS offering that:</p>
<ul>
<li>Manages Terraform runs in a consistent &amp; reliable environment</li>
<li>Includes easy access to shared state and secret data </li>
<li>Access controls for approving changes to infrastructure</li>
<li>A private registry for sharing Terraform modules</li>
<li>Detailed policy controls for governing the contents of Terraform configurations</li>
<li>Remote state storage</li>
<li>Version control integrations</li>
<li>Custom workspace permissions</li>
<li>Flexible workflows - CLI, UI, VCS, or the API</li>
<li>Collaboration - review/comment on plans prior to executing infra changes</li>
<li>Audit logs - who broke it</li>
</ul>
<p>Terraform Enterprise is a self-hosted distribution of Terraform Cloud. It's not on the exam, but <a target="_blank" href="https://developer.hashicorp.com/terraform/enterprise">here</a> are the docs for requirements, ref architectures, and install guides.</p>
<h3 id="heading-describe-how-terraform-cloud-enables-collaboration-and-governance">Describe how Terraform Cloud enables collaboration and governance</h3>
<p>Terraform Cloud uses <strong>Teams</strong> as its grouping paradigm. Teams are comprised of Users in a given Organization. Each Team can have an API token that is not associated with a specific user.</p>
<p>The Organization grants workspace permissions to Users and Teams. The Owners Team:</p>
<ul>
<li>Is the 1st team created</li>
<li>Cannot be deleted the Owners Team or left empty</li>
<li>Can create/delete other Teams</li>
<li>Manages Org-level permissions to other Teams</li>
<li>Can view the full list of teams (Visible and Secret)</li>
</ul>
<p>Terraform Cloud enforces <strong>Policies</strong> on runs using the <em>Sentinel Policy Language</em>. After defining a policy, they are added to policy sets that Terraform Cloud can then enforce.</p>
<ul>
<li>Do the <a target="_blank" href="https://developer.hashicorp.com/terraform/tutorials/cloud-get-started/policy-quickstart">Enforce a policy with Sentinel</a> tutorial.</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>That's it! 😂 I feel confident that if you review all of the material here, do the tutorials specified in the exam prep, and attend our <a target="_blank" href="https://terraform.cloudprojectbootcamp.com/">Terraform Beginner Bootcamp</a>, you will be well prepared to sit and pass the Terraform Associate Exam. </p>
<p>Good luck!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ HashiCorp Terraform Associate Certification Study Course – Pass the Exam With This Free 7 Hour Course ]]>
                </title>
                <description>
                    <![CDATA[ By Andrew Brown Learn how to pass the HashiCorp Terraform Associate Certification (003) with this free 7-hour course. What is the HashiCorp Terraform Associate? HashiCorp is a company specializing in open-source tools for multi-cloud workloads. Hashi... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/hashicorp-terraform-associate-certification-study-course-pass-the-exam-with-this-free-12-hour-course/</link>
                <guid isPermaLink="false">66d45dad33b83c4378a517b4</guid>
                
                    <category>
                        <![CDATA[ Certification ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Terraform ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 17 Aug 2023 14:07:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/08/hashicorp.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Andrew Brown</p>
<p>Learn how to pass the HashiCorp Terraform Associate Certification (003) with this free 7-hour course.</p>
<h2 id="heading-what-is-the-hashicorp-terraform-associate"><strong>What is the HashiCorp Terraform Associate?</strong></h2>
<p>HashiCorp is a company specializing in open-source tools for multi-cloud workloads. HashiCorp's most popular tool is Terraform, which allows DevOps Engineers to write code to provision infrastructure for multiple Cloud Service Providers (CSPs), for example AWS, Azure, and GCP.</p>
<p>The HashiCorp Terraform Associate is a certification that proves an engineer has a practical understanding of the Terraform tool as well as the SaaS offering known as Terraform Cloud.</p>
<p>For those seeking a career as a DevOps Engineer, the Terraform Associate is essential since Terraform has become the industry standard for Infrastructure as Code (IaC) and frequently appears as an expected skill in DevOps Job postings.</p>
<p>The Terraform Associate is not a difficult exam but strongly relies on practical knowledge of Terraform. That's why this study course is 7 hours – we've added many follow alongs and common edge cases that you will only experience in practice.</p>
<h2 id="heading-overview-of-the-terraform-associate"><strong>Overview of</strong> the Terraform Associate</h2>
<p>The Terraform Associate is composed of the following domains:</p>
<ol>
<li>Understand infrastructure as code (IaC) concepts</li>
<li>Understand Terraform's purpose (vs other IaC)</li>
<li>Understand Terraform basics</li>
<li>Use the Terraform CLI (outside of core workflow)</li>
<li>Interact with Terraform modules</li>
<li>Navigate Terraform workflow</li>
<li>Implement and maintain state</li>
<li>Read, generate, and modify configuration</li>
<li>Understand Terraform Cloud and Enterprise capabilities</li>
</ol>
<p>While the Terraform certification has many more domains than other cloud certifications they are finite in the expected requirements of each domain, and nearly everything you see on in the exam-guide appears on the exam.</p>
<h2 id="heading-can-i-simply-watch-the-videos-and-pass-the-exam"><strong>Can I simply watch the videos and pass the exam?</strong></h2>
<p>Yes, HashiCorp Terraform Associate is easy enough to simply watch the videos and pass the exam. </p>
<p>However, since the exam is always based on the last three minor versions of Terraform, if you don't do the follow alongs on your machine you could be missing on new emerging Terraform edge cases.</p>
<h2 id="heading-where-is-the-free-practice-exam-and-cheatsheets-for-this-course">Where is the free practice exam and cheatsheets for this course?</h2>
<p>There is a <a target="_blank" href="https://www.exampro.co/terraform">free practice exam of 57 questions for this course and downloadable cheatsheets on ExamPro</a>. No Credit Card required, No Trial Limit.</p>
<p>Head on over <a target="_blank" href="https://youtu.be/SPcwo0Gq9T8">to freeCodeCamp's YouTube channel</a> to start working through the full 7 hour course.</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/SPcwo0Gq9T8" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use Terraform to Deploy a Site on Google Cloud Platform ]]>
                </title>
                <description>
                    <![CDATA[ Modern cloud technologies have revolutionized the way we develop and deploy applications. But managing complex infrastructure can still be a daunting task, especially when working at scale. The solution? Infrastructure as Code (IaC) — an approach tha... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-terraform-to-deploy-a-site-on-google-cloud-platform/</link>
                <guid isPermaLink="false">66b2037582069b4c678c98d5</guid>
                
                    <category>
                        <![CDATA[ Terraform ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Thu, 13 Jul 2023 21:46:56 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/07/terrafromgcp.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Modern cloud technologies have revolutionized the way we develop and deploy applications. But managing complex infrastructure can still be a daunting task, especially when working at scale. The solution? Infrastructure as Code (IaC) — an approach that brings programming paradigms to infrastructure management, thereby enhancing efficiency, repeatability, and agility.</p>
<p>With that in mind, we're excited to introduce a new course that combines IaC with the power of Terraform and Google Cloud Platform (GCP). The course is now live on the freeCodeCamp.org YouTube channel. This course is designed to equip you with the skills to efficiently deploy a website to the GCP using Terraform, an industry-leading IaC tool.</p>
<p>Rishab Kumar created this course. He is a Developer Evangelist at Twilio and is an excellent teacher.</p>
<p>Here's a snapshot of the course:</p>
<ol>
<li><strong>Introduction to Project</strong>: Get an overview of the project you'll be working on throughout the course — deploying a website to the GCP.</li>
<li><strong>Setting Up Google Cloud Platform (GCP)</strong>: Navigate through the GCP setup process, understanding key features of this powerful cloud service provider.</li>
<li><strong>Installing Terraform and Setting Up the Directory</strong>: Dive into the Terraform setup, learn how to install it, and set up the directory for your project.</li>
<li><strong>Writing Terraform Code</strong>: Master the art of writing effective Terraform code for managing infrastructure.</li>
<li><strong>Deploying Google Storage Bucket to GCP</strong>: Implement your first deployment — a Google Storage Bucket — to the GCP using Terraform.</li>
<li><strong>Adding Other Resources in Terraform</strong>: Expand your Terraform skills by learning to add more resources, taking your deployment capabilities to the next level.</li>
<li><strong>Custom Domain Configuration</strong>: Find out how to configure a custom domain for your website, adding a professional touch.</li>
<li><strong>Deploying Remaining Resources to GCP</strong>: Deploy the rest of the resources to the GCP, consolidating all the concepts you've learned so far.</li>
<li><strong>Terraform Destroy and gitignore</strong>: Finally, understand how to safely de-provision infrastructure using 'Terraform Destroy' and learn about the role of '.gitignore' in a Terraform project.</li>
</ol>
<p>This course is an excellent opportunity to tap into the transformative power of IaC. Whether you're a beginner or an experienced developer looking to expand your skillset, this course provides a hands-on, practical approach to mastering Terraform and GCP.</p>
<p>Watch the full course on the freeCodeCamp.org YouTube channel (1-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/VCayKl82Lt8" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What is Infrastructure as Code? Explained for Beginners ]]>
                </title>
                <description>
                    <![CDATA[ Infrastructure as Code (IaC) is a way of managing your infrastructure like it was code. This gives you all the benefits of using code to create your infrastructure, like version control, faster and safer infrastructure deployments across different en... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/infrastructure-as-code-basics/</link>
                <guid isPermaLink="false">66d45e193dce891ac3a967e4</guid>
                
                    <category>
                        <![CDATA[ Infrastructure as code ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Terraform ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Daniel Adetunji ]]>
                </dc:creator>
                <pubDate>Thu, 15 Jun 2023 14:32:46 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/06/cover-5.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Infrastructure as Code (IaC) is a way of managing your infrastructure like it was code. This gives you all the benefits of using code to create your infrastructure, like version control, faster and safer infrastructure deployments across different environments, and having up to date documentation of your infrastructure.</p>
<p>The article will cover how infrastructure as code works using an analogy. We'll cover the different infrastructure as code tools available as well as declarative vs imperative code</p>
<p>I'll also introduce you to Terraform, which is an open source infrastructure as code tool you can use to create infrastructure across multiple cloud providers like AWS, GCP, Azure and others.</p>
<h2 id="heading-infrastructure-as-code-in-practice">Infrastructure as Code in Practice</h2>
<p>Imagine you are trying to create a three-tiered web application on AWS as you can see in the image below:</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f739c67-e79c-4995-b58d-71d695cccd47_1018x1682.png" alt="Image" width="1018" height="1682" loading="lazy"></p>
<p><em>Three tiered web application example</em></p>
<p>The presentation tier is responsible for presenting the user interface to the user. It includes the user interface components such as HTML, CSS, and JavaScript running on EC2 instances.</p>
<p>The logic tier is responsible for processing user requests and generating responses, by communicating with the database layer to retrieve or store data. This is also deployed on EC2 instances</p>
<p>The database tier is responsible for storing and managing the application's data and allows access to its data through the logic tier. The database runs on AWS RDS.</p>
<p>Each of the instances are in an <a target="_blank" href="https://lightcloud.substack.com/i/102200211/auto-scaling-explained">autoscaling</a> group with a <a target="_blank" href="https://lightcloud.substack.com/i/102200211/load-balancing-explained">load balancer</a> in front of it (except for the database tier).</p>
<p>If you want to create this infrastructure through the AWS console, you would have to manually click through various screens to spin up the infrastructure. This is fine if it is a one time activity.</p>
<p>But if you need to repeat this across different environments like development and test, or need to add additional infrastructure like caches, queues, firewall rules, <a target="_blank" href="https://lightcloud.substack.com/p/aws-iam-identity-and-access-management">IAM</a> or SSL certificates, then it becomes increasingly more complex to manage through the AWS console.</p>
<p>Managing complex infrastructure through the console also introduces the possibility of human error.</p>
<p>Infrastructure as code expresses your desired infrastructure in the language of code. This brings all the benefits of code to managing your infrastructure like:</p>
<ol>
<li><p>Version Control – allows you to store the history of your infrastructure and revert to a previous version if needed.</p>
</li>
<li><p>Faster &amp; safer deployments – can recreate infrastructure in new environments quickly and with less errors since every part of the infrastructure is clearly defined in the code.</p>
</li>
<li><p>Documentation – your current infrastructure state is documented and kept up to date automatically whenever you make a change. This keeps your infrastructure documentation detailed and accurate, compared to having the infrastructure written in a document or on a confluence page that may not be updated whenever there is a change.</p>
</li>
</ol>
<h2 id="heading-how-infrastructure-as-code-works-explained-with-an-analogy">How Infrastructure as Code Works – Explained with an Analogy</h2>
<p>Infrastructure as code allows you to create a detailed blueprint of your infrastructure. This blueprint gives instructions to your cloud provider about the infrastructure you want created.</p>
<p>This is similar to how an architecture blueprint works. It outlines the layout, dimensions, materials, and various components of the structure. The blueprint serves as a reference for architects and engineers to understand the desired construction.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d1aacbf-34c7-43ae-8384-2f912072cc00_2728x1514.png" alt="Image" width="1456" height="808" loading="lazy"></p>
<p><em>how an architectural blueprint is analogous to infrastructure as code</em></p>
<p>The blueprint leaves little room for error. It will be interpreted in the same way by any architect or engineer. If you wanted to build exact copies of this house, all you need is the architecture blueprint.</p>
<p>Infrastructure as code, at a basic level, works in the same way as an architecture blueprint. It details the infrastructure you want to create as code in a number of different possible languages (JSON, YAML, HCL, Python, Ruby, JavaScript, and so on), instructing the cloud provider to create your infrastructure exactly as specified.</p>
<h2 id="heading-declarative-amp-imperative-infrastructure-as-code-tools">Declarative &amp; Imperative Infrastructure as Code Tools</h2>
<p>There are many IaC options to choose from, and all the major cloud providers have their own dedicated tools:</p>
<ul>
<li><p>AWS has CloudFormation</p>
</li>
<li><p>GCP has Deployment Manager</p>
</li>
<li><p>Azure has Resource manager</p>
</li>
</ul>
<p>One limitation of these cloud provider-specific tools is that they can only create infrastructure in their respective clouds. So CloudFormation only works in AWS and Deployment Manager only works in GCP. IaC using these providers is usually written in JSON or YAML format.</p>
<p>Terraform, on the other hand, is open source and you can use it to create infrastructure across all the major cloud providers. It uses HCL (HashiCorp Configuration Language).</p>
<p>Infrastructure as code can also be written using popular languages like Python and JavaScript.</p>
<p>These scripting/programming languages lie on a spectrum of declarative and imperative code as shown below.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdaba03dc-13eb-4f45-873f-6f00dd648ffb_2508x870.png" alt="Image" width="1456" height="505" loading="lazy"></p>
<p><em>A spectrum of declarative &amp; imperative languages and where Terraform HCL fits</em></p>
<p>The main difference between an imperative and declarative language is that imperative languages explicitly define the <em>control flow</em>. This is simply the order in which instructions are executed in a program. Control flow determines the path the program takes and how it responds to different conditions or events.</p>
<p>In imperative languages, control flow is explicitly defined using control structures such as loops, conditionals, and function calls. Imperative languages give you more flexibility in configuring your infrastructure. This is not necessarily a positive, as more flexibility means more opportunity to introduce errors into your infrastructure.</p>
<p>A declarative language focuses on describing the desired result without giving specific instructions on how to achieve it.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fe4d2ce-6449-4597-beca-5a46f2aa6ee8_2620x1468.png" alt="Image" width="1456" height="816" loading="lazy"></p>
<p><em>An illustration demonstrating the difference between declarative and imperative languages</em></p>
<p>An example JSON is shown below, used in AWS CloudFormation to create an EC2 instance:</p>
<pre><code class="lang-python"><span class="hljs-string">"Type"</span>: <span class="hljs-string">"AWS::EC2::Instance"</span>,
      <span class="hljs-string">"Properties"</span>: {
        <span class="hljs-string">"ImageId"</span>: <span class="hljs-string">"ami-0123456789"</span>,
        <span class="hljs-string">"InstanceType"</span>: <span class="hljs-string">"t2.micro"</span>,
        <span class="hljs-string">"KeyName"</span>: <span class="hljs-string">"my-key-pair"</span>,
        <span class="hljs-string">"SecurityGroupIds"</span>: [<span class="hljs-string">"sg-0123456789"</span>],
        <span class="hljs-string">"SubnetId"</span>: <span class="hljs-string">"subnet-0123456789"</span>,
        <span class="hljs-string">"Tags"</span>: [
          {
            <span class="hljs-string">"Key"</span>: <span class="hljs-string">"Name"</span>,
            <span class="hljs-string">"Value"</span>: <span class="hljs-string">"MyEC2Instance"</span>
          }
        ]
      }
</code></pre>
<p>A declarative language like JSON <a target="_blank" href="https://lightcloud.substack.com/p/cloud-computing-abstractions-explained">abstracts</a> away the underlying complexity that details how the EC2 instance will be created. All it cares about is the end state.</p>
<p>Terraform HCL is closer to the declarative end of the spectrum. Terraform allows you to describe the desired infrastructure's final state without specifying the exact steps to get there. Terraform internally manages the execution order, resource dependencies, and handles the infrastructure changes based on the desired configuration.</p>
<p>But Terraform does have support for some imperative features like variables and expressions, allowing dynamic behaviour based on inputs. So, it is not a completely declarative language like JSON.</p>
<h2 id="heading-how-terraform-works">How Terraform Works</h2>
<p>There are two fundamental concepts that serve as a foundation for understanding Terraform:</p>
<ol>
<li><p>The configuration file – this describes the desired infrastructure</p>
</li>
<li><p>The state file – this describes the current infrastructure as it exists in the real world</p>
</li>
</ol>
<p>Terraform’s job is to create, modify or delete infrastructure as needed so that the desired infrastructure configuration is met. It does this by executing the necessary API calls to your cloud provider(s) to create, modify, or destroy the resources as specified.</p>
<p>Once the infrastructure has been created/modified/destroyed to match the configuration file, the state file is updated to reflect the current infrastructure.</p>
<p>The <code>terraform plan</code> command creates an <a target="_blank" href="https://developer.hashicorp.com/terraform/cli/commands/plan">execution plan</a>, which lets you preview the changes that Terraform plans to make to your infrastructure.</p>
<p>By default, when Terraform creates a plan, it compares the desired configuration as described in the configuration file, with the current configuration as described in the state file. Terraform then proposes a list of changes needed what will ensure that the current configuration matches the desired configuration.</p>
<p>If you then run the <code>terraform apply</code> command, terraform will modify the real world infrastructure so that it matches the desired configuration, and updates the state file to show the new infrastructure configuration.</p>
<p>At a high level, this is what terraform does:</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba9f88a0-e8bb-4507-9fd9-a9576bb8fefe_2650x1380.png" alt="Image" width="1456" height="758" loading="lazy"></p>
<p><em>What happens when you run the</em> <code>terraform apply</code> command</p>
<p>Let’s bring back the architectural blueprint analogy.</p>
<p>The configuration file is like the architectural blueprint. It details the infrastructure that needs to be built, that is the desired construction. The real world infrastructure is the existing construction in the physical world and the state file is a representation of what currently exists – the current blueprint. The engineers work to ensure that the existing construction matches the architecture blueprint.</p>
<p>In this analogy, engineers do the work of Terraform in ensuring that the existing construction matches the architecture blueprint. You don’t need to specify the details of how to build the house, you just need to specify what you want built and the engineers handle the rest.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F973320df-191b-46b6-be0a-18cd43305288_2620x1468.png" alt="Image" width="1456" height="816" loading="lazy"></p>
<p><em>An architectural analogy to running</em> <code>terraform apply</code></p>
<p>If you want to learn more about how Terraform works and how you can use it in your projects, you can <a target="_blank" href="https://www.freecodecamp.org/news/learn-terraform-and-aws-by-building-a-dev-environment/">check out this free course</a> on freeCodeCamp's YouTube channel.</p>
<h2 id="heading-bringing-it-together">Bringing it Together</h2>
<p>Infrastructure as code (IaC) is a great way of managing complex infrastructure configuration in the form of code. This naturally brings all the advantages of code to your infrastructure like version control, faster and safer infrastructure deployments across different environments and up to date documentation of your infrastructure.</p>
<p>Terraform is an open source IaC tool that allows you to work with multiple cloud providers to spin up infrastructure as defined in your configuration files.</p>
<p>Terraform HCL is a declarative language that allows you to describe your desired infrastructure configuration. All you have to do is specify what you want created and terraform handles the creation on your behalf by making API calls to your chosen cloud provider(s).</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Learn Terraform by Deploying a Jenkins Server on AWS ]]>
                </title>
                <description>
                    <![CDATA[ Hello, everyone! Today we're going to learn about Terraform by building a project. Terraform is more than just a tool to boost the productivity of operations teams. You have the chance to transform your developers into operators by implementing Terra... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/learn-terraform-by-deploying-jenkins-server-on-aws/</link>
                <guid isPermaLink="false">66b906b7e8c4e204927a90f3</guid>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ deployment ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Devops ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Jenkins ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Terraform ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Destiny Erhabor ]]>
                </dc:creator>
                <pubDate>Tue, 26 Jul 2022 18:03:50 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/07/pexels-pok-rie-2003885.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Hello, everyone! Today we're going to learn about Terraform by building a project.</p>
<p>Terraform is more than just a tool to boost the productivity of operations teams. You have the chance to transform your developers into operators by implementing Terraform.</p>
<p>This can help increase the efficiency of your entire engineering team and improve communication between developers and operators.</p>
<p>In this article, I'll show you how to fully automate the deployment of your Jenkins services on the AWS cloud using Terraform with a custom baked image.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><a class="post-section-overview" href="#heading-what-is-terraform">What is Terraform?</a></li>
<li><a class="post-section-overview" href="#heading-why-should-you-use-terraform">Why Should You Use Terraform?</a></li>
<li><a class="post-section-overview" href="#heading-how-terraform-works">How Terraform works</a></li>
<li><a class="post-section-overview" href="#heading-what-is-a-procedural-language-vs-a-declarative-language">What is a Procedural Language vs a Declarative Language?</a></li>
<li><a class="post-section-overview" href="#heading-prerequisites-and-installation">Prerequisites and Installation</a></li>
<li><a class="post-section-overview" href="#heading-filefolder-structure-of-our-project">File/Folder Structure of Our Project</a></li>
<li><a class="post-section-overview" href="#heading-how-to-first-initialize-terraform-state">How to First Initialize Terraform State</a></li>
<li><a class="post-section-overview" href="#heading-how-to-provision-an-aws-virtual-private-cloud">How to Provision an AWS Virtual Private Cloud</a></li>
<li><a class="post-section-overview" href="#heading-how-to-work-with-terraform-modules">How to Work with Terraform Modules</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-a-vpc-subnet">How to Create a VPC Subnet</a></li>
<li><a class="post-section-overview" href="#heading-how-to-setup-vpc-route-tables">How to Setup VPC Route Tables</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-a-public-route-table">How to Create a Public Route Table</a></li>
<li><a class="post-section-overview" href="#how-to-create-a-private-route-table">How to Create a Private Route Table</a></li>
<li><a class="post-section-overview" href="#heading-how-to-setup-a-vpc-bastion-host">How to Setup a VPC Bastion Host</a></li>
<li><a class="post-section-overview" href="#heading-how-to-provision-our-compute-service">How to Provision our Compute Service</a></li>
<li><a class="post-section-overview" href="#heading-jenkins-master-instance">Jenkins master instance</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-the-load-balancer">How to Create the Load Balancer</a></li>
<li><a class="post-section-overview" href="#heading-cleaning-up">Cleaning Up</a></li>
<li><a class="post-section-overview" href="#heading-summary">Summary</a></li>
</ul>
<h1 id="heading-what-is-terraform">What is Terraform?</h1>
<p>Terraform by HashiCorp is an infrastructure as code solution. It lets you specify cloud and on-premise resources in human-readable configuration files that you can reuse and share. It is a powerful DevOps provisioning tool.</p>
<h1 id="heading-why-should-you-use-terraform">Why Should You Use Terraform?</h1>
<p>Terraform has a number of use-cases, including the capacity to:</p>
<ul>
<li>Specify infrastructure in config/code and easily rebuild/change and track changes to infrastructure.</li>
<li>Support different cloud platforms</li>
<li>Perform incremental resource modifications</li>
<li>Support software-defined networking</li>
</ul>
<h1 id="heading-how-terraform-works">How Terraform works</h1>
<p>Let's have a look at how Terraform works at a high level.</p>
<p>Terraform is developed in the Go programing language. The Go code is compiled into <strong>terraform,</strong> a single binary. You can use this binary to deploy infrastructure from your laptop, a build server, or just about any other computer, and you won't need to run any additional infrastructure to do so.</p>
<p>This is because the Terraform binary makes API calls on your behalf to one or more providers, which include Azure, AWS, Google Cloud, DigitalOcean, and others. This allows Terraform to take advantage of the infrastructure that those providers already have in place for their API servers, as well as the authentication processes they require.</p>
<p><strong>But Terraform doesn't know what API requests to make – so how does it know?</strong> Terraform configurations, which are text files in <strong>declarative language</strong> that specify what infrastructure you want to generate, are the answer. The "code" in "infrastructure as code" is these setups.</p>
<p>You have complete control over your infrastructure, including servers, databases, load balancers, network topology, and more. On your behalf, the Terraform binary parses your code and converts it into a series of API calls as quickly as possible.</p>
<h1 id="heading-what-is-a-procedural-language-vs-a-declarative-language">What is a Procedural Language vs a Declarative Language?</h1>
<p>A procedural language allows you to specify the entire process and list the steps necessary to complete it. You merely give instructions and specify how the process will be carried out. Chef and Ansible encourage this method.</p>
<p>Declarative languages, on the other hand, allow you to simply set the command or order and leave it up to the system to carry it out. You don't need to go into the process; you just need the result. Examples are Terraform, cloudFormation, and Puppeteer.</p>
<p>Enough of the theory...</p>
<p>Now is the moment to put Terraform's high availability, security, performance, and dependability into action.</p>
<p>Here, we're talking about a Terraform-based Jenkins server on Amazon Web Services. We are setting up the networking from the ground-up, so let's get started.</p>
<h2 id="heading-prerequisites-and-installation">Prerequisites and Installation</h2>
<p>There are a few things you'll need to have setup and installed to follow along with this tutorial:</p>
<ul>
<li><a target="_blank" href="https://aws.amazon.com/console/">Create an AWS account</a></li>
<li><a target="_blank" href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html#id_users_create_console">Create an IAM user</a></li>
<li><a target="_blank" href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html#Using_CreateAccessKey">Create and Download your user secret and access key</a></li>
<li><a target="_blank" href="https://learn.hashicorp.com/tutorials/terraform/install-cli">Install Terraform from the Terraform - HashiCorp Learn page</a></li>
</ul>
<h2 id="heading-filefolder-structure-of-our-project">File/Folder Structure of Our Project</h2>
<p>We'll use a modular development strategy to separate our Jenkins cluster deployment into numerous template files (rather than developing one large template file).</p>
<p>Each file is in charge of executing a target infrastructure component or AWS resource.</p>
<p>For creating and enforcing infrastructure settings, Terraform leverages the syntax of a JSON-like configuration language called HCL (HashiCorp Configuration Language).</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/structure.PNG" alt="files/folders structures" width="600" height="400" loading="lazy">
<em>files/folders structures</em></p>
<h2 id="heading-how-to-first-initialize-terraform-state">How to First Initialize Terraform State</h2>
<p>To follow best practices, we will be storing our Terraform state files in our cloud storage. This is essential especially for team collaboration.</p>
<p>Terraform state files are files that contain Terraform resources on the projects.</p>
<p>Inside the main.tf file in the backend-state folder, add the following code:</p>
<pre><code class="lang-json">variable <span class="hljs-string">"aws_region"</span> { 
    default = <span class="hljs-attr">"us-east-1"</span> 
 } 
variable <span class="hljs-string">"aws_secret_key"</span> {} 
variable <span class="hljs-string">"aws_access_key"</span> {} 

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

resource <span class="hljs-string">"aws_s3_bucket"</span> <span class="hljs-string">"terraform_state"</span> { 
    bucket = <span class="hljs-attr">"terraform-state-caesar-tutorial-jenkins"</span> 

    lifecycle { 
        prevent_destroy = true 
    } 

    versioning { 
        enabled = true 
   } 

   server_side_encryption_configuration { 
           rule { 
            apply_server_side_encryption_by_default { 
                sse_algorithm = <span class="hljs-attr">"AES256"</span> 
            } 
        } 
   } 
}
</code></pre>
<p>Let's make sure we know what's going on in the above code.</p>
<p>We use <strong>variables</strong> to store data, and in Terraform you declare a variable with the variable keyword followed by the name. The variable block can either take some properties such as default, description, type, and so on or none. You will be seeing this a lot.</p>
<p>Now we are declaring the variables as <code>variable "variable_name"{}</code> and <strong>using them in any resources/data block as</strong> <code>var.variable_name</code>. Later you'll see how we will be assigning values to those variables in our secrets.tfvars file.</p>
<p>To use Terraform, you need to tell it the <strong>provider</strong> it will be communicating with and pass in its required properties for authentication. Here we have the AWS region, access, and secret key (you should have these downloaded on your system from the prerequisites).</p>
<p>In terraform, each <strong>resource</strong> we need is defined in the resource block. Resources is the underlined infrastructure that creates our cloud service. It follows the syntax <code>resource "terraform-resource-name" "custom-name" {}</code>.</p>
<p>Terraform has a lot of resources for particular providers in the terraform docs (always refer to the docs if you have questions).</p>
<p>Next, we are creating the aws_s3_bucket. This will store our remote state. It takes the following properties:</p>
<ul>
<li><strong>bucket</strong> → This has to be globally unique</li>
<li><strong>lifecycle</strong> → If you need to destroy your Terraform resources, you might want to prevent destroying the state as it is shared across teams</li>
<li><strong>versioning</strong> → Helps provide some version control over the states</li>
<li><strong>server_side_encryption_configuration</strong> → Provides encryption.</li>
</ul>
<p>Our state backend is ready. But before we initialize it, plan, and apply it with Terraform, let’s <strong>assign our variable to its values</strong>.</p>
<p>In secrets.tfvars, add the following info from your AWS account:</p>
<pre><code class="lang-json">  aws_region = <span class="hljs-string">"us-east-1 
  aws_secret_key = "</span>enter-your-secret<span class="hljs-string">" 
  aws_access_key = "</span>enter-your-access
</code></pre>
<p>In your terminal in the same backend-state folder, run <code>terraform init</code>.</p>
<p><img src="https://paper-attachments.dropbox.com/s_0475B692F95FDEC0A6E8498B95C86079E0E8D8D5196F9F4DEAA5AA6D3B79CB44_1655827434318_terminal-state-1.PNG" alt="terraform state on terminal" width="600" height="400" loading="lazy">
<em>terraform state on terminal</em></p>
<p>Then <code>terraform apply -var-file=secrets.tfvars</code>:</p>
<p><img src="https://paper-attachments.dropbox.com/s_0475B692F95FDEC0A6E8498B95C86079E0E8D8D5196F9F4DEAA5AA6D3B79CB44_1655827520492_terminal-state-2.PNG" alt="terraform state on terminal" width="600" height="400" loading="lazy">
<em>terraform state on terminal</em></p>
<p>In your <strong>AWS console</strong>, here's what you'll see:</p>
<p><img src="https://paper-attachments.dropbox.com/s_0475B692F95FDEC0A6E8498B95C86079E0E8D8D5196F9F4DEAA5AA6D3B79CB44_1655827945123_aws-console.PNG" alt="terraform state on aws s3 bucket" width="600" height="400" loading="lazy">
<em>terraform state on aws s3 bucket</em></p>
<p>‌‌Now that our state is ready, let’s move to the next part.</p>
<h2 id="heading-how-to-provision-an-aws-virtual-private-cloud">How to Provision an AWS Virtual Private Cloud</h2>
<p>To secure our Jenkins cluster, we will deploy the architecture within a virtual private cloud (VPC) and private subnet. You can deploy the cluster in the AWS default VPC.</p>
<p>To have complete control over the network topology, we will create a VPC from scratch.</p>
<pre><code class="lang-json"> variable <span class="hljs-string">"cidr_block"</span> {} 
 variable <span class="hljs-string">"aws_access_key"</span> {} 
 variable <span class="hljs-string">"aws_secret_key"</span> {} 
 variable <span class="hljs-string">"aws_region"</span> {} 

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

terraform { 
    backend <span class="hljs-attr">"s3"</span> { 
        bucket     = <span class="hljs-attr">"terraform-state-caesar-tutorial-jenkins"</span> 
        key        = <span class="hljs-attr">"tutorial-jenkins/development/network/terraform.tfstate"</span> 
        region     = <span class="hljs-attr">"us-east-1"</span> 
        encrypt    = true 
   }
} 

resource <span class="hljs-string">"aws_vpc"</span> <span class="hljs-string">"main_vpc"</span> { 
    cidr_block           = var.cidr_block 
    enable_dns_support   = true 
    enable_dns_hostnames = true 

    tags = { 
        Name        = <span class="hljs-attr">"jenkins-instance-main_vpc"</span> 
    } 
}
</code></pre>
<pre><code class="lang-json">output <span class="hljs-string">"vpc_id"</span> { 
    value = aws_vpc.main_vpc.id 
} 

output <span class="hljs-string">"vpc_cidr_block"</span> { 
    value = aws_vpc.main_vpc.cidr_block 
}
</code></pre>
<pre><code class="lang-json">cidr_block            = <span class="hljs-string">"172.0.0.0/16"</span> 
aws_region = <span class="hljs-string">"us-east-1"</span> 
aws_secret_key = <span class="hljs-string">"enter-your-secret"</span> 
aws_access_key = <span class="hljs-string">"enter-your-access"</span>
</code></pre>
<ul>
<li><strong>cidr_block →</strong> Classless Inter-Domain Routing is referred to as CIDR. A CIDR block is an IP address range, to put it simply. This defines what range we are working in.</li>
<li><strong>output →</strong> The output block in Terraform is used to export resource values to other modules. This is another important term when transferring a resource data in one module to another resource in a separate module. (You will learn what modules are soon) Here's its syntax: <code>output "custom_output_name" {  value = "resource-name"}</code>. It takes in a <strong>value</strong> key that takes the resource passed. Here we are output vpc_id and cidr_block.</li>
</ul>
<p>Now, in the terminal, run <code>terraform init</code> and <code>terraform apply</code> to create the resources. You can run <code>terraform plan</code> before to see what resources you are actually creating. Here's the command: <code>terraform apply -var-file=secrets.tfvars</code>, and the output:</p>
<p><img src="https://paper-attachments.dropbox.com/s_0475B692F95FDEC0A6E8498B95C86079E0E8D8D5196F9F4DEAA5AA6D3B79CB44_1655907973370_vpc.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You should see your <strong>vpc_id and vpc_cidr_block</strong> in your <strong>AWS Console</strong>:</p>
<p><img src="https://paper-attachments.dropbox.com/s_0475B692F95FDEC0A6E8498B95C86079E0E8D8D5196F9F4DEAA5AA6D3B79CB44_1656907111688_vpc-aws.PNG" alt="vpc resource on aws" width="600" height="400" loading="lazy">
<em>vpc on aws</em></p>
<h2 id="heading-how-to-work-with-terraform-modules">How to Work with Terraform Modules</h2>
<p>A group of typical configuration files in a specific directory make up a Terraform module. Terraform modules put together resources that are used for a single operation. This cuts down on the amount of code you need to create identical infrastructure components.</p>
<p>Using the syntax below, you can transfer one Terraform module resource to another to be used.</p>
<pre><code class="lang-json">module <span class="hljs-string">"custom-module-name"</span> { 
    source     = <span class="hljs-attr">"path-to-modules-resources"</span> 
}
</code></pre>
<p>And to used the module resource output inside another resource module, this is the command: <code>module.custom-module-name.resource-output-value</code>.</p>
<h2 id="heading-how-to-create-a-vpc-subnet">How to Create a VPC Subnet</h2>
<p>Creating a VPC isn't enough – we also need a subnet to be able to install Jenkins instances on this isolated network. We must pass the VPC ID we output before, since this subnet belongs to a previously constructed VPC.</p>
<p>For resilience, we'll use two public subnets and two private subnets in distinct availability zones. Each subnet has its own CIDR block, which is a subset of the VPC CIDR block, which we got from the VPC resource.</p>
<pre><code class="lang-json">resource <span class="hljs-string">"aws_subnet"</span> <span class="hljs-string">"public_subnets"</span> { 
    vpc_id         = var.vpc_id 
    cidr_block     = cidrsubnet(var.vpc_cidr_block, 8, 2 + count.index)  
       availability_zone   = element(var.availability_zones, count.index)      
    map_public_ip_on_launch = true 
    count                   = var.public_subnets_count 

    tags = { 
        Name        = <span class="hljs-attr">"jenkins-instance-public-subnet"</span> 
   } 
} 

resource <span class="hljs-string">"aws_subnet"</span> <span class="hljs-string">"private_subnets"</span> { 
    vpc_id     = var.vpc_id 
    cidr_block = cidrsubnet(var.vpc_cidr_block, 8, count.index)              
    availability_zone    = element(var.availability_zones, count.index)  
    map_public_ip_on_launch = false 
    count                   = var.private_subnets_count 

    tags = { 
        Name        = <span class="hljs-attr">"jenkins-instance-private-subnet"</span> 
    } 
 }
</code></pre>
<p>Alright, what's going on in this code?</p>
<ul>
<li><a target="_blank" href="https://www.terraform.io/language/meta-arguments/count"><strong>count</strong></a> <strong>→</strong> The count meta-argument accepts a whole number, and creates that many instances of the resource or module. Here we are specifying 2 each to the variables private_subnets_count and public_subnets_count.</li>
<li><strong>map_public_ip_on_launch →</strong> Specify true to indicate that instances launched into the subnet should be assigned a public IP address.</li>
<li><a target="_blank" href="https://www.terraform.io/language/functions/cidrsubnet"><strong>cidrsubnet()</strong></a> <strong>→</strong> cidrsubnet calculates a subnet address within a given IP network address prefix.</li>
<li><a target="_blank" href="https://www.terraform.io/language/functions/element"><strong>element()</strong></a> <strong>→</strong> element retrieves a single element from a list.</li>
</ul>
<p>Now let’s update our modules variables:</p>
<pre><code class="lang-json">variable <span class="hljs-string">"vpc_id"</span> {} 
variable <span class="hljs-string">"vpc_cidr_block"</span> {} 
variable <span class="hljs-string">"private_subnets_count"</span> {} 
variable <span class="hljs-string">"public_subnets_count"</span> {} 
variable <span class="hljs-string">"availability_zones"</span> {}
</code></pre>
<p>Update the secrets.tfvars like this:</p>
<pre><code class="lang-json">private_subnets_count = <span class="hljs-number">2</span> 
public_subnets_count  = <span class="hljs-number">2</span>
</code></pre>
<p>You must establish private and public route tables to specify the traffic-routing method in VPC subnets. Let’s do that before we execute <strong>terraform apply</strong> on our resources.</p>
<h2 id="heading-how-to-setup-vpc-route-tables">How to Setup VPC Route Tables</h2>
<p>We will develop private and public route tables for fine-grained traffic management. This will enable instances deployed in private subnets to access the internet without being exposed to the general public.</p>
<h3 id="heading-how-to-create-a-public-route-table">How to create a public route table</h3>
<p>First we need to establish an <strong>Internet gateway resource</strong> and link it to the VPC we generated previously. Then we need to define a <strong>public route table</strong> and a route that points all traffic (0.0.0.0/0) to the internet gateway. And lastly we need to link it with public subnets in our VPC so that traffic flowing from those subnets is routed to the internet gateway by creating a <strong>route table association.</strong></p>
<pre><code class="lang-json"><span class="hljs-comment">/*** Internet Gateway - Provides a connection between the VPC and the public internet, allowing traffic to flow in and out of the VPC and translating IP addresses to public* addresses.*/</span> 
resource <span class="hljs-string">"aws_internet_gateway"</span> <span class="hljs-string">"igw"</span> { 
    vpc_id = var.vpc_id 

    tags = { 
        Name = <span class="hljs-attr">"igw_jenkins"</span> 
   } 
} 

<span class="hljs-comment">/*** A route from the public route table out to the internet through the internet* gateway.*/</span> 
resource <span class="hljs-string">"aws_route_table"</span> <span class="hljs-string">"public_rt"</span> { 
    vpc_id = var.vpc_id 

    route { 
        cidr_block = <span class="hljs-attr">"0.0.0.0/0"</span> 
        gateway_id = aws_internet_gateway.igw.id 
   } 

   tags = { 
           Name = <span class="hljs-attr">"public_rt_jenkins"</span> 
   } 
} 
<span class="hljs-comment">/*** Associate the public route table with the public subnets.*/</span> 
resource <span class="hljs-string">"aws_route_table_association"</span> <span class="hljs-string">"public"</span> { 
    count     = var.public_subnets_count 
    subnet_id = element(var.public_subnets.*.id, count.index) 
    route_table_id = aws_route_table.public_rt.id 
}
</code></pre>
<h3 id="heading-how-to-create-a-private-route-table">‌How to create a private route table</h3>
<p>Now that our public route table is finished, let’s create the private route table.</p>
<p>To allow our Jenkins instances to connect to the internet as it is deployed on the private subnet, we will construct a <strong>NAT gateway resource</strong> inside a public subnet.</p>
<p>Add an <strong>Elastic IP</strong> address to the NAT gateway after that and a private route table with a route (0.0.0.0/0) that directs all traffic to the ID of the NAT gateway you established. Then we attach private subnets to the private route table by creating the <strong>route table association</strong>.</p>
<pre><code class="lang-json"> <span class="hljs-comment">/*** An elastic IP address to be used by the NAT Gateway defined below.  The NAT* gateway acts as a gateway between our private subnets and the public* internet, providing access out to the internet from within those subnets,* while denying access to them from the public internet.  This IP address* acts as the IP address from which all the outbound traffic from the private* subnets will originate.*/</span> 

 resource <span class="hljs-string">"aws_eip"</span> <span class="hljs-string">"eip_for_the_nat_gateway"</span> { 
     vpc = true 

    tags = { 
        Name = <span class="hljs-attr">"jenkins-tutoral-eip_for_the_nat_gateway"</span> 
    } 
} 

<span class="hljs-comment">/*** A NAT Gateway that lives in our public subnet and provides an interface* between our private subnets and the public internet.  It allows traffic to* exit our private subnets, but prevents traffic from entering them.*/</span> 

resource <span class="hljs-string">"aws_nat_gateway"</span> <span class="hljs-string">"nat_gateway"</span> { 
    allocation_id = aws_eip.eip_for_the_nat_gateway.id 
    subnet_id     = element(var.public_subnets.*.id, 0) 

    tags = { 
        Name = <span class="hljs-attr">"jenkins-tutorial-nat_gateway"</span> 
    } 
} 
<span class="hljs-comment">/*** A route from the private route table out to the internet through the NAT * Gateway.*/</span> 

resource <span class="hljs-string">"aws_route_table"</span> <span class="hljs-string">"private_rt"</span> { 
    vpc_id = var.vpc_id 

    route { 
        cidr_block     = <span class="hljs-attr">"0.0.0.0/0"</span> 
        nat_gateway_id = aws_nat_gateway.nat_gateway.id } 

        tags = { 
            Name   = <span class="hljs-attr">"private_rt_${var.vpc_name}"</span> 
            Author = var.author 
        } 
} 
<span class="hljs-comment">/*** Associate the private route table with the private subnet.*/</span> 
resource <span class="hljs-string">"aws_route_table_association"</span> <span class="hljs-string">"private"</span> { 
    count = var.private_subnets_count 
    subnet_id = element(aws_subnet.private_subnets.*.id, count.index) 
    route_table_id = aws_route_table.private_rt.id 
}
</code></pre>
<p>‌Now let's run <code>terraform apply</code>. But we need to <strong>update our main.tf</strong> files (as this is our entry terraform file) to be aware of our subnets and module variables and <strong>secrets.tfvars</strong> (for our variables).</p>
<pre><code class="lang-json">variable <span class="hljs-string">"vpc_id"</span> {} 
variable <span class="hljs-string">"vpc_cidr_block"</span> {} 
variable <span class="hljs-string">"private_subnets_count"</span> {} 
variable <span class="hljs-string">"public_subnets_count"</span> {} 
variable <span class="hljs-string">"availability_zones"</span> {} 
variable <span class="hljs-string">"public_subnets"</span> {}
</code></pre>
<pre><code class="lang-json">variable <span class="hljs-string">"private_subnets_count"</span> {} 
variable <span class="hljs-string">"public_subnets_count"</span> {} 
variable <span class="hljs-string">"availability_zones"</span> {} 

module <span class="hljs-string">"subnet_module"</span> { 
    source     = <span class="hljs-attr">"./modules"</span> 
    vpc_id     = aws_vpc.main_vpc.id 
    vpc_cidr_block = aws_vpc.main_vpc.cidr_block 
    availability_zones = var.availability_zones 
    public_subnets_count = var.public_subnets_count 
    private_subnets_count = var.private_subnets_count 
 }
</code></pre>
<pre><code class="lang-json"> availability_zones    = [<span class="hljs-string">"us-east-1a"</span>, <span class="hljs-string">"us-east-1b"</span>, <span class="hljs-string">"us-east-1c"</span>, <span class="hljs-string">"us-east-1d"</span>, <span class="hljs-string">"us-east-1e"</span>]
</code></pre>
<p>Our subnets and respective securities are ready. Now we can initialize it, plan, and apply with Terraform.</p>
<p>We will run <strong>terraform apply</strong> to create the resources. You can run terraform plan before to see what resources you are actually creating.</p>
<p>In the terminal run <code>terraform apply -var-file=secrets.tfvars</code>.</p>
<p><img src="https://paper-attachments.dropbox.com/s_0475B692F95FDEC0A6E8498B95C86079E0E8D8D5196F9F4DEAA5AA6D3B79CB44_1656906570979_terminal-state-3.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Just keep in mind that the number of resources added here might defer from yours.</p>
<p>Here's the AWS Console (subnets, elastic address, route_tables):</p>
<p><img src="https://paper-attachments.dropbox.com/s_0475B692F95FDEC0A6E8498B95C86079E0E8D8D5196F9F4DEAA5AA6D3B79CB44_1656907223081_image.png" alt="subnets" width="600" height="400" loading="lazy">
<em>subnets</em></p>
<p><img src="https://paper-attachments.dropbox.com/s_0475B692F95FDEC0A6E8498B95C86079E0E8D8D5196F9F4DEAA5AA6D3B79CB44_1656907210658_elastic-ip.PNG" alt="elastic ip" width="600" height="400" loading="lazy">
<em>elastic ip</em></p>
<p><img src="https://paper-attachments.dropbox.com/s_0475B692F95FDEC0A6E8498B95C86079E0E8D8D5196F9F4DEAA5AA6D3B79CB44_1656907210672_rt.PNG" alt="route tables" width="600" height="400" loading="lazy">
<em>route tables</em></p>
<h2 id="heading-how-to-setup-a-vpc-bastion-host">How to Setup a VPC Bastion Host</h2>
<p>We deployed our Jenkins cluster inside the private subnets. Because the cluster lacks a public IP, instances won't be publicly available via the internet. So to take care of this, we'll set up a bastion host so that we can access Jenkins instances safely.</p>
<p>Add the following resources and security group in the bastion.tf file:</p>
<pre><code class="lang-json"><span class="hljs-comment">/*** A security group to allow SSH access into our bastion instance.*/</span> 
resource <span class="hljs-string">"aws_security_group"</span> <span class="hljs-string">"bastion"</span> { 
    name   = <span class="hljs-attr">"bastion-security-group"</span> 
    vpc_id = var.vpc_id 

    ingress { 
        protocol    = <span class="hljs-attr">"tcp"</span> 
        from_port   = 22 
        to_port     = 22 
        cidr_blocks = [<span class="hljs-attr">"0.0.0.0/0"</span>] 
    } 
    egress { 
        protocol    = -1 
        from_port   = 0 
        to_port     = 0 
        cidr_blocks = [<span class="hljs-attr">"0.0.0.0/0"</span>] 
   } 

   tags = { 
           Name = <span class="hljs-attr">"aws_security_group.bastion_jenkins"</span> 
   } 
} 

<span class="hljs-comment">/*** The public key for the key pair we'll use to ssh into our bastion instance.*/</span> 

resource <span class="hljs-string">"aws_key_pair"</span> <span class="hljs-string">"bastion"</span> { 
    key_name   = <span class="hljs-attr">"bastion-key-jenkins"</span> 
    public_key = var.public_key 
 } 

 <span class="hljs-comment">/*** This parameter contains the AMI ID for the most recent Amazon Linux 2 ami,* managed by AWS.*/</span> 

 data <span class="hljs-string">"aws_ssm_parameter"</span> <span class="hljs-string">"linux2_ami"</span> { 
     name = <span class="hljs-attr">"/aws/service/ami-amazon-linux-latest/amzn-ami-hvm-x86_64-ebs"</span> 
} 

<span class="hljs-comment">/*** Launch a bastion instance we can use to gain access to the private subnets of* this availabilty zone.*/</span> 

resource <span class="hljs-string">"aws_instance"</span> <span class="hljs-string">"bastion"</span> { 
    ami           = data.aws_ssm_parameter.linux2_ami.value 
    key_name      = aws_key_pair.bastion.key_name 
    instance_type = <span class="hljs-attr">"t2.large"</span> 
    associate_public_ip_address = true 
    subnet_id                   = element(aws_subnet.public_subnets, 0).id 
    vpc_security_group_ids      = [aws_security_group.bastion.id] 

    tags = { 
        Name        = <span class="hljs-attr">"jenkins-bastion"</span> 
    } 
} 

output <span class="hljs-string">"bastion"</span> { value = aws_instance.bastion.public_ip }
</code></pre>
<p>Let's see what's going on in the code here:</p>
<ul>
<li><strong>bastion security group resource –</strong> Newly generated EC2 instances do not allow SSH access.</li>
<li>We will link a security group to the active instance in order to enable SSH access to the bastion hosts. Any inbound (ingress) traffic on port 22 (SSH) from anyplace (0.0.0.0/0) will be permitted by the security group. To improve security and prevent security breaches, you can substitute your own public IP address/32 or network address for the CIDR source block.</li>
<li><strong>aws_key_pair –</strong> To be able to connect to the bastion host using SSH and the private key, we added an SSH key pair when we created the EC2. Our public SSH key is used in the key pair. Using the <strong>sshkeygen</strong> command, you can also create a new one.</li>
<li><strong>aws_ssm_parameter</strong> – The Amazon 2 Linux machine image is used by the EC2 instance. The AMI ID is obtained from the AWS marketplace using the AWS AMI data source</li>
<li><strong>aws_instance –</strong>  Finally, we deploy our EC2 bastion instance with its defined configurations and access</li>
<li><strong>output</strong> – By specifying an output, we use the Terraform outputs functionality to show the IP address in the terminal session.</li>
</ul>
<p>Now, let’s update our variable within the modules and the main.tf with the new <strong>public_key</strong> we are passing as a variable:</p>
<pre><code class="lang-json">variable <span class="hljs-string">"public_key"</span>{}
</code></pre>
<pre><code class="lang-json">varable <span class="hljs-string">"public_key"</span> {} 
module <span class="hljs-string">"subnet_module"</span> { 
    source     = <span class="hljs-attr">"./modules"</span> 
    ... 
    publc_key = var.public_key 
}
</code></pre>
<pre><code class="lang-json">public_key = <span class="hljs-string">"enter-your-public-key"</span>
</code></pre>
<p>We will run <strong>terraform apply</strong> to create the resources. You can run terraform plan before to see what resources you are actually creating.</p>
<p>On the terminal, let's run <code>terraform apply -var-file=secrets.tfvars</code>:</p>
<p><img src="https://paper-attachments.dropbox.com/s_0475B692F95FDEC0A6E8498B95C86079E0E8D8D5196F9F4DEAA5AA6D3B79CB44_1656907792871_terminal-state-4.PNG" alt="terminal resources" width="600" height="400" loading="lazy">
<em>terminal resources</em></p>
<p>Here's the output in the AWS console:</p>
<p><img src="https://paper-attachments.dropbox.com/s_0475B692F95FDEC0A6E8498B95C86079E0E8D8D5196F9F4DEAA5AA6D3B79CB44_1656907897195_bastion.PNG" alt="aws-console instances" width="600" height="400" loading="lazy">
<em>aws-console instances</em></p>
<h2 id="heading-how-to-provision-our-compute-service">How to Provision our Compute Service</h2>
<h3 id="heading-jenkins-master-instance">Jenkins master instance</h3>
<p>So far, we have successfully been able to set up our VPC and networking topology. ‌‌Finally, we will create our Jenkins EC2 instance that will use a Jenkins master AMI baked by Packer.</p>
<p>You can check out my previous article on how it was baked: <a target="_blank" href="https://www.freecodecamp.org/news/learn-instructure-as-a-code-by-building-custom-machine-image-in-aws/">Learn Infrastructure as Code by Building a Custom Machine Image in AWS on freecodecamp.org</a>. Regardless, you can used any of your custom images if you have one.</p>
<pre><code class="lang-json"> <span class="hljs-comment">/*** This parameter contains our baked AMI ID fetch from the Amazon Console*/</span> data <span class="hljs-string">"aws_ami"</span> <span class="hljs-string">"jenkins-master"</span> { 
     most_recent = true owners      = [<span class="hljs-attr">"self"</span>] 
} 

resource <span class="hljs-string">"aws_security_group"</span> <span class="hljs-string">"jenkins_master_sg"</span> { 
    name        = <span class="hljs-attr">"jenkins_master_sg"</span> 
    description = <span class="hljs-attr">"Allow traffic on port 8080 and enable SSH"</span> 
    vpc_id      = var.vpc_id 

    ingress { 
        from_port       = <span class="hljs-attr">"22"</span> 
        to_port         = <span class="hljs-attr">"22"</span> 
        protocol        = <span class="hljs-attr">"tcp"</span> 
        security_groups = [aws_security_group.bastion.id] 
   } 
   ingress { 
           from_port       = <span class="hljs-attr">"8080"</span> 
        to_port         = <span class="hljs-attr">"8080"</span> 
        protocol        = <span class="hljs-attr">"tcp"</span> 
        security_groups = [aws_security_group.lb.id] 
   } 
   ingress { 
           from_port   = <span class="hljs-attr">"8080"</span> 
        to_port     = <span class="hljs-attr">"8080"</span> 
        protocol    = <span class="hljs-attr">"tcp"</span> 
        cidr_blocks = [<span class="hljs-attr">"0.0.0.0/0"</span>] 
  } 
  egress { 
          from_port   = <span class="hljs-attr">"0"</span> 
        to_port     = <span class="hljs-attr">"0"</span> 
        protocol    = <span class="hljs-attr">"-1"</span> 
        cidr_blocks = [<span class="hljs-attr">"0.0.0.0/0"</span>] 
  } 

  tags = { 
      Name = <span class="hljs-attr">"jenkins_master_sg"</span> 
  }
}
</code></pre>
<p>Attaching a security group to the instance will enable inbound traffic on port 8080 (the Jenkins web dashboard) and SSH only from the bastion server and the VPC CIDR block.</p>
<pre><code class="lang-json">resource <span class="hljs-string">"aws_key_pair"</span> <span class="hljs-string">"jenkins"</span> { 
    key_name   = <span class="hljs-attr">"key-jenkins"</span> 
    public_key = var.public_key 
} 

resource <span class="hljs-string">"aws_instance"</span> <span class="hljs-string">"jenkins_master"</span> { 
    ami       = data.aws_ami.jenkins-master.id 
    instance_type  = <span class="hljs-attr">"t2.large"</span> 
    key_name       = aws_key_pair.jenkins.key_name 
    vpc_security_group_ids = [aws_security_group.jenkins_master_sg.id]
    subnet_id              = element(aws_subnet.private_subnets, 0).id
    root_block_device { 
        volume_type           = <span class="hljs-attr">"gp3"</span> 
        volume_size           = 30 
        delete_on_termination = false 
    } 

    tags = { 
        Name = <span class="hljs-attr">"jenkins_master"</span> 
     } 
 }
</code></pre>
<p>Next, we create a variable and define the instance type that we used to deploy the EC2 instance. We won't be allocating executors or workers on the master, so t2.large (8 GB of memory and 2vCPU) should be adequate for the purposes of simplicity.</p>
<p>Thus, build jobs won't cause the Jenkins master to get overcrowded. But Jenkins' memory requirements vary depending on your project's build requirements and the tools used in those builds. It will require two to three threads, or at least 2 MB of memory, to connect to each build node.</p>
<p>Just a note: consider installing Jenkins workers to prevent overworking the master. As a result, a general-purpose instance can host a Jenkins master and offer a balance between computation and memory resources. In order to maintain the article's simplicity, we won't do that.</p>
<h2 id="heading-how-to-create-the-load-balancer">How to Create the Load Balancer</h2>
<p>To access the Jenkins dashboard, we will create a public load balancer in front of the EC2 instance.</p>
<p>This Elastic load balancer will accept HTTP traffic on port 80 and forward it to the EC2 instance on port 8080. Also, it automatically checks the health of the registered EC2 instance on port 8080. If the Elastic Load Balancing (ELB) finds the instance unhealthy, it stops sending traffic to the Jenkins instance.</p>
<pre><code class="lang-json"> <span class="hljs-comment">/*** A security group to allow SSH access into our load balancer*/</span> resource <span class="hljs-string">"aws_security_group"</span> <span class="hljs-string">"lb"</span> { 
     name   = <span class="hljs-attr">"ecs-alb-security-group"</span> 
    vpc_id = var.vpc_id 

    ingress { 
        protocol    = <span class="hljs-attr">"tcp"</span> 
        from_port   = 80 
        to_port     = 80 
        cidr_blocks = [<span class="hljs-attr">"0.0.0.0/0"</span>] 
     } 
     egress { 
         from_port   = 0 
        to_port     = 0 
        protocol    = <span class="hljs-attr">"-1"</span> 
        cidr_blocks = [<span class="hljs-attr">"0.0.0.0/0"</span>] 
     } 

     tags = { 
         Name = <span class="hljs-attr">"jenkins-lb-sg"</span> 
      } 
 } 

 <span class="hljs-comment">/***Load Balancer to be attached to the ECS cluster to distribute the load among instances*/</span> 

 resource <span class="hljs-string">"aws_elb"</span> <span class="hljs-string">"jenkins_elb"</span> { 
     subnets    = [for subnet in aws_subnet.public_subnets : subnet.id]
    cross_zone_load_balancing = <span class="hljs-literal">true</span> 
    security_groups       = [aws_security_group.lb.id] 
    instances             = [aws_instance.jenkins_master.id] 

    listener { 
        instance_port     = 8080 
        instance_protocol = <span class="hljs-attr">"http"</span> 
        lb_port           = 80 
        lb_protocol       = <span class="hljs-attr">"http"</span> 
     } 

     health_check { 
         healthy_threshold   = 2 
        unhealthy_threshold = 2 
        timeout             = 3 
        target              = <span class="hljs-attr">"TCP:8080"</span>    
        interval            = 5 
    } 

    tags = { 
        Name = <span class="hljs-attr">"jenkins_elb"</span> 
    } 
 } 

 output <span class="hljs-string">"load-balancer-ip"</span> { 
     value = aws_elb.jenkins_elb.dns_name 
 }
</code></pre>
<p>Before, we do our terraform apply, let’s update our development/output.tf folder to output the load balancer DNS:</p>
<pre><code class="lang-json"> output <span class="hljs-string">"load-balancer-ip"</span> { 
     value = module.subnet_module.load-balancer-ip
 }
</code></pre>
<p>On the terminal, run the following command: <code>terraform apply -var-file="secrets.tfvars"</code>. Which will give you this:</p>
<p><img src="https://paper-attachments.dropbox.com/s_0475B692F95FDEC0A6E8498B95C86079E0E8D8D5196F9F4DEAA5AA6D3B79CB44_1657669467370_load-balancer.PNG" alt="load balancer output" width="600" height="400" loading="lazy">
<em>load balancer output</em></p>
<p>After you apply the changes with Terraform, the Jenkins master load balancer URL should be displayed in your terminal session.</p>
<p>Point your favorite browser to the URL, and you should have access to the Jenkins web dashboard.</p>
<p><img src="https://paper-attachments.dropbox.com/s_0475B692F95FDEC0A6E8498B95C86079E0E8D8D5196F9F4DEAA5AA6D3B79CB44_1657669441393_jenkns.PNG" alt="jenkins-instances" width="600" height="400" loading="lazy">
<em>jenkins-instances</em></p>
<p>Then just follow the screen instructions to UNLOCK.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/screencapture-ec2-3-87-146-72-compute-1-amazonaws-8080-2022-05-25-05_38_35-1.png" alt="unlock jenkins" width="600" height="400" loading="lazy">
<em>unlock jenkins</em></p>
<p>You can find the <a target="_blank" href="https://github.com/Caesarsage/terraform-jenkins-instance">full code at this GitHub repo.</a></p>
<h2 id="heading-cleaning-up">Cleaning Up</h2>
<p>To avoid the unnecessary cost of running AWS services, you will need to run the following command to destroy all created and running resources:‌‌<code>terraform destroy -var-file="secrets.tfvars"</code> which should give this output:</p>
<p><img src="https://paper-attachments.dropbox.com/s_0475B692F95FDEC0A6E8498B95C86079E0E8D8D5196F9F4DEAA5AA6D3B79CB44_1657669741091_destroy.PNG" alt="destroy resources" width="600" height="400" loading="lazy">
<em>destroy resources</em></p>
<p>How interesting, right? With just few lines of code we can destroy and spin up our resources.</p>
<h2 id="heading-summary">Summary</h2>
<p>In this tutorial, you have learned how to use Terraform at a high level. You've also learned one of its applications by provisioning a Jenkins server on the AWS cloud platform.</p>
<p>You have also learned about best practices of Terraform backend states and modules.</p>
<p>To learn more about Terraform and its many use-cases, you can check out the official <a target="_blank" href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs">Terraform docs here</a>.</p>
<p>Happy Learning!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Learn Terraform and Azure by Building a Dev Environment ]]>
                </title>
                <description>
                    <![CDATA[ Terraform is an open source infrastructure as code software tool that makes it easy to programmatically to set up infrastructure on a variety of cloud service providers. We just published a course on the freeCodeCamp.org YouTube channel that will tea... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/learn-terraform-and-azure-by-building-a-dev-environment/</link>
                <guid isPermaLink="false">66b2052339b555ffda8bfeaa</guid>
                
                    <category>
                        <![CDATA[ Azure ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Terraform ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Wed, 29 Jun 2022 14:35:30 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/06/terraformazure.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Terraform is an open source infrastructure as code software tool that makes it easy to programmatically to set up infrastructure on a variety of cloud service providers.</p>
<p>We just published a course on the freeCodeCamp.org YouTube channel that will teach you how to use Terraform and Azure to setup a development environment.</p>
<p>We previously posted a similar course with AWS instead of Azure.</p>
<p>Derek Morgan created this course. He has created a bunch of courses to prepare students for a variety of technical certifications.</p>
<p>This course is designed to help you learn the basics of Terraform fast with a hands-on project that can serve as a framework for your own projects!</p>
<p>This project will guide you through Terraform basics as you utilize Visual Studio Code (on Windows, Mac, or Linux!) to deploy Azure resources and an Azure VM that you can SSH into to have your own redeployable environment that will be perfect for your own future projects!</p>
<p>Here are the sections covered in this course:</p>
<ul>
<li>Welcome and Setup</li>
<li>Terraform Provider Init</li>
<li>A Resource Group</li>
<li>A Virtual Network and Referencing other Resources </li>
<li>Terraform State</li>
<li>Terraform Destroy</li>
<li>A Subnet</li>
<li>A Security Group</li>
<li>Security Group Associations</li>
<li>A Public IP</li>
<li>A Network Interface</li>
<li>A Key Pair</li>
<li>Custom Data</li>
<li>SSH Config Scripts</li>
<li>The Provisioner</li>
<li>Data Sources</li>
<li>Outputs</li>
<li>Variables</li>
<li>Variable Precedence</li>
<li>Conditionals</li>
</ul>
<p>Watch the full course below or on <a target="_blank" href="https://youtu.be/iRaai1IBlB0">the freeCodeCamp.org YouTube channel</a> (2-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/V53AHWun17s" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Learn Terraform and AWS by Building a Dev Environment ]]>
                </title>
                <description>
                    <![CDATA[ Terraform is an open source infrastructure as code software tool that makes it easy to programmatically to set up infrastructure on a variety of cloud service providers. We just published a course on the freeCodeCamp.org YouTube channel that will tea... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/learn-terraform-and-aws-by-building-a-dev-environment/</link>
                <guid isPermaLink="false">66b2052127569435a9255ad8</guid>
                
                    <category>
                        <![CDATA[ Terraform ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Tue, 12 Apr 2022 19:32:38 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/04/terraform.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Terraform is an open source infrastructure as code software tool that makes it easy to programmatically to set up infrastructure on a variety of cloud service providers.</p>
<p>We just published a course on the freeCodeCamp.org YouTube channel that will teach you how to use Terraform and AWS to setup a development environment.</p>
<p>Derek Morgan created this course. He has created a bunch of courses to prepare students for a variety of technical certifications.</p>
<p>This course is designed to help you learn the basics of Terraform fast with a hands-on project that can serve as a framework for your own projects!</p>
<p>This project will guide you through Terraform basics as you utilize Visual Studio Code (on Windows, Mac, or Linux) to deploy AWS resources and an EC2 instance that you can SSH into to have your own redeployable environment that you can use for future projects.</p>
<p>Here are the sections covered in this course:</p>
<ul>
<li>Welcome and Setup</li>
<li>What We're Going to Build</li>
<li>AWS IAM Setup</li>
<li>Local Environment Setup</li>
<li>Let's Build!</li>
<li>AWS Provider and Terraform Init</li>
<li>A VPC and Terraform Apply</li>
<li>The Terraform State</li>
<li>Terraform Destroy</li>
<li>A Subnet and Referencing</li>
<li>An IGW and Terraform fmt</li>
<li>A Route Table</li>
<li>A Route Table Association</li>
<li>A Security Group</li>
<li>An AMI Datasource</li>
<li>A Key Pair</li>
<li>An EC2 Instance</li>
<li>Userdata and the File Function</li>
<li>SSH Config Scripts</li>
<li>The Provisioner and Templatefile</li>
<li>The Deployment and Replace</li>
<li>Variables</li>
<li>Variable Precedence</li>
<li>Conditional Expressions</li>
<li>Outputs</li>
<li>Conclusion</li>
</ul>
<p>Watch the full course below or on <a target="_blank" href="https://youtu.be/iRaai1IBlB0">the freeCodeCamp.org YouTube channel</a> (2-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/iRaai1IBlB0" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
