<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Akhilesh Mishra</title>
    <description>The latest articles on Forem by Akhilesh Mishra (@livingdevops).</description>
    <link>https://forem.com/livingdevops</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1363514%2Faf4b7195-ce89-4c82-a620-387cde875747.jpg</url>
      <title>Forem: Akhilesh Mishra</title>
      <link>https://forem.com/livingdevops</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/livingdevops"/>
    <language>en</language>
    <item>
      <title>Every Kubernetes Concept Has a Story</title>
      <dc:creator>Akhilesh Mishra</dc:creator>
      <pubDate>Sat, 21 Mar 2026 22:26:23 +0000</pubDate>
      <link>https://forem.com/livingdevops/every-kubernetes-concept-has-a-story-49g2</link>
      <guid>https://forem.com/livingdevops/every-kubernetes-concept-has-a-story-49g2</guid>
      <description>&lt;p&gt;Kubernetes Concepts are not random; each one has a beautiful problem-solving story behind it. &lt;/p&gt;

&lt;p&gt;Most people learn Kubernetes the wrong way.&lt;/p&gt;

&lt;p&gt;They see it as a list of concepts. Pods. Deployments. Services. Ingress. ConfigMaps. Secrets. HPA. They memorize them without understanding why they exist.&lt;/p&gt;

&lt;p&gt;Every concept in Kubernetes exists because something broke. Someone ran it in production, something failed, and a new concept was born to fix it.&lt;/p&gt;

&lt;p&gt;Let me show you the full story in order.&lt;/p&gt;

&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;You start with a Pod.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;/blockquote&gt;

&lt;p&gt;A pod runs your container. Simple. Clean. Done.&lt;/p&gt;

&lt;p&gt;Until it crashes.&lt;/p&gt;

&lt;p&gt;Nobody restarts it. It is just gone.&lt;/p&gt;

&lt;p&gt;In production, that is not acceptable.&lt;/p&gt;

&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;So you use a Deployment.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;/blockquote&gt;

&lt;p&gt;A Deployment watches your pods.&lt;/p&gt;

&lt;p&gt;One dies and it creates another.&lt;/p&gt;

&lt;p&gt;You want 3 running, it keeps 3 running.&lt;/p&gt;

&lt;p&gt;You want to scale to 10, one command does it.&lt;/p&gt;

&lt;p&gt;Pods were too fragile for production. Deployment fixed that.&lt;/p&gt;

&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;But now you have a new problem.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;/blockquote&gt;

&lt;p&gt;Every pod gets a new IP when it restarts.&lt;/p&gt;

&lt;p&gt;You have 3 pods running your app.&lt;/p&gt;

&lt;p&gt;Another service needs to talk to them.&lt;/p&gt;

&lt;p&gt;Which IP do you use? They keep changing.&lt;/p&gt;

&lt;p&gt;You cannot hardcode them. You cannot track them at scale.&lt;/p&gt;

&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;So you use a Service.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;/blockquote&gt;

&lt;p&gt;A Service gives your app one stable IP address.&lt;/p&gt;

&lt;p&gt;It finds your pods using labels, not IPs.&lt;/p&gt;

&lt;p&gt;Pods die and come back with new IPs. The Service does not care.&lt;/p&gt;

&lt;p&gt;It always finds them. It also load balances.&lt;/p&gt;

&lt;p&gt;Traffic coming in gets distributed across all healthy pods automatically.&lt;/p&gt;

&lt;p&gt;Pods had unstable IPs. Service fixed that.&lt;/p&gt;

&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;But your app still needs to be accessible from the internet.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;/blockquote&gt;

&lt;p&gt;So you use a LoadBalancer Service.&lt;/p&gt;

&lt;p&gt;This creates a real cloud load balancer. AWS ALB. Azure LB. GCP LB.&lt;/p&gt;

&lt;p&gt;Your app gets a public endpoint.&lt;/p&gt;

&lt;p&gt;Works perfectly. Until you have 10 services.&lt;/p&gt;

&lt;p&gt;Now you have 10 load balancers. Each one costs money every single month.&lt;/p&gt;

&lt;p&gt;Your cloud bill does not care that 6 of them handle almost no traffic.&lt;/p&gt;

&lt;p&gt;LoadBalancer Services solved external access. But one per service does not scale.&lt;/p&gt;

&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;So you use Ingress.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;/blockquote&gt;

&lt;p&gt;One load balancer. All your services behind it.&lt;/p&gt;

&lt;p&gt;Ingress routes traffic based on rules.&lt;/p&gt;

&lt;p&gt;Request comes in for /api, goes to the API service.&lt;/p&gt;

&lt;p&gt;Request comes in for /dashboard, goes to the frontend service.&lt;/p&gt;

&lt;p&gt;One entry point. Smart routing. One cloud load balancer on your bill.&lt;/p&gt;

&lt;p&gt;But Ingress is just a set of rules. Something has to execute those rules.&lt;/p&gt;

&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;So you use an Ingress Controller.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;/blockquote&gt;

&lt;p&gt;Nginx. Traefik. AWS Load Balancer Controller.&lt;/p&gt;

&lt;p&gt;These are the actual engines that read your Ingress rules and make the routing happen.&lt;/p&gt;

&lt;p&gt;Ingress without a controller is just a config file nobody reads.&lt;/p&gt;

&lt;p&gt;Ingress Controller made the rules actually work.&lt;/p&gt;

&lt;p&gt;If you want to learn Kubernetes the way it works in production, I am starting a 9-week advanced Kubernetes on AWS bootcamp where we build real production projects, troubleshoot live incidents, write RCAs, and implement AIops on Kubernetes. Checkout 👇&lt;/p&gt;

&lt;p&gt;9-week Advanced Kubernetes Bootcamp on AWS (EKS) + AIops&lt;/p&gt;

&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Now your app is running. But it needs configuration.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;/blockquote&gt;

&lt;p&gt;Database URL. API keys. Environment name. Feature flags.&lt;/p&gt;

&lt;p&gt;So you do what feels natural. You hardcode them inside the container.&lt;/p&gt;

&lt;p&gt;It works on your laptop.&lt;/p&gt;

&lt;p&gt;You deploy to staging. Wrong database URL.&lt;/p&gt;

&lt;p&gt;You deploy to production. Wrong API key.&lt;/p&gt;

&lt;p&gt;You fix it by rebuilding the image every time config changes.&lt;/p&gt;

&lt;p&gt;In production, rebuilding an image to change a config value is not acceptable.&lt;/p&gt;

&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;So you use a ConfigMap.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;/blockquote&gt;

&lt;p&gt;A ConfigMap holds your configuration outside the container.&lt;/p&gt;

&lt;p&gt;You inject it into your pod at runtime as environment variables or a mounted file.&lt;/p&gt;

&lt;p&gt;Change the ConfigMap. Redeploy. Your app picks up the new values.&lt;/p&gt;

&lt;p&gt;The image never changes. The config does.&lt;/p&gt;

&lt;p&gt;Same image runs in dev, staging and production with different configs.&lt;/p&gt;

&lt;p&gt;Hardcoded config made your image environment-specific. ConfigMap fixed that.&lt;/p&gt;

&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;But now you have a new problem.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;/blockquote&gt;

&lt;p&gt;Your database password is sitting in a ConfigMap.&lt;/p&gt;

&lt;p&gt;ConfigMaps are not encrypted.&lt;/p&gt;

&lt;p&gt;Anyone with basic kubectl access can read them.&lt;/p&gt;

&lt;p&gt;You just stored your production database credentials in plain text inside your cluster.&lt;/p&gt;

&lt;p&gt;That is not a mistake. That is a security incident.&lt;/p&gt;

&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;So you use a Secret.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;/blockquote&gt;

&lt;p&gt;A Secret holds sensitive data. Passwords. Tokens. Certificates. API keys.&lt;/p&gt;

&lt;p&gt;Stored separately from ConfigMaps with its own access controls.&lt;/p&gt;

&lt;p&gt;Your app reads it at runtime. Your image never sees it.&lt;/p&gt;

&lt;p&gt;You control who in the cluster can access which Secret.&lt;/p&gt;

&lt;p&gt;ConfigMaps were not safe for sensitive data. Secrets fixed that.&lt;/p&gt;

&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Now traffic starts growing. And manual scaling breaks you.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;/blockquote&gt;

&lt;p&gt;Some days 100 users. Some days 10,000.&lt;/p&gt;

&lt;p&gt;You are running 3 pods. On a busy day all three are maxed out.&lt;/p&gt;

&lt;p&gt;Users are seeing slow responses. Requests are timing out.&lt;/p&gt;

&lt;p&gt;You jump on, run a scale command, bump it to 8 pods. Crisis over.&lt;/p&gt;

&lt;p&gt;Traffic drops at night. 8 pods sitting idle. Wasting money.&lt;/p&gt;

&lt;p&gt;Next spike you do it all over again.&lt;/p&gt;

&lt;p&gt;You cannot babysit your cluster every time traffic changes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;So you use HPA. Horizontal Pod Autoscaler.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;/blockquote&gt;

&lt;p&gt;HPA watches your pods continuously.&lt;/p&gt;

&lt;p&gt;CPU goes above 70 percent. It adds more pods automatically.&lt;/p&gt;

&lt;p&gt;Traffic drops. It scales back down automatically.&lt;/p&gt;

&lt;p&gt;You define the minimum and maximum. Kubernetes does the rest.&lt;/p&gt;

&lt;p&gt;Your app handles the spike. You are not woken up at 2am to manually scale.&lt;/p&gt;

&lt;p&gt;Manual scaling could not keep up with real traffic. HPA fixed that.&lt;/p&gt;

&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;But scaling pods created a new problem.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;/blockquote&gt;

&lt;p&gt;HPA adds pods during a traffic spike.&lt;/p&gt;

&lt;p&gt;Your nodes are full.&lt;/p&gt;

&lt;p&gt;The new pods sit in Pending state.&lt;/p&gt;

&lt;p&gt;They cannot be scheduled because there is no capacity.&lt;/p&gt;

&lt;p&gt;HPA did its job. But your cluster had nowhere to put the pods.&lt;/p&gt;

&lt;p&gt;Scaling pods without scaling nodes is half a solution.&lt;/p&gt;

&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;So you use Cluster Autoscaler or Karpenter.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;/blockquote&gt;

&lt;p&gt;They watch for pods stuck in Pending state.&lt;/p&gt;

&lt;p&gt;Not enough capacity. They add a new node automatically.&lt;/p&gt;

&lt;p&gt;Pending pods get scheduled. Traffic is handled.&lt;/p&gt;

&lt;p&gt;Load drops. Nodes sit underutilized. They remove them automatically.&lt;/p&gt;

&lt;p&gt;You only pay for the compute you actually need.&lt;/p&gt;

&lt;p&gt;On EKS, Karpenter is the better choice. It is faster and more cost efficient. It provisions the exact right node for your workload instead of waiting for a fixed node group to scale.&lt;/p&gt;

&lt;p&gt;HPA scaled your pods. Karpenter scaled your nodes. Together they make your cluster truly elastic.&lt;/p&gt;

&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;But one last problem. And it is the one that takes things down silently.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;/blockquote&gt;

&lt;p&gt;Everything is scaling. Pods are coming up. Nodes are being added.&lt;/p&gt;

&lt;p&gt;One pod starts consuming 4GB of memory. It was never supposed to.&lt;/p&gt;

&lt;p&gt;But nobody told Kubernetes that. So it keeps consuming.&lt;/p&gt;

&lt;p&gt;It starves every other pod on that node.&lt;/p&gt;

&lt;p&gt;Those pods start failing. A cascade begins.&lt;/p&gt;

&lt;p&gt;One rogue pod. No limits. Your entire node is affected.&lt;/p&gt;

&lt;p&gt;An unpredictable cluster is an unreliable cluster.&lt;/p&gt;

&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;So you use Resource Requests and Limits.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;/blockquote&gt;

&lt;p&gt;Requests tell Kubernetes the minimum your pod needs to be scheduled on a node.&lt;/p&gt;

&lt;p&gt;Limits tell Kubernetes the maximum it is ever allowed to consume.&lt;/p&gt;

&lt;p&gt;The scheduler places pods intelligently across nodes using requests.&lt;/p&gt;

&lt;p&gt;Limits make sure one noisy pod cannot take down everything around it.&lt;/p&gt;

&lt;p&gt;Your cluster runs predictably. Every pod gets what it needs. Nothing more.&lt;/p&gt;

&lt;p&gt;Uncontrolled resource usage made your cluster unpredictable. Requests and Limits fixed that.&lt;/p&gt;

&lt;h2&gt;
  
  
  To summarize the full story:
&lt;/h2&gt;

&lt;p&gt;Pod ran your app but had no resilience. Deployment fixed that.&lt;/p&gt;

&lt;p&gt;Pods had unstable IPs. Service fixed that.&lt;/p&gt;

&lt;p&gt;One load balancer per service was too expensive. Ingress fixed that.&lt;/p&gt;

&lt;p&gt;Ingress needed something to execute its rules. Ingress Controller fixed that.&lt;/p&gt;

&lt;p&gt;Hardcoded config made images inflexible. ConfigMap fixed that.&lt;/p&gt;

&lt;p&gt;ConfigMaps were not safe for sensitive data. Secrets fixed that.&lt;/p&gt;

&lt;p&gt;Manual scaling could not keep up with traffic. HPA fixed that.&lt;/p&gt;

&lt;p&gt;Pod scaling without node scaling left pods pending. Karpenter fixed that.&lt;/p&gt;

&lt;p&gt;Uncontrolled resource usage made clusters unpredictable. Requests and Limits fixed that.&lt;/p&gt;

&lt;p&gt;Each concept exists because the previous one was not enough.&lt;/p&gt;

&lt;p&gt;That is how you stop memorizing Kubernetes and start understanding it.&lt;/p&gt;

</description>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Deploy Secure Google Cloud VMs with Terraform and GitHub Actions in 2025</title>
      <dc:creator>Akhilesh Mishra</dc:creator>
      <pubDate>Wed, 23 Jul 2025 09:57:44 +0000</pubDate>
      <link>https://forem.com/livingdevops/deploy-secure-google-cloud-vms-with-terraform-and-github-actions-in-2025-2k1o</link>
      <guid>https://forem.com/livingdevops/deploy-secure-google-cloud-vms-with-terraform-and-github-actions-in-2025-2k1o</guid>
      <description>&lt;h2&gt;
  
  
  Automate VM provisioning, networking, and CI/CD pipelines using Infrastructure as Code best practices
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Part 2 of our comprehensive Terraform on Google Cloud series - Building on your VPC foundation with compute resources and automation&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ready to automate your VM deployments like a DevOps pro?&lt;/strong&gt; This tutorial builds directly on our Part 1 foundation, adding secure Compute Engine instances with automated GitHub Actions workflows. You'll master firewall rules, Cloud NAT setup, and production-ready CI/CD patterns.&lt;/p&gt;

&lt;p&gt;By the end of this guide, you'll have a complete automated pipeline that deploys VMs to Google Cloud every time you push code to your GitHub repository.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Perfect for teams wanting to eliminate manual infrastructure deployments and embrace true DevOps automation.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What You'll Learn in This Guide
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Secure VM provisioning&lt;/strong&gt; with proper service accounts and networking&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Firewall rule management&lt;/strong&gt; for SSH access and internet connectivity
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud NAT configuration&lt;/strong&gt; for private VM internet access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Actions automation&lt;/strong&gt; with service account authentication&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production CI/CD patterns&lt;/strong&gt; for infrastructure deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before starting this tutorial, ensure you have:&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Completed Part 1&lt;/strong&gt; of this series (VPC and storage setup)&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;GitHub account&lt;/strong&gt; with repository access&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Git installed&lt;/strong&gt; locally with basic knowledge&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;GitHub personal access token&lt;/strong&gt; created&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Same GCP project&lt;/strong&gt; from Part 1 with billing enabled&lt;/p&gt;
&lt;h2&gt;
  
  
  Understanding Google Cloud Compute Components
&lt;/h2&gt;
&lt;h3&gt;
  
  
  What are Firewall Rules in GCP?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Firewall rules&lt;/strong&gt; in Google Cloud are security policies that control inbound and outbound traffic to your VM instances and other resources within a VPC network. They work at the network level and are stateful - meaning return traffic is automatically allowed.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Direction&lt;/strong&gt;: INGRESS (incoming) or EGRESS (outgoing) traffic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Priority&lt;/strong&gt;: Lower numbers = higher priority (0-65534)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Targets&lt;/strong&gt;: Which resources the rule applies to (tags, service accounts)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sources/Destinations&lt;/strong&gt;: Where traffic can come from or go to&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protocols and Ports&lt;/strong&gt;: What type of traffic is allowed&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  What is Cloud NAT?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cloud NAT (Network Address Translation)&lt;/strong&gt; provides outbound internet connectivity for VM instances that only have private IP addresses. This is essential for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt;: VMs don't need public IPs to access the internet&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost optimization&lt;/strong&gt;: Fewer external IP addresses needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance&lt;/strong&gt;: Keeps resources private while allowing necessary internet access&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  What is Cloud Router?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cloud Router&lt;/strong&gt; is a networking service that provides dynamic routing capabilities. It's required for Cloud NAT and enables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic routing&lt;/strong&gt; between VPCs and on-premises networks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BGP support&lt;/strong&gt; for advanced networking scenarios&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NAT gateway functionality&lt;/strong&gt; when paired with Cloud NAT&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Extending Your Terraform Configuration
&lt;/h2&gt;

&lt;p&gt;Let's add the new variables and resources to your existing Terraform setup from Part 1.&lt;/p&gt;
&lt;h3&gt;
  
  
  Add VM Variables (&lt;code&gt;variables.tf&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Add these variables to your existing &lt;code&gt;variables.tf&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"zone"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"GCP zone for VM deployment"&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-central1-b"&lt;/span&gt;
  &lt;span class="nx"&gt;validation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;can&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"^[a-z]+-[a-z]+[0-9]+-[a-z]$"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Zone must be a valid GCP zone format (e.g., us-central1-b)."&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"vm_machine_type"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Machine type for the VM instance"&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"e2-standard-2"&lt;/span&gt;
  &lt;span class="nx"&gt;validation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="s2"&gt;"e2-micro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"e2-small"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"e2-medium"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"e2-standard-2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="s2"&gt;"e2-standard-4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"n1-standard-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"n1-standard-2"&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vm_machine_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Machine type must be a valid GCP machine type."&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"prefix"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Prefix for resource naming"&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt;
  &lt;span class="nx"&gt;validation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;can&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"^[a-z][a-z0-9-]{1,10}$"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Prefix must start with a letter, contain only lowercase letters, numbers, and hyphens, and be 2-11 characters long."&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"vm_disk_size"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Boot disk size in GB"&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
  &lt;span class="nx"&gt;validation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vm_disk_size&lt;/span&gt; &lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="err"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vm_disk_size&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"VM disk size must be between 10 and 2000 GB."&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"vm_image"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"VM boot disk image"&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"debian-cloud/debian-12"&lt;/span&gt;
  &lt;span class="nx"&gt;validation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="s2"&gt;"debian-cloud/debian-12"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"ubuntu-os-cloud/ubuntu-2204-lts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"centos-cloud/centos-7"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"rhel-cloud/rhel-8"&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vm_image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"VM image must be a supported OS image."&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Update Variable Values (&lt;code&gt;terraform.tfvars&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Add these values to your existing &lt;code&gt;terraform.tfvars&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Existing variables from Part 1&lt;/span&gt;
&lt;span class="nx"&gt;project_id&lt;/span&gt;  &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"your-gcp-project-id"&lt;/span&gt;
&lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt;
&lt;span class="nx"&gt;region&lt;/span&gt;      &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-central1"&lt;/span&gt;
&lt;span class="nx"&gt;vpc_name&lt;/span&gt;    &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"main-vpc"&lt;/span&gt;
&lt;span class="nx"&gt;subnet_name&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"primary-subnet"&lt;/span&gt;
&lt;span class="nx"&gt;subnet_cidr&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.1.0/24"&lt;/span&gt;

&lt;span class="c1"&gt;# New VM variables&lt;/span&gt;
&lt;span class="nx"&gt;zone&lt;/span&gt;            &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-central1-b"&lt;/span&gt;
&lt;span class="nx"&gt;vm_machine_type&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"e2-standard-2"&lt;/span&gt;
&lt;span class="nx"&gt;prefix&lt;/span&gt;          &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"demo"&lt;/span&gt;
&lt;span class="nx"&gt;vm_disk_size&lt;/span&gt;    &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
&lt;span class="nx"&gt;vm_image&lt;/span&gt;        &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"debian-cloud/debian-12"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create VM Resources (&lt;code&gt;vm.tf&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Create a new file &lt;code&gt;vm.tf&lt;/code&gt; for your compute resources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Service account for VM instances&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_service_account"&lt;/span&gt; &lt;span class="s2"&gt;"vm_service_account"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;project&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_id&lt;/span&gt;
  &lt;span class="nx"&gt;account_id&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.prefix}-vm-sa"&lt;/span&gt;
  &lt;span class="nx"&gt;display_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"VM Service Account for ${var.environment}"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Service account for VM instances with minimal required permissions"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# IAM roles for VM service account&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_project_iam_member"&lt;/span&gt; &lt;span class="s2"&gt;"vm_sa_logging"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;project&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_id&lt;/span&gt;
  &lt;span class="nx"&gt;role&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"roles/logging.logWriter"&lt;/span&gt;
  &lt;span class="nx"&gt;member&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"serviceAccount:${google_service_account.vm_service_account.email}"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_project_iam_member"&lt;/span&gt; &lt;span class="s2"&gt;"vm_sa_monitoring"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;project&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_id&lt;/span&gt;
  &lt;span class="nx"&gt;role&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"roles/monitoring.metricWriter"&lt;/span&gt;
  &lt;span class="nx"&gt;member&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"serviceAccount:${google_service_account.vm_service_account.email}"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Compute Engine VM instance&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_compute_instance"&lt;/span&gt; &lt;span class="s2"&gt;"main_vm"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.prefix}-${var.environment}-vm"&lt;/span&gt;
  &lt;span class="nx"&gt;project&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_id&lt;/span&gt;
  &lt;span class="nx"&gt;machine_type&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vm_machine_type&lt;/span&gt;
  &lt;span class="nx"&gt;zone&lt;/span&gt;                      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zone&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt;                      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ssh-allowed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"http-server"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;allow_stopping_for_update&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="c1"&gt;# Enable deletion protection in production&lt;/span&gt;
  &lt;span class="nx"&gt;deletion_protection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"prod"&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

  &lt;span class="c1"&gt;# Boot disk configuration&lt;/span&gt;
  &lt;span class="nx"&gt;boot_disk&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;initialize_params&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vm_image&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"pd-standard"&lt;/span&gt;
      &lt;span class="nx"&gt;size&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vm_disk_size&lt;/span&gt;
      &lt;span class="nx"&gt;labels&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
        &lt;span class="nx"&gt;managed-by&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;auto_delete&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Network configuration - private IP only&lt;/span&gt;
  &lt;span class="nx"&gt;network_interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;network&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;self_link&lt;/span&gt;
    &lt;span class="nx"&gt;subnetwork&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_subnetwork&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;self_link&lt;/span&gt;

    &lt;span class="c1"&gt;# No external IP - will use Cloud NAT for internet access&lt;/span&gt;
    &lt;span class="c1"&gt;# Uncomment below for external IP:&lt;/span&gt;
    &lt;span class="c1"&gt;# access_config {&lt;/span&gt;
    &lt;span class="c1"&gt;#   nat_ip = google_compute_address.vm_external_ip.address&lt;/span&gt;
    &lt;span class="c1"&gt;# }&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Service account and scopes&lt;/span&gt;
  &lt;span class="nx"&gt;service_account&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;email&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_service_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vm_service_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;
    &lt;span class="nx"&gt;scopes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s2"&gt;"https://www.googleapis.com/auth/cloud-platform"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"https://www.googleapis.com/auth/logging.write"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"https://www.googleapis.com/auth/monitoring.write"&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Metadata for VM configuration&lt;/span&gt;
  &lt;span class="nx"&gt;metadata&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;enable-oslogin&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"TRUE"&lt;/span&gt;
    &lt;span class="nx"&gt;startup-script&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
      #!/bin/bash
      apt-get update
      apt-get install -y nginx
      systemctl start nginx
      systemctl enable nginx
      echo "&amp;lt;h1&amp;gt;Hello from ${var.prefix}-${var.environment}-vm&amp;lt;/h1&amp;gt;" &amp;gt; /var/www/html/index.html
      echo "&amp;lt;p&amp;gt;Deployed with Terraform and GitHub Actions&amp;lt;/p&amp;gt;" &amp;gt;&amp;gt; /var/www/html/index.html
&lt;/span&gt;&lt;span class="no"&gt;    EOF
&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Labels for resource management&lt;/span&gt;
  &lt;span class="nx"&gt;labels&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
    &lt;span class="nx"&gt;managed-by&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform"&lt;/span&gt;
    &lt;span class="nx"&gt;team&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"devops"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Depends on the subnet being ready&lt;/span&gt;
  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;google_compute_subnetwork&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main_subnet&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Optional: Static external IP (commented out for security)&lt;/span&gt;
&lt;span class="c1"&gt;# resource "google_compute_address" "vm_external_ip" {&lt;/span&gt;
&lt;span class="c1"&gt;#   name    = "${var.prefix}-${var.environment}-vm-ip"&lt;/span&gt;
&lt;span class="c1"&gt;#   project = var.project_id&lt;/span&gt;
&lt;span class="c1"&gt;#   region  = var.region&lt;/span&gt;
&lt;span class="c1"&gt;# }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create Firewall Rules (&lt;code&gt;firewall.tf&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Create a new file &lt;code&gt;firewall.tf&lt;/code&gt; for network security:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Ingress firewall rule for SSH access&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_compute_firewall"&lt;/span&gt; &lt;span class="s2"&gt;"allow_ssh"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.environment}-allow-ssh"&lt;/span&gt;
  &lt;span class="nx"&gt;network&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;project&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_id&lt;/span&gt;

  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow SSH access to instances with ssh-allowed tag"&lt;/span&gt;
  &lt;span class="nx"&gt;direction&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"INGRESS"&lt;/span&gt;
  &lt;span class="nx"&gt;priority&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;

  &lt;span class="nx"&gt;allow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;ports&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"22"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Restrict source ranges for better security&lt;/span&gt;
  &lt;span class="c1"&gt;# For production, use your specific IP ranges&lt;/span&gt;
  &lt;span class="nx"&gt;source_ranges&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# Change this in production!&lt;/span&gt;
  &lt;span class="nx"&gt;target_tags&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ssh-allowed"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="c1"&gt;# Log firewall activity&lt;/span&gt;
  &lt;span class="nx"&gt;log_config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;metadata&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"INCLUDE_ALL_METADATA"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Ingress firewall rule for HTTP traffic&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_compute_firewall"&lt;/span&gt; &lt;span class="s2"&gt;"allow_http"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.environment}-allow-http"&lt;/span&gt;
  &lt;span class="nx"&gt;network&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;project&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_id&lt;/span&gt;

  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow HTTP access to web servers"&lt;/span&gt;
  &lt;span class="nx"&gt;direction&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"INGRESS"&lt;/span&gt; 
  &lt;span class="nx"&gt;priority&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;

  &lt;span class="nx"&gt;allow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;ports&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"80"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"8080"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;source_ranges&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;target_tags&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"http-server"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;log_config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;metadata&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"INCLUDE_ALL_METADATA"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Egress firewall rule for internet access&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_compute_firewall"&lt;/span&gt; &lt;span class="s2"&gt;"allow_egress"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.environment}-allow-egress"&lt;/span&gt;
  &lt;span class="nx"&gt;network&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;project&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_id&lt;/span&gt;

  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow outbound internet access"&lt;/span&gt;
  &lt;span class="nx"&gt;direction&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"EGRESS"&lt;/span&gt;
  &lt;span class="nx"&gt;priority&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;

  &lt;span class="nx"&gt;allow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;ports&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"80"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"443"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"53"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;allow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"udp"&lt;/span&gt;
    &lt;span class="nx"&gt;ports&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"53"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"123"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;destination_ranges&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;target_tags&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ssh-allowed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"http-server"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Internal communication firewall rule&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_compute_firewall"&lt;/span&gt; &lt;span class="s2"&gt;"allow_internal"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.environment}-allow-internal"&lt;/span&gt;
  &lt;span class="nx"&gt;network&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;project&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_id&lt;/span&gt;

  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow internal communication within VPC"&lt;/span&gt;
  &lt;span class="nx"&gt;direction&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"INGRESS"&lt;/span&gt;
  &lt;span class="nx"&gt;priority&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;

  &lt;span class="nx"&gt;allow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;ports&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"0-65535"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;allow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"udp"&lt;/span&gt;
    &lt;span class="nx"&gt;ports&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"0-65535"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;allow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"icmp"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Allow traffic from the VPC CIDR&lt;/span&gt;
  &lt;span class="nx"&gt;source_ranges&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subnet_cidr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"10.1.0.0/16"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"10.2.0.0/16"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Update Networking with Cloud NAT (&lt;code&gt;networking.tf&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Add these resources to your existing &lt;code&gt;networking.tf&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Cloud NAT for private VM internet access&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_compute_router_nat"&lt;/span&gt; &lt;span class="s2"&gt;"main_nat"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.environment}-nat-gateway"&lt;/span&gt;
  &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main_router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;

  &lt;span class="c1"&gt;# NAT IP allocation&lt;/span&gt;
  &lt;span class="nx"&gt;nat_ip_allocate_option&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AUTO_ONLY"&lt;/span&gt;

  &lt;span class="c1"&gt;# Which subnets to provide NAT for&lt;/span&gt;
  &lt;span class="nx"&gt;source_subnetwork_ip_ranges_to_nat&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"LIST_OF_SUBNETWORKS"&lt;/span&gt;

  &lt;span class="nx"&gt;subnetwork&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;                    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_subnetwork&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
    &lt;span class="nx"&gt;source_ip_ranges_to_nat&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ALL_IP_RANGES"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Logging for monitoring&lt;/span&gt;
  &lt;span class="nx"&gt;log_config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;enable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="nx"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ERRORS_ONLY"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Timeout settings&lt;/span&gt;
  &lt;span class="nx"&gt;min_ports_per_vm&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;
  &lt;span class="nx"&gt;udp_idle_timeout_sec&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
  &lt;span class="nx"&gt;icmp_idle_timeout_sec&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
  &lt;span class="nx"&gt;tcp_established_idle_timeout_sec&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;
  &lt;span class="nx"&gt;tcp_transitory_idle_timeout_sec&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Update Outputs (&lt;code&gt;outputs.tf&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Add these VM-related outputs to your existing &lt;code&gt;outputs.tf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Existing outputs from Part 1...&lt;/span&gt;

&lt;span class="c1"&gt;# VM outputs&lt;/span&gt;
&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"vm_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main_vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Name of the created VM instance"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"vm_internal_ip"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main_vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;network_interface&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;network_ip&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Internal IP address of the VM"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"vm_zone"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main_vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zone&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Zone where the VM is deployed"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"vm_service_account"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_service_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vm_service_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Service account email used by the VM"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"ssh_command"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"gcloud compute ssh ${google_compute_instance.main_vm.name} --zone=${var.zone} --project=${var.project_id}"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Command to SSH into the VM instance"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# NAT gateway output&lt;/span&gt;
&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"nat_gateway_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_router_nat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main_nat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Name of the Cloud NAT gateway"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setting Up GitHub Actions Automation
&lt;/h2&gt;

&lt;p&gt;Now let's create an automated CI/CD pipeline that will deploy your infrastructure whenever you push code to GitHub.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create GitHub Secrets
&lt;/h3&gt;

&lt;p&gt;First, you need to store your service account credentials in GitHub secrets:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Get your service account key content:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;terraform-sa-key.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;In your GitHub repository:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Go to &lt;strong&gt;Settings&lt;/strong&gt; &amp;gt; &lt;strong&gt;Secrets and variables&lt;/strong&gt; &amp;gt; &lt;strong&gt;Actions&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;New repository secret&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Name: &lt;code&gt;GCLOUD_SERVICE_ACCOUNT_KEY&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Value: Paste the entire contents of your service account key file&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Security Note&lt;/strong&gt;: This approach using service account keys is for learning purposes. In Part 3, we'll upgrade to Workload Identity Federation for keyless authentication, which is the production-recommended approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create GitHub Actions Workflow
&lt;/h3&gt;

&lt;p&gt;Create the workflow directory and file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; .github/workflows
&lt;span class="nb"&gt;touch&lt;/span&gt; .github/workflows/deploy.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add this workflow configuration to &lt;code&gt;deploy.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terraform Infrastructure Deployment&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;develop&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;PROJECT_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-gcp-project-id&lt;/span&gt;  &lt;span class="c1"&gt;# Replace with your project ID&lt;/span&gt;
  &lt;span class="na"&gt;TF_VERSION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.6.0&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;terraform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terraform Plan and Apply&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
      &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
      &lt;span class="na"&gt;pull-requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;

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

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Google Cloud SDK&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;google-github-actions/setup-gcloud@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;latest'&lt;/span&gt;
          &lt;span class="na"&gt;service_account_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GCLOUD_SERVICE_ACCOUNT_KEY }}&lt;/span&gt;
          &lt;span class="na"&gt;project_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.PROJECT_ID }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure Terraform authentication&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;echo '${{ secrets.GCLOUD_SERVICE_ACCOUNT_KEY }}' &amp;gt; /tmp/gcp-key.json&lt;/span&gt;
          &lt;span class="s"&gt;gcloud auth activate-service-account --key-file=/tmp/gcp-key.json&lt;/span&gt;
          &lt;span class="s"&gt;gcloud config set project ${{ env.PROJECT_ID }}&lt;/span&gt;
          &lt;span class="s"&gt;gcloud config set compute/region us-central1&lt;/span&gt;
          &lt;span class="s"&gt;export GOOGLE_APPLICATION_CREDENTIALS=/tmp/gcp-key.json&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Terraform&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hashicorp/setup-terraform@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;terraform_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.TF_VERSION }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terraform Format Check&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fmt&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform fmt -check -diff -recursive&lt;/span&gt;
        &lt;span class="na"&gt;continue-on-error&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terraform Initialize&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;init&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform init&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terraform Validation&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;validate&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform validate&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terraform Plan&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;plan&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;terraform plan -input=false -out=tfplan -detailed-exitcode&lt;/span&gt;
        &lt;span class="na"&gt;continue-on-error&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Comment PR with Plan&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.event_name == 'pull_request'&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/github-script@v7&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`&lt;/span&gt;
            &lt;span class="s"&gt;#### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`&lt;/span&gt;
            &lt;span class="s"&gt;#### Terraform Validation 🤖\`${{ steps.validate.outcome }}\`&lt;/span&gt;
            &lt;span class="s"&gt;#### Terraform Plan 📖\`${{ steps.plan.outcome }}\`&lt;/span&gt;

            &lt;span class="s"&gt;&amp;lt;details&amp;gt;&amp;lt;summary&amp;gt;Show Plan&amp;lt;/summary&amp;gt;&lt;/span&gt;

            &lt;span class="s"&gt;\`\`\`terraform&lt;/span&gt;
            &lt;span class="s"&gt;${{ steps.plan.outputs.stdout }}&lt;/span&gt;
            &lt;span class="s"&gt;\`\`\`&lt;/span&gt;

            &lt;span class="s"&gt;&amp;lt;/details&amp;gt;&lt;/span&gt;

            &lt;span class="s"&gt;*Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`*`;&lt;/span&gt;

            &lt;span class="s"&gt;github.rest.issues.createComment({&lt;/span&gt;
              &lt;span class="s"&gt;issue_number: context.issue.number,&lt;/span&gt;
              &lt;span class="s"&gt;owner: context.repo.owner,&lt;/span&gt;
              &lt;span class="s"&gt;repo: context.repo.repo,&lt;/span&gt;
              &lt;span class="s"&gt;body: output&lt;/span&gt;
            &lt;span class="s"&gt;})&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terraform Apply&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.ref == 'refs/heads/main' &amp;amp;&amp;amp; github.event_name == 'push'&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform apply -input=false tfplan&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Clean up credentials&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;rm -f /tmp/gcp-key.json&lt;/span&gt;
          &lt;span class="s"&gt;rm -f tfplan&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Output VM Information&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.ref == 'refs/heads/main' &amp;amp;&amp;amp; github.event_name == 'push'&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;echo "## 🚀 Deployment Complete!" &amp;gt;&amp;gt; $GITHUB_STEP_SUMMARY&lt;/span&gt;
          &lt;span class="s"&gt;echo "| Resource | Value |" &amp;gt;&amp;gt; $GITHUB_STEP_SUMMARY&lt;/span&gt;
          &lt;span class="s"&gt;echo "|----------|-------|" &amp;gt;&amp;gt; $GITHUB_STEP_SUMMARY&lt;/span&gt;
          &lt;span class="s"&gt;echo "| VM Name | $(terraform output -raw vm_name) |" &amp;gt;&amp;gt; $GITHUB_STEP_SUMMARY&lt;/span&gt;
          &lt;span class="s"&gt;echo "| Internal IP | $(terraform output -raw vm_internal_ip) |" &amp;gt;&amp;gt; $GITHUB_STEP_SUMMARY&lt;/span&gt;
          &lt;span class="s"&gt;echo "| SSH Command | \`$(terraform output -raw ssh_command)\` |" &amp;gt;&amp;gt; $GITHUB_STEP_SUMMARY&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deploying Your Infrastructure
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Local Deployment First
&lt;/h3&gt;

&lt;p&gt;Before pushing to GitHub, test your configuration locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Format and validate&lt;/span&gt;
terraform &lt;span class="nb"&gt;fmt&lt;/span&gt; &lt;span class="nt"&gt;-recursive&lt;/span&gt;
terraform validate

&lt;span class="c"&gt;# Plan and apply&lt;/span&gt;
terraform plan &lt;span class="nt"&gt;-out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tfplan
terraform apply tfplan

&lt;span class="c"&gt;# Verify deployment&lt;/span&gt;
terraform output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Expected Output
&lt;/h3&gt;

&lt;p&gt;You should see output similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vm_internal_ip = "10.0.1.2"
vm_name = "demo-dev-vm"
vm_zone = "us-central1-b"
ssh_command = "gcloud compute ssh demo-dev-vm --zone=us-central1-b --project=your-project-id"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test VM Connectivity
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# SSH into your VM (if you have gcloud configured)&lt;/span&gt;
gcloud compute ssh demo-dev-vm &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;us-central1-b &lt;span class="nt"&gt;--project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-project-id

&lt;span class="c"&gt;# Test internet connectivity from VM&lt;/span&gt;
curl &lt;span class="nt"&gt;-I&lt;/span&gt; http://google.com

&lt;span class="c"&gt;# Check nginx is running&lt;/span&gt;
curl localhost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Push to GitHub for Automated Deployment
&lt;/h3&gt;

&lt;p&gt;Once local testing works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Add VM deployment with GitHub Actions automation"&lt;/span&gt;
git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;GitHub Actions will:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Validate&lt;/strong&gt; your Terraform code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plan&lt;/strong&gt; the infrastructure changes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apply&lt;/strong&gt; changes automatically on main branch&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comment&lt;/strong&gt; on pull requests with plan details&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Monitoring Your Deployment
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Check GitHub Actions
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to your repository on GitHub&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;Actions&lt;/strong&gt; tab&lt;/li&gt;
&lt;li&gt;Watch your workflow run in real-time&lt;/li&gt;
&lt;li&gt;Check the job summary for deployment details&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Verify in GCP Console
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Compute Engine&lt;/strong&gt; &amp;gt; &lt;strong&gt;VM instances&lt;/strong&gt; - see your VM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VPC network&lt;/strong&gt; &amp;gt; &lt;strong&gt;Firewall&lt;/strong&gt; - check your firewall rules&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network services&lt;/strong&gt; &amp;gt; &lt;strong&gt;Cloud NAT&lt;/strong&gt; - verify NAT configuration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logging&lt;/strong&gt; &amp;gt; &lt;strong&gt;Logs Explorer&lt;/strong&gt; - monitor VM and firewall logs&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Test Your VM
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List your VMs&lt;/span&gt;
gcloud compute instances list

&lt;span class="c"&gt;# SSH into the VM&lt;/span&gt;
gcloud compute ssh demo-dev-vm &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;us-central1-b

&lt;span class="c"&gt;# Inside the VM, test internet access&lt;/span&gt;
curl &lt;span class="nt"&gt;-I&lt;/span&gt; https://www.google.com

&lt;span class="c"&gt;# Check nginx status&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status nginx
curl localhost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Production Considerations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Security Enhancements
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Firewall Rules:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Restrict SSH access to specific IP ranges&lt;/li&gt;
&lt;li&gt;Use IAP (Identity-Aware Proxy) for SSH access&lt;/li&gt;
&lt;li&gt;Implement network tags consistently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Service Accounts:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Follow principle of least privilege&lt;/li&gt;
&lt;li&gt;Use separate service accounts for different workloads&lt;/li&gt;
&lt;li&gt;Regularly rotate service account keys&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Monitoring and Logging
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Enable Monitoring:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Add to vm.tf&lt;/span&gt;
&lt;span class="nx"&gt;metadata&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;enable-oslogin&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"TRUE"&lt;/span&gt;
  &lt;span class="nx"&gt;google-monitoring-enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"TRUE"&lt;/span&gt;
  &lt;span class="nx"&gt;google-logging-enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"TRUE"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Set up Alerts:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VM instance health checks&lt;/li&gt;
&lt;li&gt;High CPU or memory usage&lt;/li&gt;
&lt;li&gt;Network connectivity issues&lt;/li&gt;
&lt;li&gt;Unusual SSH access patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cost Optimization
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Right-sizing:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Monitor VM performance and adjust machine types&lt;/li&gt;
&lt;li&gt;Use preemptible instances for non-critical workloads&lt;/li&gt;
&lt;li&gt;Implement auto-shutdown for development VMs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Storage Optimization:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use appropriate disk types (pd-standard vs pd-ssd)&lt;/li&gt;
&lt;li&gt;Enable disk auto-deletion with VMs&lt;/li&gt;
&lt;li&gt;Implement disk snapshots for backups&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Troubleshooting Common Issues
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Authentication Failures
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Re-authenticate gcloud&lt;/span&gt;
gcloud auth application-default login

&lt;span class="c"&gt;# Verify project setting&lt;/span&gt;
gcloud config get-value project

&lt;span class="c"&gt;# Check service account permissions&lt;/span&gt;
gcloud projects get-iam-policy YOUR_PROJECT_ID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  VM Won't Start
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check VM status&lt;/span&gt;
gcloud compute instances describe VM_NAME &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ZONE

&lt;span class="c"&gt;# View serial console output&lt;/span&gt;
gcloud compute instances get-serial-port-output VM_NAME &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ZONE

&lt;span class="c"&gt;# Check quotas&lt;/span&gt;
gcloud compute project-info describe &lt;span class="nt"&gt;--project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;PROJECT_ID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Network Connectivity Issues
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Test firewall rules&lt;/span&gt;
gcloud compute firewall-rules list

&lt;span class="c"&gt;# Check routes&lt;/span&gt;
gcloud compute routes list

&lt;span class="c"&gt;# Verify NAT configuration&lt;/span&gt;
gcloud compute routers get-nat-mappings ROUTER_NAME &lt;span class="nt"&gt;--region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;REGION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What's Next in Part 3
&lt;/h2&gt;

&lt;p&gt;In our next tutorial, we'll enhance this setup with:&lt;/p&gt;

&lt;p&gt;🔜 &lt;strong&gt;Secure PostgreSQL deployment&lt;/strong&gt; with Cloud SQL&lt;br&gt;&lt;br&gt;
🔜 &lt;strong&gt;Workload Identity Federation&lt;/strong&gt; for keyless GitHub Actions&lt;br&gt;&lt;br&gt;
🔜 &lt;strong&gt;Private service connections&lt;/strong&gt; for database security&lt;br&gt;&lt;br&gt;
🔜 &lt;strong&gt;Advanced networking&lt;/strong&gt; with VPC peering&lt;/p&gt;

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

&lt;p&gt;✅ &lt;strong&gt;Automated infrastructure&lt;/strong&gt; reduces human error and increases deployment speed&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;GitHub Actions integration&lt;/strong&gt; enables true Infrastructure as Code workflows&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Proper firewall rules&lt;/strong&gt; are essential for both security and functionality&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Cloud NAT&lt;/strong&gt; provides secure internet access without public IPs&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Service account best practices&lt;/strong&gt; improve security posture from the start&lt;/p&gt;

&lt;p&gt;Ready to level up your infrastructure automation? This foundation of automated VM deployment sets you up perfectly for the advanced database and security patterns we'll cover in Part 3!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connect with me:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://linkedin.com/in/akhilesh-mishra-0ab886124" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; for more Google Cloud and DevOps content&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; #Terraform #GoogleCloud #GCP #GitHubActions #DevOps #InfrastructureAsCode #ComputeEngine #Automation&lt;/p&gt;

</description>
      <category>googlecloud</category>
    </item>
    <item>
      <title>Build Production-Ready Google Cloud Infrastructure with Terraform in 2025</title>
      <dc:creator>Akhilesh Mishra</dc:creator>
      <pubDate>Wed, 23 Jul 2025 09:39:26 +0000</pubDate>
      <link>https://forem.com/livingdevops/build-production-ready-google-cloud-infrastructure-with-terraform-in-2025-1jj7</link>
      <guid>https://forem.com/livingdevops/build-production-ready-google-cloud-infrastructure-with-terraform-in-2025-1jj7</guid>
      <description>&lt;h2&gt;
  
  
  Complete step-by-step guide to creating VPC networks, subnets, and storage buckets using Infrastructure as Code
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Part 1 of our comprehensive 6-part Terraform on Google Cloud series - from beginner setup to advanced DevOps automation&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ready to transform your Google Cloud infrastructure management?&lt;/strong&gt; This comprehensive guide kicks off our &lt;strong&gt;Terraform on Google Cloud&lt;/strong&gt; series, where you'll master professional-level cloud automation, GitHub Actions CI/CD, advanced security patterns, and production-ready DevOps practices.&lt;/p&gt;

&lt;p&gt;By the end of this tutorial series, you'll have hands-on experience with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure as Code&lt;/strong&gt; fundamentals and best practices&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated CI/CD pipelines&lt;/strong&gt; with GitHub Actions and Workload Identity Federation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production security&lt;/strong&gt; with Secret Manager and key rotation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serverless computing&lt;/strong&gt; with Cloud Functions and Cloud Run&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Big data processing&lt;/strong&gt; with Cloud SQL and Dataproc&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;🚀 Perfect for cloud engineers, DevOps practitioners, and developers ready to level up their infrastructure automation game.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What You'll Build in This Series
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Part 1 (This Post)&lt;/strong&gt;: Foundation - VPC, Subnets, and Storage&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Part 2&lt;/strong&gt;: Compute Engine VMs with GitHub Actions CI/CD&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Part 3&lt;/strong&gt;: Secure PostgreSQL with Workload Identity Federation&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Part 4&lt;/strong&gt;: Secret Management and Automated Key Rotation&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Part 5&lt;/strong&gt;: Serverless FastAPI with Cloud Run&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Part 6&lt;/strong&gt;: Big Data Processing with Dataproc (Advanced)&lt;/p&gt;
&lt;h2&gt;
  
  
  Understanding Google Cloud Networking Fundamentals
&lt;/h2&gt;
&lt;h3&gt;
  
  
  What is a VPC in Google Cloud?
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;Virtual Private Cloud (VPC)&lt;/strong&gt; serves as your isolated network environment within Google Cloud:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;🔒 Network Isolation&lt;/strong&gt;: Complete separation from other projects and tenants&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🛡️ Security Control&lt;/strong&gt;: Fine-grained access control and firewall rules
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🌍 Global Resource&lt;/strong&gt;: Automatically spans multiple regions worldwide&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;📍 IP Management&lt;/strong&gt;: Custom IP addressing, subnets, and routing control&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🔗 Connectivity&lt;/strong&gt;: VPN, interconnect, and peering capabilities&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  What is a Subnet in Google Cloud?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Subnets&lt;/strong&gt; are regional network segments within your VPC that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Define IP address ranges (CIDR blocks) for your resources&lt;/li&gt;
&lt;li&gt;Enable regional resource deployment and isolation&lt;/li&gt;
&lt;li&gt;Provide traffic segmentation and security boundaries&lt;/li&gt;
&lt;li&gt;Support private Google API access for enhanced security&lt;/li&gt;
&lt;li&gt;Allow custom routing and firewall rule application&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pro Tip&lt;/strong&gt;: Unlike AWS, Google Cloud subnets are regional (not zonal), giving you automatic high availability across zones within a region.&lt;/p&gt;


&lt;h2&gt;
  
  
  Prerequisites: Setting Up Your Environment
&lt;/h2&gt;

&lt;p&gt;Before diving into Terraform automation, ensure you have:&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Google Cloud Platform account&lt;/strong&gt; (&lt;a href="https://cloud.google.com" rel="noopener noreferrer"&gt;Sign up here&lt;/a&gt;)&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Active GCP project&lt;/strong&gt; with billing enabled&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Terraform installed&lt;/strong&gt; (&lt;a href="https://terraform.io" rel="noopener noreferrer"&gt;Installation guide&lt;/a&gt;) - Version 1.0+ recommended&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Google Cloud SDK&lt;/strong&gt; (&lt;a href="https://cloud.google.com/sdk" rel="noopener noreferrer"&gt;Download here&lt;/a&gt;)&lt;br&gt;
✅ &lt;strong&gt;Basic understanding&lt;/strong&gt; of cloud networking concepts&lt;/p&gt;
&lt;h3&gt;
  
  
  Verify Your Installation
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check Google Cloud SDK&lt;/span&gt;
gcloud &lt;span class="nt"&gt;--version&lt;/span&gt;
gcloud init 

&lt;span class="c"&gt;# Verify Terraform&lt;/span&gt;
terraform &lt;span class="nt"&gt;--version&lt;/span&gt;

&lt;span class="c"&gt;# Check your active project&lt;/span&gt;
gcloud config get-value project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Google Cloud Authentication Setup
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Creating a Service Account (Development Setup)
&lt;/h3&gt;

&lt;p&gt;For this tutorial series, we'll create a service account with owner permissions. &lt;strong&gt;Important&lt;/strong&gt;: In production environments, always follow the &lt;strong&gt;principle of least privilege&lt;/strong&gt; and use keyless authentication with Workload Identity Federation (covered in Part 3).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Set your project ID (replace with your actual project ID)&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PROJECT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-gcp-project-id"&lt;/span&gt;
gcloud config &lt;span class="nb"&gt;set &lt;/span&gt;project &lt;span class="nv"&gt;$PROJECT_ID&lt;/span&gt;

&lt;span class="c"&gt;# Create service account for Terraform&lt;/span&gt;
gcloud iam service-accounts create terraform-automation &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Service account for Terraform infrastructure automation"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--display-name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Terraform Automation SA"&lt;/span&gt;

&lt;span class="c"&gt;# Grant owner role (development only - we'll improve this in later parts)&lt;/span&gt;
gcloud projects add-iam-policy-binding &lt;span class="nv"&gt;$PROJECT_ID&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--member&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"serviceAccount:terraform-automation@&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PROJECT_ID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.iam.gserviceaccount.com"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"roles/owner"&lt;/span&gt;

&lt;span class="c"&gt;# Generate service account key&lt;/span&gt;
gcloud iam service-accounts keys create terraform-sa-key.json &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--iam-account&lt;/span&gt; terraform-automation@&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PROJECT_ID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;.iam.gserviceaccount.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Set Environment Variables
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Set credentials for Terraform&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GOOGLE_APPLICATION_CREDENTIALS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"terraform-sa-key.json"&lt;/span&gt;

&lt;span class="c"&gt;# Verify authentication&lt;/span&gt;
gcloud auth application-default print-access-token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Project Structure: Organizing Your Terraform Code
&lt;/h2&gt;

&lt;p&gt;Create a clean, maintainable project structure that will scale throughout our series:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;terraform-gcp-foundation &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;terraform-gcp-foundation

&lt;span class="c"&gt;# Create core Terraform files&lt;/span&gt;
&lt;span class="nb"&gt;touch &lt;/span&gt;main.tf variables.tf outputs.tf providers.tf terraform.tfvars

&lt;span class="c"&gt;# Create resource-specific files&lt;/span&gt;
&lt;span class="nb"&gt;touch &lt;/span&gt;networking.tf storage.tf

&lt;span class="c"&gt;# Create backend configuration&lt;/span&gt;
&lt;span class="nb"&gt;touch &lt;/span&gt;backend.tf

&lt;span class="c"&gt;# Initialize git for version control&lt;/span&gt;
git init
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"*.tfvars"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .gitignore
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"terraform-sa-key.json"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .gitignore
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;".terraform/"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .gitignore
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"*.tfstate*"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .gitignore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this structure?&lt;/strong&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Separation of concerns&lt;/strong&gt;: Each file has a specific purpose&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability&lt;/strong&gt;: Easy to add new resources in dedicated files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team collaboration&lt;/strong&gt;: Clear organization for multiple developers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version control ready&lt;/strong&gt;: Proper .gitignore for sensitive files&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Terraform Configuration Files Explained
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Provider Configuration (&lt;code&gt;providers.tf&lt;/code&gt;)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 1.0"&lt;/span&gt;

  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;google&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/google"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 5.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;google-beta&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/google-beta"&lt;/span&gt; 
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 5.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"google"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;project&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_id&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"google-beta"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;project&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_id&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Input Variables (&lt;code&gt;variables.tf&lt;/code&gt;)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"project_id"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Google Cloud Project ID"&lt;/span&gt;
  &lt;span class="nx"&gt;validation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Project ID must be more than 6 characters."&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"region"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"GCP region for resources"&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-central1"&lt;/span&gt;
  &lt;span class="nx"&gt;validation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="s2"&gt;"us-central1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"us-east1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"us-west1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"us-west2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"europe-west1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"europe-west2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"asia-east1"&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Region must be a valid GCP region."&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"environment"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Environment name (dev, staging, prod)"&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt;
  &lt;span class="nx"&gt;validation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"staging"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"prod"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Environment must be dev, staging, or prod."&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"vpc_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Name for the VPC network"&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"main-vpc"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"subnet_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Name for the subnet"&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"main-subnet"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"subnet_cidr"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CIDR range for the subnet"&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.1.0/24"&lt;/span&gt;
  &lt;span class="nx"&gt;validation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;can&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cidrhost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subnet_cidr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Subnet CIDR must be a valid IPv4 CIDR block."&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Networking Resources (&lt;code&gt;networking.tf&lt;/code&gt;)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# VPC Network&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_compute_network"&lt;/span&gt; &lt;span class="s2"&gt;"main_vpc"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.environment}-${var.vpc_name}"&lt;/span&gt;
  &lt;span class="nx"&gt;project&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_id&lt;/span&gt;
  &lt;span class="nx"&gt;auto_create_subnetworks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;routing_mode&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"REGIONAL"&lt;/span&gt;
  &lt;span class="nx"&gt;mtu&lt;/span&gt;                    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1460&lt;/span&gt;

  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Main VPC network for ${var.environment} environment"&lt;/span&gt;

  &lt;span class="c1"&gt;# Enable deletion protection in production&lt;/span&gt;
  &lt;span class="nx"&gt;delete_default_routes_on_create&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Subnet&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_compute_subnetwork"&lt;/span&gt; &lt;span class="s2"&gt;"main_subnet"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.environment}-${var.subnet_name}"&lt;/span&gt;
  &lt;span class="nx"&gt;project&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_id&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;
  &lt;span class="nx"&gt;network&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;ip_cidr_range&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subnet_cidr&lt;/span&gt;
  &lt;span class="nx"&gt;private_ip_google_access&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Primary subnet in ${var.region} for ${var.environment}"&lt;/span&gt;

  &lt;span class="c1"&gt;# Enable flow logs for security monitoring&lt;/span&gt;
  &lt;span class="nx"&gt;log_config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;aggregation_interval&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"INTERVAL_10_MIN"&lt;/span&gt;
    &lt;span class="nx"&gt;flow_sampling&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;
    &lt;span class="nx"&gt;metadata&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"INCLUDE_ALL_METADATA"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Secondary IP ranges for future use (GKE, etc.)&lt;/span&gt;
  &lt;span class="nx"&gt;secondary_ip_range&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;range_name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"pods"&lt;/span&gt;
    &lt;span class="nx"&gt;ip_cidr_range&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.1.0.0/16"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;secondary_ip_range&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;range_name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"services"&lt;/span&gt;
    &lt;span class="nx"&gt;ip_cidr_range&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.2.0.0/16"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Cloud Router for Cloud NAT (we'll use this in Part 2)&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_compute_router"&lt;/span&gt; &lt;span class="s2"&gt;"main_router"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.environment}-cloud-router"&lt;/span&gt;
  &lt;span class="nx"&gt;project&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_id&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;
  &lt;span class="nx"&gt;network&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Cloud Router for NAT gateway"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Storage Resources (&lt;code&gt;storage.tf&lt;/code&gt;)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Storage bucket for Terraform state (remote backend)&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_storage_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"terraform_state"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_id}-${var.environment}-terraform-state"&lt;/span&gt;
  &lt;span class="nx"&gt;project&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_id&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;

  &lt;span class="c1"&gt;# Force destroy for development (disable in production)&lt;/span&gt;
  &lt;span class="nx"&gt;force_destroy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

  &lt;span class="c1"&gt;# Enable versioning for state file safety&lt;/span&gt;
  &lt;span class="nx"&gt;versioning&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Uniform bucket-level access for better security&lt;/span&gt;
  &lt;span class="nx"&gt;uniform_bucket_level_access&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="c1"&gt;# Encryption at rest&lt;/span&gt;
  &lt;span class="nx"&gt;encryption&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;default_kms_key_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="c1"&gt;# We'll add KMS in Part 4&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Lifecycle management to control costs&lt;/span&gt;
  &lt;span class="nx"&gt;lifecycle_rule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Delete"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;lifecycle_rule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;age&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
      &lt;span class="nx"&gt;with_state&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ARCHIVED"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Delete"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Labels for resource management&lt;/span&gt;
  &lt;span class="nx"&gt;labels&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
    &lt;span class="nx"&gt;purpose&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-state"&lt;/span&gt;
    &lt;span class="nx"&gt;managed-by&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# General purpose storage bucket&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_storage_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"app_storage"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_id}-${var.environment}-app-storage"&lt;/span&gt;
  &lt;span class="nx"&gt;project&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_id&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;

  &lt;span class="c1"&gt;# Storage class optimization&lt;/span&gt;
  &lt;span class="nx"&gt;storage_class&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"STANDARD"&lt;/span&gt;

  &lt;span class="nx"&gt;uniform_bucket_level_access&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="c1"&gt;# CORS configuration for web applications&lt;/span&gt;
  &lt;span class="nx"&gt;cors&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;origin&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;method&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"HEAD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"PUT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"DELETE"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;response_header&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;max_age_seconds&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;labels&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
    &lt;span class="nx"&gt;purpose&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"application-storage"&lt;/span&gt;
    &lt;span class="nx"&gt;managed-by&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# IAM binding for service account access to state bucket&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_storage_bucket_iam_member"&lt;/span&gt; &lt;span class="s2"&gt;"terraform_state_access"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_storage_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;terraform_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;role&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"roles/storage.admin"&lt;/span&gt;
  &lt;span class="nx"&gt;member&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"serviceAccount:terraform-automation@${var.project_id}.iam.gserviceaccount.com"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Outputs (&lt;code&gt;outputs.tf&lt;/code&gt;)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"vpc_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Name of the created VPC"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"vpc_id"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ID of the created VPC"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"vpc_self_link"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;self_link&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Self-link of the VPC (useful for other resources)"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"subnet_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_subnetwork&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Name of the created subnet"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"subnet_cidr"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_subnetwork&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ip_cidr_range&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CIDR range of the subnet"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"subnet_gateway_address"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_subnetwork&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gateway_address&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Gateway IP address of the subnet"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"terraform_state_bucket"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_storage_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;terraform_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Name of the Terraform state storage bucket"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"app_storage_bucket"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_storage_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app_storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Name of the application storage bucket"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"cloud_router_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main_router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Name of the Cloud Router (for NAT in Part 2)"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Network details for use in subsequent parts&lt;/span&gt;
&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"network_details"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;vpc_name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
    &lt;span class="nx"&gt;subnet_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_subnetwork&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
    &lt;span class="nx"&gt;region&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;
    &lt;span class="nx"&gt;project_id&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_id&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Network configuration details for other Terraform configurations"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Variable Values (&lt;code&gt;terraform.tfvars&lt;/code&gt;)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Project Configuration&lt;/span&gt;
&lt;span class="nx"&gt;project_id&lt;/span&gt;  &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"your-gcp-project-id"&lt;/span&gt;  &lt;span class="c1"&gt;# Replace with your actual project ID&lt;/span&gt;
&lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt;

&lt;span class="c1"&gt;# Network Configuration&lt;/span&gt;
&lt;span class="nx"&gt;region&lt;/span&gt;      &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-central1"&lt;/span&gt;
&lt;span class="nx"&gt;vpc_name&lt;/span&gt;    &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"main-vpc"&lt;/span&gt;
&lt;span class="nx"&gt;subnet_name&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"primary-subnet"&lt;/span&gt;
&lt;span class="nx"&gt;subnet_cidr&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.1.0/24"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7. Backend Configuration (&lt;code&gt;backend.tf&lt;/code&gt;)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Local backend for initial setup&lt;/span&gt;
&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="s2"&gt;"local"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform.tfstate"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# After creating the storage bucket, uncomment below and migrate:&lt;/span&gt;
&lt;span class="c1"&gt;# terraform {&lt;/span&gt;
&lt;span class="c1"&gt;#   backend "gcs" {&lt;/span&gt;
&lt;span class="c1"&gt;#     bucket = "your-project-id-dev-terraform-state"&lt;/span&gt;
&lt;span class="c1"&gt;#     prefix = "foundation/state"&lt;/span&gt;
&lt;span class="c1"&gt;#   }&lt;/span&gt;
&lt;span class="c1"&gt;# }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Understanding Terraform State Management
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Terraform state&lt;/strong&gt; is the crucial component that tracks your infrastructure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📊 &lt;strong&gt;Resource Metadata&lt;/strong&gt;: Current state, IDs, and configurations&lt;/li&gt;
&lt;li&gt;🔗 &lt;strong&gt;Dependencies&lt;/strong&gt;: Relationships and creation order between resources
&lt;/li&gt;
&lt;li&gt;📝 &lt;strong&gt;Performance&lt;/strong&gt;: Caches resource attributes for faster operations&lt;/li&gt;
&lt;li&gt;🎯 &lt;strong&gt;Drift Detection&lt;/strong&gt;: Identifies manual changes made outside Terraform&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  State Storage Options
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Local State (Development)&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stored on your local machine&lt;/li&gt;
&lt;li&gt;Simple for learning and testing&lt;/li&gt;
&lt;li&gt;Not suitable for team collaboration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Remote State (Production)&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Cloud Storage&lt;/strong&gt; (recommended for GCP)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terraform Cloud&lt;/strong&gt; (HashiCorp's managed solution)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon S3&lt;/strong&gt; (for multi-cloud scenarios)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Migrating to Remote State
&lt;/h3&gt;

&lt;p&gt;After your first deployment, migrate to remote state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Update backend.tf with your bucket name&lt;/span&gt;
&lt;span class="c"&gt;# 2. Run migration command&lt;/span&gt;
terraform init &lt;span class="nt"&gt;-migrate-state&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deploying Your Infrastructure
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Initialize Terraform
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Downloads provider plugins and initializes the working directory&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Format and Validate
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Format code for consistency&lt;/span&gt;
terraform &lt;span class="nb"&gt;fmt&lt;/span&gt; &lt;span class="nt"&gt;-recursive&lt;/span&gt;

&lt;span class="c"&gt;# Validate configuration&lt;/span&gt;
terraform validate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Plan Your Deployment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform plan &lt;span class="nt"&gt;-out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tfplan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Creates an execution plan and saves it to a file&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Understanding the Plan Output:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;+&lt;/code&gt; Resources to be &lt;strong&gt;created&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-&lt;/code&gt; Resources to be &lt;strong&gt;destroyed&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;~&lt;/code&gt; Resources to be &lt;strong&gt;modified&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;=&lt;/code&gt; Resources to be &lt;strong&gt;read&lt;/strong&gt; (data sources)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 4: Apply Changes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform apply tfplan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Executes the plan - no additional confirmation needed since plan was saved&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Verify Deployment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check outputs&lt;/span&gt;
terraform output

&lt;span class="c"&gt;# Verify in GCP Console&lt;/span&gt;
gcloud compute networks list
gcloud compute networks subnets list &lt;span class="nt"&gt;--network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev-main-vpc
gsutil &lt;span class="nb"&gt;ls &lt;/span&gt;gs://your-project-id-dev-terraform-state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6: Clean Up (When Needed)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform destroy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;⚠️ Removes all managed infrastructure - be extremely careful in production!&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Production-Ready Best Practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🔧 Code Organization &amp;amp; Standards
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Modularize&lt;/strong&gt; configurations for reusability across environments&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;consistent naming conventions&lt;/strong&gt; with environment prefixes&lt;/li&gt;
&lt;li&gt;Implement &lt;strong&gt;proper file structure&lt;/strong&gt; with clear separation of concerns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version pin&lt;/strong&gt; providers and modules to avoid breaking changes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔒 Security &amp;amp; Access Management
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Never commit&lt;/strong&gt; sensitive data or credentials to version control&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;remote state backend&lt;/strong&gt; with proper access controls and encryption&lt;/li&gt;
&lt;li&gt;Implement &lt;strong&gt;state locking&lt;/strong&gt; to prevent concurrent modifications&lt;/li&gt;
&lt;li&gt;Follow &lt;strong&gt;least privilege principle&lt;/strong&gt; for service account permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  📚 Documentation &amp;amp; Team Collaboration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Add &lt;strong&gt;meaningful descriptions&lt;/strong&gt; to all resources and variables&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;version control&lt;/strong&gt; (Git) with proper branching strategies&lt;/li&gt;
&lt;li&gt;Implement &lt;strong&gt;peer reviews&lt;/strong&gt; for all infrastructure changes&lt;/li&gt;
&lt;li&gt;Document &lt;strong&gt;runbooks&lt;/strong&gt; for common operations and troubleshooting&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🚀 Automation &amp;amp; CI/CD (Coming in Part 2)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Integrate with &lt;strong&gt;GitHub Actions&lt;/strong&gt; for automated deployments&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;environment-specific&lt;/strong&gt; variable files and workspaces&lt;/li&gt;
&lt;li&gt;Implement &lt;strong&gt;automated testing&lt;/strong&gt; for infrastructure code&lt;/li&gt;
&lt;li&gt;Set up &lt;strong&gt;monitoring and alerting&lt;/strong&gt; for infrastructure changes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  💰 Cost Optimization
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;labels and tags&lt;/strong&gt; for resource tracking and cost allocation&lt;/li&gt;
&lt;li&gt;Implement &lt;strong&gt;lifecycle policies&lt;/strong&gt; for storage resources&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;appropriate machine types&lt;/strong&gt; and disk sizes&lt;/li&gt;
&lt;li&gt;Monitor and set up &lt;strong&gt;budget alerts&lt;/strong&gt; for cost control&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Coming Next in This Series
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🔜 &lt;strong&gt;Part 2: Compute Engine VMs with GitHub Actions&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Deploy secure VM instances with proper networking&lt;/li&gt;
&lt;li&gt;Set up automated CI/CD pipelines with GitHub Actions&lt;/li&gt;
&lt;li&gt;Implement firewall rules and Cloud NAT for secure internet access&lt;/li&gt;
&lt;li&gt;Master service account management and authentication&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔜 &lt;strong&gt;Part 3: PostgreSQL with Workload Identity Federation&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Deploy Cloud SQL PostgreSQL in private networks&lt;/li&gt;
&lt;li&gt;Implement keyless authentication with Workload Identity Federation&lt;/li&gt;
&lt;li&gt;Set up VPC peering and private service connections&lt;/li&gt;
&lt;li&gt;Database security and backup configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔜 &lt;strong&gt;Part 4: Secret Management and Key Rotation&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Secure credential storage with Secret Manager&lt;/li&gt;
&lt;li&gt;Automated service account key rotation with Cloud Functions&lt;/li&gt;
&lt;li&gt;Production-grade security patterns&lt;/li&gt;
&lt;li&gt;Integration with existing infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔜 &lt;strong&gt;Part 5: Serverless FastAPI with Cloud Run&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Containerize and deploy Python applications&lt;/li&gt;
&lt;li&gt;Implement proper artifact registry workflows&lt;/li&gt;
&lt;li&gt;Configure custom domains and SSL certificates&lt;/li&gt;
&lt;li&gt;Performance optimization and scaling strategies&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔜 &lt;strong&gt;Part 6: Big Data with Dataproc (Advanced)&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Set up managed Hadoop and Spark clusters&lt;/li&gt;
&lt;li&gt;Configure preemptible instances for cost optimization&lt;/li&gt;
&lt;li&gt;Data processing pipelines and job orchestration&lt;/li&gt;
&lt;li&gt;Integration with Cloud Storage and BigQuery&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;✅ &lt;strong&gt;Infrastructure as Code&lt;/strong&gt; provides consistency, repeatability, and version control&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Proper project structure&lt;/strong&gt; makes maintenance and collaboration easier&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Variable validation&lt;/strong&gt; catches errors early in the development cycle&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Remote state management&lt;/strong&gt; is essential for team collaboration and production use&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Security best practices&lt;/strong&gt; should be implemented from day one, not as an afterthought&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ready to continue?&lt;/strong&gt; This foundation sets you up for success in the remaining parts of our series. Each subsequent tutorial builds upon this infrastructure, adding more sophisticated patterns and production-ready features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting Common Issues
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Authentication Problems
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Re-authenticate if needed&lt;/span&gt;
gcloud auth application-default login
gcloud config &lt;span class="nb"&gt;set &lt;/span&gt;project YOUR_PROJECT_ID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  API Enablement
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Enable required APIs&lt;/span&gt;
gcloud services &lt;span class="nb"&gt;enable &lt;/span&gt;compute.googleapis.com
gcloud services &lt;span class="nb"&gt;enable &lt;/span&gt;storage-api.googleapis.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Permission Issues
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Verify service account permissions&lt;/span&gt;
gcloud projects get-iam-policy YOUR_PROJECT_ID &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--flatten&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"bindings[].members"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--filter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"bindings.members:serviceAccount:terraform-automation@YOUR_PROJECT_ID.iam.gserviceaccount.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Connect with me:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://linkedin.com/in/akhilesh-mishra-0ab886124" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; #Terraform #GoogleCloud #GCP #DevOps #InfrastructureAsCode #CloudAutomation #NetworkingSeries&lt;/p&gt;

</description>
      <category>gcp</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Managing AWS Lambda Layers Was a Nightmare Until I Did This</title>
      <dc:creator>Akhilesh Mishra</dc:creator>
      <pubDate>Fri, 27 Jun 2025 12:17:15 +0000</pubDate>
      <link>https://forem.com/aws-builders/managing-aws-lambda-layers-was-a-nightmare-until-i-did-this-4830</link>
      <guid>https://forem.com/aws-builders/managing-aws-lambda-layers-was-a-nightmare-until-i-did-this-4830</guid>
      <description>&lt;h2&gt;
  
  
  Automation That Saved My Team Months of Toil
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1518432031352-d6fc5c10da5a%3Fixlib%3Drb-4.0.3%26ixid%3DM3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%253D%253D%26auto%3Dformat%26fit%3Dcrop%26w%3D2074%26q%3D80" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1518432031352-d6fc5c10da5a%3Fixlib%3Drb-4.0.3%26ixid%3DM3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%253D%253D%26auto%3Dformat%26fit%3Dcrop%26w%3D2074%26q%3D80" alt="AWS Lambda automation with Terraform" width="2074" height="1556"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Automate Lambda Layer management with Terraform and GitHub Actions - A complete guide from pain to automation&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I started my DevOps career with the Google Cloud platform and used Cloud Functions for event-driven serverless workloads. It was pretty straightforward — package the Python code with its dependencies, i.e. &lt;code&gt;requirements.txt&lt;/code&gt; and everything else will be taken care of by Cloud Function (i.e. GCP).&lt;/p&gt;

&lt;p&gt;Then I moved to AWS and discovered Lambda, their popular serverless offering. It was great, except for one headache: &lt;strong&gt;managing code dependencies&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I was used to just using a &lt;code&gt;requirements.txt&lt;/code&gt; file, but that doesn't work with Lambda. Instead, you have two options. You can either bundle all your libraries with your code in a big zip file (which is a pain to manage) or use something called &lt;strong&gt;Lambda Layers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Before I explain Layers and how to use them effectively, let me give you a quick rundown on Lambda and serverless computing in general.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What is Serverless?&lt;/li&gt;
&lt;li&gt;What is AWS Lambda?&lt;/li&gt;
&lt;li&gt;What are Lambda Layers?&lt;/li&gt;
&lt;li&gt;How to Build Lambda Layers&lt;/li&gt;
&lt;li&gt;The Automation Solution&lt;/li&gt;
&lt;li&gt;Implementation Guide&lt;/li&gt;
&lt;li&gt;Running the Automation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is Serverless?
&lt;/h2&gt;

&lt;p&gt;Don't let the term "serverless" fool you — it doesn't mean servers have vanished into thin air. Instead, think of it as &lt;strong&gt;"servers you don't see."&lt;/strong&gt; In this model, your cloud provider manages the resources for you, handling all the nitty-gritty of server maintenance.&lt;/p&gt;

&lt;p&gt;At its core, serverless computing is about simplifying your life as a developer or business owner. It's a world where you can focus on what truly matters — your code and business logic — without getting bogged down in the complexities of managing the machines that run it.&lt;/p&gt;

&lt;p&gt;That's the promise of serverless computing — it's about freeing you to innovate, while your cloud provider handles the heavy lifting of infrastructure management.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is AWS Lambda?
&lt;/h2&gt;

&lt;p&gt;Lambda is the Function as a Service (serverless) offering by AWS. It uses an &lt;strong&gt;event-driven architecture&lt;/strong&gt;, meaning it runs a piece of code when some event occurs. It integrates well with other AWS services such as DynamoDB, S3, SQS, API Gateway, etc.&lt;/p&gt;

&lt;p&gt;Your code may be just a couple of lines, or spread across 2 files but if it requires 15 different packages to tie everything together, it's referred to as a &lt;strong&gt;"deployment package."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is best practice to manage the code dependencies separately and this is where Lambda Layers come in handy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are Lambda Layers?
&lt;/h2&gt;

&lt;p&gt;To quote AWS documentation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A Lambda layer is a .zip file archive that contains supplementary code or data. Layers usually contain library dependencies, a custom runtime, or configuration files.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In simple words, if your Python code requires 5, 10, 15 libraries to run the code, you build them separately and enable Lambda to use them on runtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key benefits of Lambda Layers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📦 Separate dependencies from your application code&lt;/li&gt;
&lt;li&gt;🔄 Share layers across multiple Lambda functions&lt;/li&gt;
&lt;li&gt;🌐 Share layers across multiple AWS accounts&lt;/li&gt;
&lt;li&gt;💾 Reduce deployment package size&lt;/li&gt;
&lt;li&gt;⚡ Faster deployments and cold starts&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to Build Lambda Layers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Manual Method (The Old Way)
&lt;/h3&gt;

&lt;p&gt;The simplest way of building Layers is to install the Python dependencies in your local machine inside a Python directory, zip it, and upload it to AWS layers. Your Lambda function can use the layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⚠️ Important Note:&lt;/strong&gt; Creating a zip package from a non-Linux environment (even Mac) will NOT WORK with Lambda.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker Method
&lt;/h3&gt;

&lt;p&gt;Alternatively, you can use Docker with &lt;code&gt;--platform linux/amd64&lt;/code&gt; flag to build the layer with the help of sam/build-python Docker images.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--platform&lt;/span&gt; linux/amd64 &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;:/var/task &lt;span class="s2"&gt;"public.ecr.aws/sam/build-python3.10"&lt;/span&gt; /bin/sh &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"pip install -r requirements.txt -t python/; exit"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cross-Account Sharing
&lt;/h3&gt;

&lt;p&gt;If you want to share your Layer in a different AWS account, add permissions by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws lambda add-layer-version-permission &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--layer-name&lt;/span&gt; &amp;lt;Layer_name&amp;gt; &lt;span class="se"&gt;\ &lt;/span&gt;       
    &lt;span class="nt"&gt;--statement-id&lt;/span&gt; xaccount &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--action&lt;/span&gt; lambda:GetLayerVersion  &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--principal&lt;/span&gt; &amp;lt;AWS_ACCOUNT_ID&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--version-number&lt;/span&gt; &amp;lt;Layer_Version&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt; Managing Lambda Layers manually can be a lot of pain when every other month you need to update the libraries to use new versions of libraries to take your security team off your back.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Automation Solution
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Automating Layer Build with Terraform and GitHub Actions
&lt;/h3&gt;

&lt;p&gt;Before we start writing Terraform, create the file structure in your VSCode as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lambda-python-layers/
├── .github/
│   └── workflows/
│       └── build-lambda-layers.yml
├── layers/
│   ├── layer1/
│   │   └── requirements.txt
│   ├── layer2/
│   │   └── requirements.txt
│   └── common-scripts/
│       ├── logger.py
│       └── db_connection.py
├── providers.tf
├── variables.tf
├── config.tf
├── s3.tf
└── main.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Defining Python Dependencies for Layers
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;layers/layer1/requirements.txt&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws-psycopg2
requests==2.32.3
urllib3==2.2.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;layers/layer2/requirements.txt&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pyzipper==0.3.6
pycryptodome==3.20.0
cryptography==41.0.7
PyPDF2==3.0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sometimes we also need to use common scripts such as logger, and db-connection scripts used by Lambda functions. We can package them with the dependencies to avoid packaging them with Lambda code or building separate layers for these scripts.&lt;/p&gt;

&lt;p&gt;Place those Python scripts in &lt;code&gt;layers/common-scripts/&lt;/code&gt; path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation Guide
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Writing Terraform Configuration
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Note:&lt;/strong&gt; You can find the complete code for this blog in my &lt;a href="https://github.com/akhileshmishrabiz/Devops-zero-to-hero" rel="noopener noreferrer"&gt;public GitHub repo&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;providers.tf&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Setting up Terraform providers and remote backend using S3 bucket I have already created to store the Terraform state files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 1.6"&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/aws"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 5.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="s2"&gt;"s3"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"your-terraform-state-bucket"&lt;/span&gt;
    &lt;span class="nx"&gt;key&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lambda-layers/terraform.tfstate"&lt;/span&gt;
    &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ap-south-1"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_region&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;variables.tf&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We will store default values for Python layers such as runtime, and compatible runtimes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"aws_region"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWS region"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ap-south-1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"python_runtime"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Python runtime version"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"python3.10"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;config.tf&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We will create local variables to store the layer details such as layer name and path to the dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;layer_definitions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;identifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"layer1"&lt;/span&gt;
      &lt;span class="nx"&gt;path&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path.module}/layers/layer1"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;identifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"layer2"&lt;/span&gt;
      &lt;span class="nx"&gt;path&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path.module}/layers/layer2"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;s3.tf&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use the data source to get the AWS account ID and use it as a prefix to ensure the unique name of the S3 bucket, and use it to store the layers packages in .zip format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_caller_identity"&lt;/span&gt; &lt;span class="s2"&gt;"current"&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"lambda_layers"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${data.aws_caller_identity.current.account_id}-lambda-layers-bucket"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket_versioning"&lt;/span&gt; &lt;span class="s2"&gt;"lambda_layers"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_layers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;versioning_configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Enabled"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;main.tf&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It will use the Terraform module (terraform-aws-modules/lambda/aws) for Lambda to build Python layers. I will package the custom scripts along with the layers for usability:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"layers"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-aws-modules/lambda/aws"&lt;/span&gt;
  &lt;span class="nx"&gt;version&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 6.0"&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;layer_definitions&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;identifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;create_layer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nx"&gt;layer_name&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;identifier&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Lambda layer for ${each.value.identifier}"&lt;/span&gt;
  &lt;span class="nx"&gt;compatible_runtimes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;python_runtime&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;source_path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;path&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;
      &lt;span class="nx"&gt;pip_requirements&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="nx"&gt;prefix_in_zip&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"python"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;path&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path.module}/layers/common-scripts"&lt;/span&gt;
      &lt;span class="nx"&gt;prefix_in_zip&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"python"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;store_on_s3&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;s3_bucket&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_layers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cross-Account Layer Sharing (Optional)
&lt;/h3&gt;

&lt;p&gt;If you want to share the layers across multiple accounts, add these configurations:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Append to config.tf:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# List of layer names&lt;/span&gt;
  &lt;span class="nx"&gt;layer_names&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;layer_definitions&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="c1"&gt;# Accounts that should have permission on layer version&lt;/span&gt;
  &lt;span class="nx"&gt;allowed_accounts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"AWS_ACCOUNT_ID_1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"AWS_ACCOUNT_ID_2"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="c1"&gt;# Mapping layers -&amp;gt; aws accounts for permission on layer version&lt;/span&gt;
  &lt;span class="nx"&gt;layers_to_accounts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;layer&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;layer_names&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allowed_accounts&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;id&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${layer}-${account}"&lt;/span&gt;
        &lt;span class="nx"&gt;layer&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;layer&lt;/span&gt;
        &lt;span class="nx"&gt;account&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="c1"&gt;# Map to be used by for_each loop for resource&lt;/span&gt;
  &lt;span class="nx"&gt;layers_to_accounts_map&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;layers_to_accounts&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Append to main.tf:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lambda_layer_version_permission"&lt;/span&gt; &lt;span class="s2"&gt;"lambda_layer_permission"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;layers_to_accounts_map&lt;/span&gt;
  &lt;span class="nx"&gt;layer_name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;lambda_layer_layer_arn&lt;/span&gt;
  &lt;span class="nx"&gt;version_number&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;lambda_layer_version&lt;/span&gt;
  &lt;span class="nx"&gt;principal&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;
  &lt;span class="nx"&gt;action&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lambda:GetLayerVersion"&lt;/span&gt;
  &lt;span class="nx"&gt;statement_id&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${each.value.layer}-${each.value.account}-${random_integer.random.result}"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"random_integer"&lt;/span&gt; &lt;span class="s2"&gt;"random"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;min&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="nx"&gt;max&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  GitHub Actions Workflow
&lt;/h3&gt;

&lt;p&gt;Here is the GitHub Action workflow that will build the layers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;.github/workflows/build-lambda-layers.yml&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build Lambda Layers with Terraform&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# On push to main branch when there is a change in dependencies&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;lambda-python-layers/layers/&lt;/span&gt;
  &lt;span class="c1"&gt;# Run manually&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;terraform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_ACCESS_KEY_ID }}&lt;/span&gt;
      &lt;span class="na"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_SECRET_ACCESS_KEY }}&lt;/span&gt;
      &lt;span class="na"&gt;AWS_REGION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ap-south-1&lt;/span&gt;
      &lt;span class="na"&gt;TERRAFORM_VER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.6.6&lt;/span&gt;
      &lt;span class="na"&gt;TERRAFORM_PATH&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;lambda-python-layers/&lt;/span&gt;
      &lt;span class="na"&gt;PYTHON_VERSION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.10'&lt;/span&gt;

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

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Terraform&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hashicorp/setup-terraform@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;terraform_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.TERRAFORM_VER }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Python&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.PYTHON_VERSION }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terraform Init&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.TERRAFORM_PATH }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform init&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terraform Plan&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.TERRAFORM_PATH }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform plan -out=tfplan&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terraform Apply&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.TERRAFORM_PATH }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform apply -auto-approve tfplan&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Workflow Explanation
&lt;/h3&gt;

&lt;p&gt;This GitHub Action workflow is designed to deploy Terraform to build the layers for a specific path (&lt;code&gt;layers/&lt;/code&gt;) within a repository.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;On push to the main branch, specifically if files within the &lt;code&gt;lambda-python-layers/layers/&lt;/code&gt; directory are updated&lt;/li&gt;
&lt;li&gt;Can be triggered manually via &lt;code&gt;workflow_dispatch&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Job runs on the &lt;code&gt;ubuntu-latest&lt;/code&gt; runner&lt;/li&gt;
&lt;li&gt;AWS credentials and region are sourced from GitHub Secrets&lt;/li&gt;
&lt;li&gt;Terraform version (&lt;code&gt;1.6.6&lt;/code&gt;) and Python version (&lt;code&gt;3.10&lt;/code&gt;) are specified&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ol&gt;
&lt;li&gt;Checkout repository using &lt;code&gt;actions/checkout@v4&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Install Terraform using &lt;code&gt;hashicorp/setup-terraform@v3&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Install Python 3.10 using &lt;code&gt;actions/setup-python@v5&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run Terraform commands to deploy the layers&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Running the Automation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step-by-Step Setup
&lt;/h3&gt;

&lt;p&gt;You can clone the code from my &lt;a href="https://github.com/akhileshmishrabiz/Devops-zero-to-hero" rel="noopener noreferrer"&gt;Public GitHub Repo&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clone my GitHub repo&lt;/span&gt;
git clone https://github.com/akhileshmishrabiz/Devops-zero-to-hero.git

&lt;span class="c"&gt;# Create a folder/directory &lt;/span&gt;
&lt;span class="nb"&gt;mkdir &lt;/span&gt;lambda-layer-example
&lt;span class="nb"&gt;cd &lt;/span&gt;lambda-layer-example

&lt;span class="c"&gt;# Copy the terraform from Devops-zero-to-hero/lambda-python-layers&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; Devops-zero-to-hero/lambda-python-layers &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Create a .github/workflows folder to store the GitHub Action workflow&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; .github/workflows

&lt;span class="c"&gt;# Create a .yml file &lt;/span&gt;
&lt;span class="nb"&gt;touch&lt;/span&gt; .github/workflows/layer-build.yml

&lt;span class="c"&gt;# Copy the workflow code from the cloned repo&lt;/span&gt;
&lt;span class="nb"&gt;cp &lt;/span&gt;Devops-zero-to-hero/.github/workflows/build-lambda-layer.yml .github/workflows/layer-build.yml

&lt;span class="c"&gt;# Create a github repository and push the code from lambda-layer-example to your repo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configure GitHub Secrets
&lt;/h3&gt;

&lt;p&gt;Create access and secret keys and store them as secrets in your GitHub repo:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to your repo → &lt;strong&gt;Settings&lt;/strong&gt; → &lt;strong&gt;Secrets and variables&lt;/strong&gt; → &lt;strong&gt;Actions&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Create two secrets:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; = Your AWS Access Key&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt; = Your AWS Secret Key&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Deploy the Automation
&lt;/h3&gt;

&lt;p&gt;Push the code to trigger the workflow, or run it manually from the Actions tab.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4m9zd1co1nq89vk3kfqi.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4m9zd1co1nq89vk3kfqi.webp" alt="Image description" width="800" height="433"&gt;&lt;/a&gt;&lt;br&gt;
You can see the layers in your AWS account: &lt;strong&gt;Lambda&lt;/strong&gt; → &lt;strong&gt;Layers&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  AWS Lambda Layers in Console
&lt;/h3&gt;

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

&lt;h2&gt;
  
  
  Benefits of This Automation
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Before Automation&lt;/th&gt;
&lt;th&gt;After Automation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;❌ Manual zip creation&lt;/td&gt;
&lt;td&gt;✅ Automated builds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;❌ Platform compatibility issues&lt;/td&gt;
&lt;td&gt;✅ Consistent Linux builds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;❌ Version management pain&lt;/td&gt;
&lt;td&gt;✅ Automated versioning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;❌ Manual uploads&lt;/td&gt;
&lt;td&gt;✅ Automatic deployment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;❌ Cross-account sharing complexity&lt;/td&gt;
&lt;td&gt;✅ Terraform-managed permissions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;p&gt;This automation solution has saved my team countless hours of manual Lambda Layer management. Instead of spending time on repetitive packaging and uploading tasks, we can focus on writing better Lambda functions.&lt;/p&gt;

&lt;p&gt;The combination of Terraform and GitHub Actions provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Consistency&lt;/strong&gt; across environments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reproducibility&lt;/strong&gt; of builds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version control&lt;/strong&gt; for dependencies&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automated security updates&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Terraform code for this is available &lt;a href="https://github.com/akhileshmishrabiz/Devops-zero-to-hero/tree/main/lambda-python-layers" rel="noopener noreferrer"&gt;here&lt;/a&gt;, and the GitHub Action workflow is &lt;a href="https://github.com/akhileshmishrabiz/Devops-zero-to-hero/blob/main/.github/workflows/build-lambda-layer.yml" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  About Me
&lt;/h2&gt;

&lt;p&gt;I am Akhilesh Mishra, a self-taught DevOps engineer with 11+ years working on private and public cloud (GCP &amp;amp; AWS) technologies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connect with me:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/in/akhilesh-mishra-0ab886124/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/akhileshmishrabiz" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Tags
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;#aws&lt;/code&gt; &lt;code&gt;#lambda&lt;/code&gt; &lt;code&gt;#terraform&lt;/code&gt; &lt;code&gt;#automation&lt;/code&gt; &lt;code&gt;#devops&lt;/code&gt; &lt;code&gt;#serverless&lt;/code&gt; &lt;code&gt;#githubactions&lt;/code&gt; &lt;code&gt;#infrastructure&lt;/code&gt; &lt;code&gt;#iac&lt;/code&gt; &lt;code&gt;#cicd&lt;/code&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Found this automation helpful? Follow for more AWS and DevOps content!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>lambda</category>
      <category>automation</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>You Are Making A Big Mistake By Not Scanning Your Terraform Code</title>
      <dc:creator>Akhilesh Mishra</dc:creator>
      <pubDate>Fri, 27 Jun 2025 09:55:32 +0000</pubDate>
      <link>https://forem.com/livingdevops/you-are-making-a-big-mistake-by-not-scanning-your-terraform-code-350k</link>
      <guid>https://forem.com/livingdevops/you-are-making-a-big-mistake-by-not-scanning-your-terraform-code-350k</guid>
      <description>&lt;p&gt;&lt;em&gt;Stop infrastructure misconfigurations before they become security nightmares - Master Terrascan for bulletproof IaC security&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1558494949-ef010cbdcc31%3Fixlib%3Drb-4.0.3%26ixid%3DM3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%253D%253D%26auto%3Dformat%26fit%3Dcrop%26w%3D2089%26q%3D80" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1558494949-ef010cbdcc31%3Fixlib%3Drb-4.0.3%26ixid%3DM3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%253D%253D%26auto%3Dformat%26fit%3Dcrop%26w%3D2089%26q%3D80" alt="Infrastructure security scanning with Terrascan" width="2089" height="1172"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Complete DevSecOps Guide to Terrascan for Infrastructure as Code Security
&lt;/h2&gt;

&lt;p&gt;Last year, someone I know deployed a Kubernetes cluster that accidentally exposed internal APIs to the internet because of a misconfigured network policy. They caught it during a routine scan, but it could have been much worse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure misconfigurations aren't just embarrassing — they're potentially catastrophic.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Infrastructure misconfigurations have been behind some of the most notorious security incidents in recent years. From the Capital One breach caused by a misconfigured WAF to the thousands of Elasticsearch instances leaking sensitive data, the pattern is clear: &lt;strong&gt;the security of your cloud depends heavily on your configuration, not just your code.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Security in infrastructure code often feels like an afterthought. We're so focused on making things work that we forget to make them secure.&lt;/p&gt;

&lt;p&gt;At my company, we already use Checkov for basic security scanning, but we've found limitations in its policy coverage and customization options. That's why we're adding &lt;strong&gt;Terrascan&lt;/strong&gt; to our security toolkit — it offers deeper analysis, better policy flexibility, and more comprehensive coverage across different cloud providers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Why Infrastructure Security Scanning Matters&lt;/li&gt;
&lt;li&gt;What is Terrascan?&lt;/li&gt;
&lt;li&gt;Installation and Setup&lt;/li&gt;
&lt;li&gt;Basic Usage and Commands&lt;/li&gt;
&lt;li&gt;Advanced Scanning Techniques&lt;/li&gt;
&lt;li&gt;Docker Integration&lt;/li&gt;
&lt;li&gt;CI/CD Pipeline Integration&lt;/li&gt;
&lt;li&gt;Custom Security Policies&lt;/li&gt;
&lt;li&gt;Best Practices and Recommendations&lt;/li&gt;
&lt;li&gt;Troubleshooting Common Issues&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Infrastructure Security Scanning Matters
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Hidden Cost of Misconfigurations
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Real-world impact of IaC security failures:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🏦 &lt;strong&gt;Capital One (2019)&lt;/strong&gt;: Misconfigured WAF led to 100+ million customer records exposed&lt;/li&gt;
&lt;li&gt;📊 &lt;strong&gt;Elasticsearch incidents&lt;/strong&gt;: Thousands of instances leaked sensitive data due to default configurations&lt;/li&gt;
&lt;li&gt;☁️ &lt;strong&gt;AWS S3 breaches&lt;/strong&gt;: Countless incidents from misconfigured bucket permissions&lt;/li&gt;
&lt;li&gt;🔐 &lt;strong&gt;Kubernetes exposures&lt;/strong&gt;: APIs accidentally exposed to the internet via network policies&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Common Infrastructure Security Risks
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Risk Category&lt;/th&gt;
&lt;th&gt;Examples&lt;/th&gt;
&lt;th&gt;Impact Level&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Network Security&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Open security groups, exposed databases&lt;/td&gt;
&lt;td&gt;🔴 Critical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Access Control&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Overprivileged IAM roles, public buckets&lt;/td&gt;
&lt;td&gt;🔴 Critical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Encryption&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Unencrypted storage, data in transit&lt;/td&gt;
&lt;td&gt;🟠 High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Compliance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Missing logging, audit trails&lt;/td&gt;
&lt;td&gt;🟡 Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Resource Management&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Untagged resources, cost overruns&lt;/td&gt;
&lt;td&gt;🟡 Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What is Terrascan?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Terrascan&lt;/strong&gt; is an open-source static code analyzer created by Tenable, specially designed for Infrastructure as Code security. It's your first line of defense against configuration vulnerabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features That Set Terrascan Apart
&lt;/h3&gt;

&lt;p&gt;✅ &lt;strong&gt;Multi-Cloud Support&lt;/strong&gt;: AWS, Azure, GCP, Kubernetes&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Multiple IaC Tools&lt;/strong&gt;: Terraform, Helm, Kubernetes, Docker&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Custom Policies&lt;/strong&gt;: Write organization-specific security rules&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;CI/CD Integration&lt;/strong&gt;: Seamless DevOps workflow integration&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Compliance Frameworks&lt;/strong&gt;: SOC 2, PCI DSS, GDPR, HIPAA&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Active Community&lt;/strong&gt;: Regular updates and policy additions  &lt;/p&gt;
&lt;h3&gt;
  
  
  Terrascan vs Competitors
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Terrascan&lt;/th&gt;
&lt;th&gt;Checkov&lt;/th&gt;
&lt;th&gt;tfsec&lt;/th&gt;
&lt;th&gt;Bridgecrew&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Open Source&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Free&lt;/td&gt;
&lt;td&gt;✅ Free&lt;/td&gt;
&lt;td&gt;✅ Free&lt;/td&gt;
&lt;td&gt;❌ Paid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Custom Policies&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Rego&lt;/td&gt;
&lt;td&gt;✅ Python&lt;/td&gt;
&lt;td&gt;❌ Limited&lt;/td&gt;
&lt;td&gt;✅ Advanced&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multi-Cloud&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Excellent&lt;/td&gt;
&lt;td&gt;✅ Good&lt;/td&gt;
&lt;td&gt;✅ Good&lt;/td&gt;
&lt;td&gt;✅ Excellent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Docker Scanning&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Learning Curve&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;🟡 Moderate&lt;/td&gt;
&lt;td&gt;🟢 Easy&lt;/td&gt;
&lt;td&gt;🟢 Easy&lt;/td&gt;
&lt;td&gt;🔴 Steep&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  Installation and Setup
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Method 1: Homebrew (Mac/Linux)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install via Homebrew (recommended for Mac)&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;terrascan

&lt;span class="c"&gt;# Verify installation&lt;/span&gt;
terrascan version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Method 2: Manual Installation
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Download latest release&lt;/span&gt;
curl &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://api.github.com/repos/tenable/terrascan/releases/latest | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"https://.+?_Darwin_x86_64.tar.gz"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; terrascan.tar.gz

&lt;span class="c"&gt;# Extract and install&lt;/span&gt;
&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xf&lt;/span&gt; terrascan.tar.gz terrascan &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm &lt;/span&gt;terrascan.tar.gz
&lt;span class="nb"&gt;sudo install &lt;/span&gt;terrascan /usr/local/bin &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm &lt;/span&gt;terrascan

&lt;span class="c"&gt;# Verify installation&lt;/span&gt;
terrascan version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Method 3: Docker
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run as Docker container&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; tenable/terrascan version

&lt;span class="c"&gt;# Scan current directory&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;:/iac"&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; /iac tenable/terrascan scan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Basic Usage and Commands
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Your First Scan
&lt;/h3&gt;

&lt;p&gt;Navigate to any directory containing Infrastructure as Code files and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Basic scan - scans all supported files in current directory&lt;/span&gt;
terrascan scan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Example output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Violated Policies (7):
  LOW     | Ensure S3 bucket has versioning enabled
  MEDIUM  | Ensure S3 bucket has access logging enabled  
  HIGH    | Ensure S3 bucket is not publicly readable
  HIGH    | Security group allows ingress from 0.0.0.0/0 to port 22
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I ran this command with my code and this is how the results look like&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Essential Command Flags
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Get comprehensive help&lt;/span&gt;
terrascan-help
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terrascan scan --help
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Flags:
      --config-only               will output resource config (should only be used for debugging purposes)
      --config-with-error         will output resource config and errors (if any)
      --find-vuln                 fetches vulnerabilities identified in Docker images
  -h, --help                      help for scan
  -d, --iac-dir string            path to a directory containing one or more IaC files (default ".")
  -f, --iac-file string           path to a single IaC file
  -i, --iac-type string           iac type (arm, cft, docker, helm, k8s, kustomize, terraform, tfplan)
      --iac-version string        iac version (arm: v1, cft: v1, docker: v1, helm: v3, k8s: v1, kustomize: v2, v3, v4, terraform: v12, v13, v14, v15, tfplan: v1)
      --non-recursive             do not scan directories and modules recursively
  -p, --policy-path stringArray   policy path directory
  -t, --policy-type strings       policy type (all, aws, azure, docker, gcp, github, k8s) (default [all])
  -r, --remote-type string        type of remote backend (git, s3, gcs, http, terraform-registry)
  -u, --remote-url string         url pointing to remote IaC repository
      --repo-ref string           branch of the repo being scanned
      --repo-url string           URL of the repo being scanned, will be reflected in scan summary
      --scan-rules strings        one or more rules to scan (example: --scan-rules="ruleID1,ruleID2")
      --severity string           minimum severity level of the policy violations to be reported by terrascan
      --show-passed               display passed rules, along with violations
      --skip-rules strings        one or more rules to skip while scanning (example: --skip-rules="ruleID1,ruleID2")
      --use-colors string         color output (auto, t, f) (default "auto")
      --use-terraform-cache       use terraform init cache for remote modules (when used directory scan will be non recursive, flag applicable only with terraform IaC provider)
      --values-files strings      one or more values files to scan(applicable for iactype=helm) (example: --values-files="file1-values.yaml,file2-values.yaml")
  -v, --verbose                   will show violations with details (applicable for default output)
      --webhook-token string      optional token used when sending authenticated requests to the notification webhook
      --webhook-url string        webhook URL where Terrascan will send JSON scan report and normalized IaC JSON


Global Flags:
  -c, --config-path string      config file path
  -l, --log-level string        log level (debug, info, warn, error, panic, fatal) (default "info")
      --log-output-dir string   directory path to write the log and output files
  -x, --log-type string         log output type (console, json) (default "console")
  -o, --output string           output type (human, json, yaml, xml, junit-xml, sarif, github-sarif) (default "human")
      --temp-dir string         temporary directory path to download remote repository,module and templates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Target-Specific Scanning
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Scan specific cloud provider&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;-t&lt;/span&gt; aws           &lt;span class="c"&gt;# AWS only&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;-t&lt;/span&gt; azure         &lt;span class="c"&gt;# Azure only  &lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;-t&lt;/span&gt; gcp           &lt;span class="c"&gt;# GCP only&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;-t&lt;/span&gt; k8s           &lt;span class="c"&gt;# Kubernetes only&lt;/span&gt;

&lt;span class="c"&gt;# Scan specific IaC type&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;-i&lt;/span&gt; terraform     &lt;span class="c"&gt;# Terraform files&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;-i&lt;/span&gt; helm          &lt;span class="c"&gt;# Helm charts&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;-i&lt;/span&gt; docker        &lt;span class="c"&gt;# Dockerfiles&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;-i&lt;/span&gt; k8s           &lt;span class="c"&gt;# Kubernetes manifests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Output Format Options
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# JSON output for automation&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;-o&lt;/span&gt; json

&lt;span class="c"&gt;# YAML output&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;-o&lt;/span&gt; yaml

&lt;span class="c"&gt;# SARIF format for GitHub integration&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;-o&lt;/span&gt; sarif

&lt;span class="c"&gt;# JUnit XML for CI reporting&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;-o&lt;/span&gt; junit-xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Try out different flags with the command
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terrascan scan -t gcp -i terraform  -o json --severity "High"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h2&gt;
  
  
  Advanced Scanning Techniques
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Severity-Based Filtering
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Only show high severity issues&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;--severity&lt;/span&gt; &lt;span class="s2"&gt;"High"&lt;/span&gt;

&lt;span class="c"&gt;# Show medium and high severity&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;--severity&lt;/span&gt; &lt;span class="s2"&gt;"Medium"&lt;/span&gt;

&lt;span class="c"&gt;# Verbose output with details&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  File and Directory Targeting
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Scan specific file&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;-f&lt;/span&gt; main.tf

&lt;span class="c"&gt;# Scan specific directory&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;-d&lt;/span&gt; /path/to/terraform/modules

&lt;span class="c"&gt;# Non-recursive scan (current directory only)&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;--non-recursive&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Rule Management
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Skip specific rules&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;--skip-rules&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"AWS.S3.DS.High.1041,AWS.EC2.NetworkACL.Medium.0506"&lt;/span&gt;

&lt;span class="c"&gt;# Scan only specific rules&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;--scan-rules&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"AWS.S3.DS.High.1041,AWS.RDS.DS.High.1025"&lt;/span&gt;

&lt;span class="c"&gt;# Show passed rules along with violations&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;--show-passed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Remote Repository Scanning
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Scan remote Git repository&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;-r&lt;/span&gt; git &lt;span class="nt"&gt;-u&lt;/span&gt; https://github.com/user/repo.git

&lt;span class="c"&gt;# Scan specific branch&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;-r&lt;/span&gt; git &lt;span class="nt"&gt;-u&lt;/span&gt; https://github.com/user/repo.git &lt;span class="nt"&gt;--repo-ref&lt;/span&gt; develop

&lt;span class="c"&gt;# Scan Terraform modules from registry&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;-r&lt;/span&gt; terraform-registry &lt;span class="nt"&gt;-u&lt;/span&gt; terraform-aws-modules/vpc/aws
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configuration Debugging
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Output resource configuration for debugging&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;--config-only&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; json

&lt;span class="c"&gt;# Show configuration with errors&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;--config-with-error&lt;/span&gt;

&lt;span class="c"&gt;# Enable debug logging&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;-l&lt;/span&gt; debug
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Docker Integration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Scanning Dockerfiles
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Scan Dockerfiles for vulnerabilities&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;-i&lt;/span&gt; docker


&lt;span class="o"&gt;![&lt;/span&gt;Image description]&lt;span class="o"&gt;(&lt;/span&gt;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wn8vko9mi99c9mc18sxd.webp&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Find vulnerabilities in container images&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;-i&lt;/span&gt; docker &lt;span class="nt"&gt;--find-vuln&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Running Terrascan in Docker
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Basic Docker usage&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;:/iac"&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; /iac tenable/terrascan scan

&lt;span class="c"&gt;# With specific parameters&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;:/iac"&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; /iac tenable/terrascan scan &lt;span class="nt"&gt;-i&lt;/span&gt; terraform &lt;span class="nt"&gt;-t&lt;/span&gt; aws &lt;span class="nt"&gt;-o&lt;/span&gt; json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  CI/CD Pipeline Integration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  GitHub Actions Integration
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;.github/workflows/terrascan.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terrascan Security Scan&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;develop&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;terrascan_job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terrascan Security Analysis&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout repository&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Terrascan&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terrascan&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tenable/terrascan-action@main&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;iac_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;terraform'&lt;/span&gt;
        &lt;span class="na"&gt;iac_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;v15'&lt;/span&gt;
        &lt;span class="na"&gt;policy_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;aws'&lt;/span&gt;
        &lt;span class="na"&gt;only_warn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;sarif_upload&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload SARIF file&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github/codeql-action/upload-sarif@v2&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;sarif_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terrascan.sarif&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  GitLab CI Integration
&lt;/h3&gt;

&lt;p&gt;Add to &lt;code&gt;.gitlab-ci.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;terrascan_security&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;security&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tenable/terrascan:latest&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;terrascan scan -i terraform -t aws -o json --config-path terrascan-config.toml&lt;/span&gt;
  &lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;reports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;sast&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terrascan-results.json&lt;/span&gt;
  &lt;span class="na"&gt;only&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;merge_requests&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Jenkins Integration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;
    &lt;span class="n"&gt;stages&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Terrascan Security Scan'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;script&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'tenable/terrascan:latest'&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;inside&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'terrascan scan -i terraform -o junit-xml &amp;gt; terrascan-results.xml'&lt;/span&gt;
                    &lt;span class="o"&gt;}&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;always&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;publishTestResults&lt;/span&gt; &lt;span class="nl"&gt;testResultsPattern:&lt;/span&gt; &lt;span class="s1"&gt;'terrascan-results.xml'&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pre-commit Hooks
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;.pre-commit-config.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;repos&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/tenable/terrascan&lt;/span&gt;
  &lt;span class="na"&gt;rev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1.18.0&lt;/span&gt;
  &lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terrascan&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;scan'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-i'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;terraform'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--severity'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;High'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;verbose&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install and use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install pre-commit&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;pre-commit

&lt;span class="c"&gt;# Install hooks&lt;/span&gt;
pre-commit &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# Run manually&lt;/span&gt;
pre-commit run terrascan &lt;span class="nt"&gt;--all-files&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Custom Security Policies
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Understanding Rego Policy Language
&lt;/h3&gt;

&lt;p&gt;Terrascan uses &lt;strong&gt;Rego&lt;/strong&gt; (from Open Policy Agent) for custom policies. This allows you to create organization-specific security rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Prevent Public SSH Access
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;custom-policies/terraform/no_public_ssh/rule.rego&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rego"&gt;&lt;code&gt;&lt;span class="ow"&gt;package&lt;/span&gt; &lt;span class="n"&gt;accurics&lt;/span&gt;

&lt;span class="c1"&gt;# Rule to deny SSH access from 0.0.0.0/0&lt;/span&gt;
&lt;span class="n"&gt;deny_public_ssh&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ingress&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_port&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="m"&gt;22&lt;/span&gt;
    &lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_port&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;22&lt;/span&gt;
    &lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cidr_blocks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;

    &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Security group '%s' allows SSH (port 22) from 0.0.0.0/0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example: Enforce Resource Tagging
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;custom-policies/terraform/require_tags/rule.rego&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rego"&gt;&lt;code&gt;&lt;span class="ow"&gt;package&lt;/span&gt; &lt;span class="n"&gt;accurics&lt;/span&gt;

&lt;span class="n"&gt;required_tags&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Environment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Owner"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Project"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Check if EC2 instances have required tags&lt;/span&gt;
&lt;span class="n"&gt;deny_missing_tags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aws_instance&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;missing_tags&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;required_tags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;missing_tags&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"EC2 instance '%s' is missing required tag '%s'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;missing_tags&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using Custom Policies
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Scan with custom policies&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;-p&lt;/span&gt; ./custom-policies

&lt;span class="c"&gt;# Combine with built-in policies&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;-p&lt;/span&gt; ./custom-policies &lt;span class="nt"&gt;-t&lt;/span&gt; aws
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Policy Development Best Practices
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start Simple&lt;/strong&gt;: Begin with basic rules and iterate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test Thoroughly&lt;/strong&gt;: Use different scenarios to validate policies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document Rules&lt;/strong&gt;: Include clear descriptions and examples&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version Control&lt;/strong&gt;: Store policies in Git with your infrastructure code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Peer Review&lt;/strong&gt;: Have security and DevOps teams review custom policies&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Best Practices and Recommendations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Implement Shift-Left Security
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run in development environment&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;-i&lt;/span&gt; terraform &lt;span class="nt"&gt;--severity&lt;/span&gt; High

&lt;span class="c"&gt;# Integrate with IDE plugins&lt;/span&gt;
&lt;span class="c"&gt;# Use VS Code Terrascan extension&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Create Security Gates
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# GitHub Actions example with failure conditions&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terrascan Security Gate&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;terrascan scan -i terraform -o json &amp;gt; results.json&lt;/span&gt;
    &lt;span class="s"&gt;HIGH_ISSUES=$(jq '.results.violations[] | select(.severity == "HIGH") | length' results.json)&lt;/span&gt;
    &lt;span class="s"&gt;if [ "$HIGH_ISSUES" -gt 0 ]; then&lt;/span&gt;
      &lt;span class="s"&gt;echo "❌ High severity security issues found. Blocking deployment."&lt;/span&gt;
      &lt;span class="s"&gt;exit 1&lt;/span&gt;
    &lt;span class="s"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Configure Organization Policies
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;terrascan-config.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[rules]&lt;/span&gt;
    &lt;span class="py"&gt;skip-rules&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s"&gt;"AWS.S3.DS.High.1041"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c"&gt;# Allow specific exceptions&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nn"&gt;[severity]&lt;/span&gt;
    &lt;span class="py"&gt;level&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"MEDIUM"&lt;/span&gt;

&lt;span class="nn"&gt;[output]&lt;/span&gt;
    &lt;span class="py"&gt;format&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"json"&lt;/span&gt;

&lt;span class="nn"&gt;[notifications]&lt;/span&gt;
    &lt;span class="py"&gt;webhook-url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://hooks.slack.com/your-webhook"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Regular Policy Updates
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Stay updated with latest policies&lt;/span&gt;
docker pull tenable/terrascan:latest

&lt;span class="c"&gt;# Check for new rule releases&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;--help&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A&lt;/span&gt; 20 &lt;span class="s2"&gt;"policy-type"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Troubleshooting Common Issues
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Issue 1: False Positives
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: Legitimate configurations flagged as violations&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Skip specific rules for false positives&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;--skip-rules&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"RULE_ID_1,RULE_ID_2"&lt;/span&gt;

&lt;span class="c"&gt;# Use inline comments in Terraform&lt;/span&gt;
resource &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"example"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;# ts:skip=AWS.S3.DS.High.1041 Business requirement for public access&lt;/span&gt;
  bucket &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-public-bucket"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Issue 2: Performance with Large Codebases
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: Slow scanning on large repositories&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Use non-recursive scanning&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;--non-recursive&lt;/span&gt;

&lt;span class="c"&gt;# Scan specific directories&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;-d&lt;/span&gt; terraform/modules/security

&lt;span class="c"&gt;# Use .terrascignore file&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"*.backup"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .terrascignore
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"test/"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .terrascignore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Issue 3: Custom Policy Errors
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: Rego policy compilation errors&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Test policy syntax&lt;/span&gt;
opa &lt;span class="nb"&gt;fmt &lt;/span&gt;custom-policies/

&lt;span class="c"&gt;# Validate policy structure&lt;/span&gt;
opa &lt;span class="nb"&gt;test &lt;/span&gt;custom-policies/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Advanced Configuration Options
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Webhook Integration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Send results to webhook&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;--webhook-url&lt;/span&gt; https://your-webhook.com/endpoint &lt;span class="nt"&gt;--webhook-token&lt;/span&gt; your-token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Multi-Environment Scanning
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Development environment&lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;-d&lt;/span&gt; environments/dev &lt;span class="nt"&gt;--severity&lt;/span&gt; Low

&lt;span class="c"&gt;# Production environment  &lt;/span&gt;
terrascan scan &lt;span class="nt"&gt;-d&lt;/span&gt; environments/prod &lt;span class="nt"&gt;--severity&lt;/span&gt; High &lt;span class="nt"&gt;--config-path&lt;/span&gt; prod-config.toml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Security Scanning Metrics and KPIs
&lt;/h2&gt;

&lt;p&gt;Track your infrastructure security improvements:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;Measurement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Critical Issues&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;High severity violations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scan Coverage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;% of IaC files scanned&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Policy Compliance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&amp;gt;95%&lt;/td&gt;
&lt;td&gt;Passed rules / Total rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mean Time to Fix&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&amp;lt;24h&lt;/td&gt;
&lt;td&gt;Time from detection to resolution&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;p&gt;Infrastructure security can't be an afterthought anymore. With the rise in cloud misconfigurations leading to major breaches, scanning your Infrastructure as Code is no longer optional — it's essential.&lt;/p&gt;

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

&lt;p&gt;🔒 &lt;strong&gt;Prevention is cheaper than remediation&lt;/strong&gt; - Catch issues before deployment&lt;br&gt;&lt;br&gt;
⚡ &lt;strong&gt;Automation is key&lt;/strong&gt; - Integrate scanning into your CI/CD pipeline&lt;br&gt;&lt;br&gt;
📋 &lt;strong&gt;Custom policies matter&lt;/strong&gt; - Tailor security rules to your organization&lt;br&gt;&lt;br&gt;
📊 &lt;strong&gt;Measure and improve&lt;/strong&gt; - Track security metrics and continuously improve  &lt;/p&gt;

&lt;p&gt;Terrascan makes infrastructure security scanning accessible and powerful, automatically checking your code against hundreds of security policies while allowing complete customization for your organization's specific needs.&lt;/p&gt;

&lt;p&gt;Start implementing Terrascan today, and transform your infrastructure security from reactive to proactive.&lt;/p&gt;




&lt;h2&gt;
  
  
  Related DevSecOps Articles
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/example"&gt;Complete Docker Security Scanning Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/example"&gt;Kubernetes Security Best Practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/example"&gt;AWS Security Automation with CloudFormation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/example"&gt;Azure DevSecOps Pipeline Implementation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Resources and References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;📚 &lt;a href="https://docs.tenable.com/terrascan/" rel="noopener noreferrer"&gt;Terrascan Official Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🐙 &lt;a href="https://github.com/tenable/terrascan" rel="noopener noreferrer"&gt;Terrascan GitHub Repository&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📖 &lt;a href="https://www.openpolicyagent.org/docs/latest/policy-language/" rel="noopener noreferrer"&gt;Rego Policy Language Guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🎓 &lt;a href="https://owasp.org/www-project-devsecops-guideline/" rel="noopener noreferrer"&gt;DevSecOps Best Practices&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Tags
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;#devsecops&lt;/code&gt; &lt;code&gt;#terraform&lt;/code&gt; &lt;code&gt;#security&lt;/code&gt; &lt;code&gt;#iac&lt;/code&gt; &lt;code&gt;#cloudsecurity&lt;/code&gt; &lt;code&gt;#automation&lt;/code&gt; &lt;code&gt;#cicd&lt;/code&gt; &lt;code&gt;#scanning&lt;/code&gt; &lt;code&gt;#compliance&lt;/code&gt; &lt;code&gt;#devops&lt;/code&gt; &lt;code&gt;#kubernetes&lt;/code&gt; &lt;code&gt;#aws&lt;/code&gt; &lt;code&gt;#azure&lt;/code&gt; &lt;code&gt;#gcp&lt;/code&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Found this guide helpful? Follow for more DevSecOps and cloud security content!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connect with the author:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/in/akhilesh-mishra-0ab886124/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/akhileshmishrabiz" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Akhilesh Mishra is a DevSecOps engineer with 12+ years of experience in cloud security, infrastructure automation, and security tooling at KPMG UK.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to Auto-Generate requirements.txt for Python Projects: Complete Guide to pipreqs and pigar</title>
      <dc:creator>Akhilesh Mishra</dc:creator>
      <pubDate>Thu, 26 Jun 2025 11:29:20 +0000</pubDate>
      <link>https://forem.com/livingdevops/how-to-auto-generate-requirementstxt-for-python-projects-complete-guide-to-pipreqs-and-pigar-19p0</link>
      <guid>https://forem.com/livingdevops/how-to-auto-generate-requirementstxt-for-python-projects-complete-guide-to-pipreqs-and-pigar-19p0</guid>
      <description>&lt;p&gt;&lt;em&gt;Stop manually hunting for Python dependencies - automate requirements.txt generation with these powerful tools&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1516259762381-22954d7d3ad2%3Fixlib%3Drb-4.0.3%26ixid%3DM3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%253D%253D%26auto%3Dformat%26fit%3Dcrop%26w%3D2089%26q%3D80" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1516259762381-22954d7d3ad2%3Fixlib%3Drb-4.0.3%26ixid%3DM3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%253D%253D%26auto%3Dformat%26fit%3Dcrop%26w%3D2089%26q%3D80" alt="Python dependency management tools" width="800" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As a DevOps engineer, I frequently collaborate with multiple teams, diving into various projects and repositories. One common challenge I face is encountering Python code without a &lt;code&gt;requirements.txt&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Every time I run such code, I need to figure out and manually install the necessary dependencies. I found it a waste of time and effort. I am sure you guys also feel the same way.&lt;/p&gt;

&lt;p&gt;So I dug around, did some Google searches, experimented with a few tools, and found many good ones. My favorites are &lt;strong&gt;pipreqs&lt;/strong&gt; and &lt;strong&gt;pigar&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;In this comprehensive guide, I'll show you how to use these Python dependency management tools to generate &lt;code&gt;requirements.txt&lt;/code&gt; files automatically for any Python project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Why Not Use pip freeze?&lt;/li&gt;
&lt;li&gt;Method 1: Using pipreqs&lt;/li&gt;
&lt;li&gt;Method 2: Using pigar&lt;/li&gt;
&lt;li&gt;Comparison and Best Practices&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Not Use pip freeze?
&lt;/h2&gt;

&lt;p&gt;Before diving into the solutions, let's address why &lt;code&gt;pip freeze&lt;/code&gt; isn't always the best choice for generating &lt;code&gt;requirements.txt&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;❌ &lt;strong&gt;pip freeze problems:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only saves packages installed with &lt;code&gt;pip install&lt;/code&gt; in your current environment&lt;/li&gt;
&lt;li&gt;Includes ALL packages in the environment, even those not used in your project (if you don't use virtualenv)&lt;/li&gt;
&lt;li&gt;Sometimes you need to create &lt;code&gt;requirements.txt&lt;/code&gt; for a new project without installing modules first&lt;/li&gt;
&lt;li&gt;Can include system-wide packages that aren't relevant to your project&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;✅ &lt;strong&gt;What we need instead:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tools that analyze your actual Python code&lt;/li&gt;
&lt;li&gt;Generate requirements based on import statements&lt;/li&gt;
&lt;li&gt;Create clean, project-specific dependency lists&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Method 1: Using pipreqs - The Import-Based Analyzer
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is pipreqs?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;pipreqs&lt;/code&gt; is a Python tool that analyzes your Python files and generates &lt;code&gt;requirements.txt&lt;/code&gt; based on the actual imports in your code. It's smart, fast, and doesn't require installing packages beforehand.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up Your Environment
&lt;/h3&gt;

&lt;p&gt;I recommend working with isolated Python virtual environments for better dependency management:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a project directory&lt;/span&gt;
&lt;span class="nb"&gt;mkdir &lt;/span&gt;python-requirements-demo
&lt;span class="nb"&gt;cd &lt;/span&gt;python-requirements-demo

&lt;span class="c"&gt;# Create and activate virtual environment&lt;/span&gt;
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv venv
&lt;span class="nb"&gt;source &lt;/span&gt;venv/bin/activate  &lt;span class="c"&gt;# On Windows: venv\Scripts\activate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Installing pipreqs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;pipreqs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Practical Example: Word Cloud Generator
&lt;/h3&gt;

&lt;p&gt;Let's create a real Python project to demonstrate pipreqs. Create a directory and file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;project
&lt;span class="nb"&gt;cd &lt;/span&gt;project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;code&gt;word_cloud_generator.py&lt;/code&gt; with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;matplotlib.pyplot&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;wordcloud&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;WordCloud&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BytesIO&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_mask_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Download and process mask image from URL&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BytesIO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_word_cloud&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mask_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Generate word cloud from text with optional mask&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mask_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;mask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_mask_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mask_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;mask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="n"&gt;wordcloud&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;WordCloud&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;background_color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;white&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;contour_width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;contour_color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;black&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;figsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wordcloud&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interpolation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bilinear&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;axis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;off&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_arguments&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Parse command line arguments&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Generate a word cloud from input text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-t&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Input text for word cloud&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--mask_url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-m&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;URL of mask image for word cloud shape&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_arguments&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;create_word_cloud&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mask_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Generating requirements.txt with pipreqs
&lt;/h3&gt;

&lt;p&gt;Now comes the magic! Instead of manually figuring out dependencies, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Generate requirements.txt for the project directory&lt;/span&gt;
pipreqs project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Output in &lt;code&gt;requirements.txt&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;matplotlib==3.9.1
numpy==2.0.0
Pillow==10.4.0
Requests==2.32.3
wordcloud==1.9.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Installing and Testing Dependencies
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install all dependencies at once&lt;/span&gt;
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; project/requirements.txt

&lt;span class="c"&gt;# Test the script&lt;/span&gt;
python project/word_cloud_generator.py &lt;span class="nt"&gt;--text&lt;/span&gt; &lt;span class="s2"&gt;"Python dependency management made easy"&lt;/span&gt;

&lt;span class="c"&gt;# With mask image&lt;/span&gt;
python project/word_cloud_generator.py &lt;span class="nt"&gt;--text&lt;/span&gt; &lt;span class="s2"&gt;"pipreqs is awesome"&lt;/span&gt; &lt;span class="nt"&gt;--mask_url&lt;/span&gt; &lt;span class="s2"&gt;"https://example.com/mask.jpg"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Advanced pipreqs Options
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Generate requirements for specific directory&lt;/span&gt;
pipreqs /path/to/your/project

&lt;span class="c"&gt;# Specify output file name&lt;/span&gt;
pipreqs &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--savepath&lt;/span&gt; requirements-dev.txt

&lt;span class="c"&gt;# Force overwrite existing requirements.txt&lt;/span&gt;
pipreqs &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--force&lt;/span&gt;

&lt;span class="c"&gt;# Use local database for package names&lt;/span&gt;
pipreqs &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--use-local&lt;/span&gt;

&lt;span class="c"&gt;# Debug mode for troubleshooting&lt;/span&gt;
pipreqs &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--debug&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Method 2: Using pigar - The Advanced Dependency Scanner
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is pigar?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;pigar&lt;/code&gt; is another excellent tool for generating Python requirements. It offers more advanced features and better handling of complex projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing pigar
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Using pip&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;pigar

&lt;span class="c"&gt;# From source (latest features)&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;git+https://github.com/damnever/pigar.git@main &lt;span class="nt"&gt;--upgrade&lt;/span&gt;

&lt;span class="c"&gt;# Using conda&lt;/span&gt;
conda &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; conda-forge pigar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Real-World Example: Flask Application
&lt;/h3&gt;

&lt;p&gt;Let's test pigar with a Flask application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clone a sample Flask project&lt;/span&gt;
git clone https://github.com/akhileshmishrabiz/Devops-zero-to-hero
&lt;span class="nb"&gt;cd &lt;/span&gt;Devops-zero-to-hero/project5

&lt;span class="c"&gt;# Remove existing requirements.txt for demo&lt;/span&gt;
&lt;span class="nb"&gt;rm &lt;/span&gt;requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Generating requirements with pigar
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Generate requirements.txt for current directory&lt;/span&gt;
pigar generate

&lt;span class="c"&gt;# Check the generated file&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Automatically generated by https://github.com/damnever/pigar.

Flask==3.0.3
Flask-SQLAlchemy==3.1.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Advanced pigar Usage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Generate requirements for specific directory with custom filename&lt;/span&gt;
pigar gen &lt;span class="nt"&gt;-f&lt;/span&gt; custom-requirements.txt /path/to/project

&lt;span class="c"&gt;# Example with Python Lambda project&lt;/span&gt;
pigar gen &lt;span class="nt"&gt;-f&lt;/span&gt; python-lambda-requirements.txt python-for-devops/python-lambda-runtime/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Output for Lambda project:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Automatically generated by https://github.com/damnever/pigar.

boto3==1.34.142
colorlog==6.8.2
packaging==24.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  pigar Advanced Features
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check for outdated packages&lt;/span&gt;
pigar check

&lt;span class="c"&gt;# Search for packages&lt;/span&gt;
pigar search numpy

&lt;span class="c"&gt;# Show package information&lt;/span&gt;
pigar show requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Comparison: pipreqs vs pigar
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;pipreqs&lt;/th&gt;
&lt;th&gt;pigar&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Speed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⚡ Very Fast&lt;/td&gt;
&lt;td&gt;🔄 Moderate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Accuracy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ High&lt;/td&gt;
&lt;td&gt;✅ Very High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Local imports&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ Limited&lt;/td&gt;
&lt;td&gt;✅ Excellent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Complex projects&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Good&lt;/td&gt;
&lt;td&gt;✅ Excellent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Additional features&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ Basic&lt;/td&gt;
&lt;td&gt;✅ Package search, outdated checks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;File size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;📦 Lightweight&lt;/td&gt;
&lt;td&gt;📦 Slightly larger&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Best Practices for Python Dependency Management
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Use Virtual Environments
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Always create isolated environments&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; venv project-env
&lt;span class="nb"&gt;source &lt;/span&gt;project-env/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Pin Exact Versions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Good - specific versions
requests==2.32.3
numpy==2.0.0

# Avoid - unpinned versions
requests
numpy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Separate Development Dependencies
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create different requirement files&lt;/span&gt;
pigar gen &lt;span class="nt"&gt;-f&lt;/span&gt; requirements.txt &lt;span class="nb"&gt;.&lt;/span&gt;           &lt;span class="c"&gt;# Production&lt;/span&gt;
pigar gen &lt;span class="nt"&gt;-f&lt;/span&gt; requirements-dev.txt &lt;span class="nb"&gt;.&lt;/span&gt;       &lt;span class="c"&gt;# Development&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Regular Dependency Audits
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check for security vulnerabilities&lt;/span&gt;
pip-audit &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="c"&gt;# Update outdated packages&lt;/span&gt;
pip list &lt;span class="nt"&gt;--outdated&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Troubleshooting Common Issues
&lt;/h2&gt;

&lt;h3&gt;
  
  
  pipreqs Not Finding All Imports
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Use debug mode&lt;/span&gt;
pipreqs &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--debug&lt;/span&gt;

&lt;span class="c"&gt;# Force scan all files&lt;/span&gt;
pipreqs &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--scan-notebooks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  pigar Missing Local Modules
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Include local packages&lt;/span&gt;
pigar gen &lt;span class="nt"&gt;--with-referenced-comments&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Version Conflicts
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Generate without version pins&lt;/span&gt;
pipreqs &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--no-version&lt;/span&gt;

&lt;span class="c"&gt;# Then manually specify versions&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Automation with CI/CD
&lt;/h2&gt;

&lt;h3&gt;
  
  
  GitHub Actions Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check Requirements&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;check-deps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate requirements&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;pip install pipreqs&lt;/span&gt;
        &lt;span class="s"&gt;pipreqs . --force&lt;/span&gt;
        &lt;span class="s"&gt;git diff --exit-code requirements.txt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Both &lt;code&gt;pipreqs&lt;/code&gt; and &lt;code&gt;pigar&lt;/code&gt; are excellent tools for automating Python dependency management:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Choose pipreqs&lt;/strong&gt; for: Simple projects, fast generation, lightweight solution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose pigar&lt;/strong&gt; for: Complex projects, advanced features, comprehensive analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stop wasting time manually managing Python dependencies. These tools will save you hours and reduce deployment errors significantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Try both tools on your existing Python projects&lt;/li&gt;
&lt;li&gt;Integrate dependency generation into your development workflow&lt;/li&gt;
&lt;li&gt;Set up automated checks in your CI/CD pipeline&lt;/li&gt;
&lt;li&gt;Share this guide with your team to standardize dependency management&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Related Python Articles
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/example"&gt;Complete Guide to Python Virtual Environments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/livingdevops/i-ditched-python-built-in-logging-for-loguru-you-should-too-514m"&gt;Python Logging with Loguru - Advanced Tutorial&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tags
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;#python&lt;/code&gt; &lt;code&gt;#devops&lt;/code&gt; &lt;code&gt;#dependencies&lt;/code&gt; &lt;code&gt;#automation&lt;/code&gt; &lt;code&gt;#pip&lt;/code&gt; &lt;code&gt;#requirements&lt;/code&gt; &lt;code&gt;#development&lt;/code&gt; &lt;code&gt;#productivity&lt;/code&gt; &lt;code&gt;#tools&lt;/code&gt; &lt;code&gt;#tutorial&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Found this helpful? Follow for more Python and DevOps tutorials!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connect with the author:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/in/akhilesh-mishra-0ab886124/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/akhileshmishrabiz" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>programming</category>
      <category>python</category>
    </item>
    <item>
      <title>I Ditched Python Built-In Logging For Loguru — You Should Too</title>
      <dc:creator>Akhilesh Mishra</dc:creator>
      <pubDate>Wed, 25 Jun 2025 21:13:58 +0000</pubDate>
      <link>https://forem.com/livingdevops/i-ditched-python-built-in-logging-for-loguru-you-should-too-514m</link>
      <guid>https://forem.com/livingdevops/i-ditched-python-built-in-logging-for-loguru-you-should-too-514m</guid>
      <description>&lt;p&gt;&lt;em&gt;A Complete Guide to better logging in Python with loguru&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1551288049-bebda4e38f71%3Fixlib%3Drb-4.0.3%26ixid%3DM3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%253D%253D%26auto%3Dformat%26fit%3Dcrop%26w%3D2070%26q%3D80" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1551288049-bebda4e38f71%3Fixlib%3Drb-4.0.3%26ixid%3DM3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%253D%253D%26auto%3Dformat%26fit%3Dcrop%26w%3D2070%26q%3D80" alt="Photo by bady abbas on Unsplash" width="2070" height="1380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have been using Python's built-in logging module for years, I have always found it quite boring. It cannot be customized beyond a point.&lt;/p&gt;

&lt;p&gt;I wanted to include some color coding in my log messages, but had to use a third-party package, for it. I wished Python built-in logging module has options to customise logs with ease.&lt;/p&gt;

&lt;p&gt;Then I stumbled upon &lt;strong&gt;loguru&lt;/strong&gt; and found it interesting. I spent some time with this yesterday and fell in love with it.&lt;/p&gt;

&lt;p&gt;By the end of this blog post, you will fall for it too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Log Levels
&lt;/h2&gt;

&lt;p&gt;When talking about logging, loglevel is an important term, they are like a severity scale for your messages. By assigning different levels to log messages, it becomes easier to focus on critical issues while reducing noise from less important events while troubleshooting or monitoring.&lt;/p&gt;

&lt;p&gt;While the Python builtin module comes with &lt;code&gt;DEBUG&lt;/code&gt;, &lt;code&gt;INFO&lt;/code&gt;, &lt;code&gt;WARNING&lt;/code&gt;, &lt;code&gt;ERROR&lt;/code&gt;, and &lt;code&gt;CRITICAL&lt;/code&gt;, loguru include 2 more log levels, &lt;code&gt;TRACE&lt;/code&gt; and &lt;code&gt;SUCCESS&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is the table to remember log levels in order of increasing priority:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Priority&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TRACE&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DEBUG&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;INFO&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SUCCESS&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WARNING&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ERROR&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CRITICAL&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Enough with the talking, let me show you what we can do with &lt;strong&gt;loguru&lt;/strong&gt; and why I fell in love with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing Loguru
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;loguru
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Basic Usage
&lt;/h2&gt;

&lt;p&gt;To use Loguru, import the logger from loguru module and use it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;loguru&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hi, This is Akhilesh Mishra&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I will show how to use loguru for better logging in python&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; I love how the logs look&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Different color of each section of log&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Easy to get started with&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;critical&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;So many options to choose from&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; You see what i am talking about&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnjdq6ir2gq7vi56ojq8e.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnjdq6ir2gq7vi56ojq8e.webp" alt="Image description" width="800" height="106"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Output format:&lt;/strong&gt; &lt;code&gt;date | level | file location: scope: line number - message&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can see that the trace output is not printed. Default log level for loguru is debug.&lt;/p&gt;
&lt;h2&gt;
  
  
  Changing the Default Log Level
&lt;/h2&gt;

&lt;p&gt;We can use logger's &lt;code&gt;add()&lt;/code&gt; function to change the default loglevel, update the log's formatting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;loguru&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TRACE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hi, This is Akhilesh Mishra&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h2&gt;
  
  
  Change the Formatting with Loguru
&lt;/h2&gt;

&lt;p&gt;Unlike Python's built-in logging module, you can add a handler, update formatting, and change the log level in just one line — with &lt;code&gt;add()&lt;/code&gt; function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;loguru&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# remove the old formatting 
&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{time}::{level} --- {message}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INFO&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; Add a handler, update formatting, and change the loglevel&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; one function to rule them all&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; logger.add()&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h2&gt;
  
  
  Pretty Logging with Colors
&lt;/h2&gt;

&lt;p&gt;You can change the color of these output messages by using HTML like syntax.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &amp;lt;yellow&amp;gt;{time} &amp;lt;/yellow&amp;gt;:: &amp;lt;green&amp;gt; &amp;lt;bold&amp;gt; {level} &amp;lt;/bold&amp;gt; &amp;lt;/green&amp;gt;--- &amp;lt;blue&amp;gt; {message} &amp;lt;/blue&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Set the colors you want to use for logs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; How easy it was???&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h2&gt;
  
  
  Change the Time Formatting
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;loguru&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# MMMM D, YYYY &amp;gt; HH:mm:ss!UTC : UTC time
&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{time:MMMM D, YYYY &amp;gt; HH:mm:ss!UTC} | {level} | &amp;lt;level&amp;gt;{message} &amp;lt;/level&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; Use time format-&amp;gt; {time:MMMM D, YYYY &amp;gt; HH:mm:ss!UTC}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Use the default color from level: &amp;lt;level&amp;gt;{message} &amp;lt;/level&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can use more options with time, check the &lt;a href="https://loguru.readthedocs.io/en/stable/api/logger.html#time" rel="noopener noreferrer"&gt;loguru documentation&lt;/a&gt; for more time formatting options.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Sending Logs to a File
&lt;/h2&gt;

&lt;p&gt;Loguru sends logs to the console by default, but you can configure it to send logs to a file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;log_file_demo.log&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's use formatted logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;loguru&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file_{time}.log&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{time:MMMM D, YYYY &amp;gt; HH:mm:ss} | {level} | &amp;lt;level&amp;gt;{message} &amp;lt;/level&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;using file logging&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; will send logs to the file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h2&gt;
  
  
  Log Rotation, Retention, and Compression
&lt;/h2&gt;

&lt;p&gt;Loguru also allows you to rotate/retain/compress logs with time and size filters.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;log_rotate.log&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rotation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;500 MB&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# Automatically rotate too big file
&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;log_rotate2.log&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rotation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;12:00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;# New file is created each day at noon
&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;log_rotate3.log&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rotation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1 week&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# Once the file is too old, it's rotated
&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;log_retention.log&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retention&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10 days&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Cleanup after some time
&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;log_retention2.log&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;compression&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;zip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# Save some loved space
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  JSON Logging
&lt;/h2&gt;

&lt;p&gt;Loguru supports logging in JSON format with &lt;code&gt;serialize=True&lt;/code&gt; option.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;loguru&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{time:MMMM D: YYYY:: HH:mm:ss!UTC} | {level} | {message}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;serialize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; Its addictive, use with caution !&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I know you started liking loguru&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h2&gt;
  
  
  Adding Context to Log Messages
&lt;/h2&gt;

&lt;p&gt;Suppose you want to add some additional information to the log message for context, you can use the &lt;code&gt;bind()&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;You can add directive &lt;code&gt;extra&lt;/code&gt; to the format in your logger &lt;code&gt;add()&lt;/code&gt; method to add custom entries to the logs output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;loguru&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;

&lt;span class="c1"&gt;# Remove the default logger
&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Add a new logger that outputs to sys.stderr
&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; {level} | &amp;lt;level&amp;gt;{message}&amp;lt;/level&amp;gt; | {extra} &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Create a new logger with some initial context
&lt;/span&gt;&lt;span class="n"&gt;context_logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Akhilesh&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;demo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Log an info message with the current context
&lt;/span&gt;&lt;span class="n"&gt;context_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You can pass context with logs!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;You can further customize the context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Bind additional context to the logger and log a warning message
&lt;/span&gt;&lt;span class="n"&gt;context_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blog_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Tutorial&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You can use extra attributes to bind context!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Log a success message with additional context provided during formatting
&lt;/span&gt;&lt;span class="n"&gt;context_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Use kwargs to add context during formatting: {platform}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Medium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h2&gt;
  
  
  Using Context Managers
&lt;/h2&gt;

&lt;p&gt;We can also use the Python context manager to modify a context-local state temporarily with &lt;code&gt;contextualize()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;loguru&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; {level} | &amp;lt;level&amp;gt;{message}&amp;lt;/level&amp;gt; | {extra} &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;context_logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blog_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;do_something&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;context_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;doing something&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contextualize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;From context manager&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;do_something&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;do_something&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h2&gt;
  
  
  Filtering Logs
&lt;/h2&gt;

&lt;p&gt;Combine &lt;code&gt;bind()&lt;/code&gt; and filter for fine-grained control over your logs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;loguru&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;special.log&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;special&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;extra&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;This message is not logged to the file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;special&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;This message, though, is logged to the file!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h2&gt;
  
  
  Dynamic Values with patch()
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;patch()&lt;/code&gt; method allows you to attach dynamic values to be attached to each new message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;loguru&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{extra[utc]} - {level}- {message}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;extra&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;using patch method from loguru&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h2&gt;
  
  
  Create Custom Log Levels
&lt;/h2&gt;

&lt;p&gt;Loguru allows you to create your own log level with &lt;code&gt;level()&lt;/code&gt; function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;loguru&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;m_level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;level&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Medium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;yellow&amp;gt;&amp;lt;bold&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;icon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;n_level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;level&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Nedium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;blue&amp;gt;&amp;lt;bold&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;icon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &amp;lt;level&amp;gt; {level.icon} :: {message} &amp;lt;/level&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Medium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;This is my custom log level&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Nedium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I like having optional log levels&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h2&gt;
  
  
  Logging Exceptions
&lt;/h2&gt;

&lt;p&gt;Logging exceptions is crucial for tracking bugs, but it's not helpful if you don't know the cause. Loguru makes it easier by showing the entire stack trace, including variable values so you can identify the problem.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;loguru&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Caution, "diagnose=True" is the default and may leak sensitive data in prod
&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;loguru.log&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;backtrace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;diagnose&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;nested&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ZeroDivisionError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Did you just??&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;nested&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj27f2stspj9iveu29t36.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj27f2stspj9iveu29t36.webp" alt="Image description" width="800" height="476"&gt;&lt;/a&gt;&lt;br&gt;
You can also use it with context manager by using &lt;code&gt;logger.catch()&lt;/code&gt; decorator.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;loguru&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Caution, "diagnose=True" is the default and may leak sensitive data in prod
&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;loguru.log&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;backtrace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;diagnose&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@logger.catch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;

&lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ln1sdsyfrwvgturoqb2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ln1sdsyfrwvgturoqb2.webp" alt="Image description" width="800" height="124"&gt;&lt;/a&gt;&lt;br&gt;
By default the level you will see &lt;code&gt;ERROR&lt;/code&gt; level, but you can customize it to use different levels.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Final Words
&lt;/h2&gt;

&lt;p&gt;If you got to this point, you know how nice loguru is. If you love customization and making things pretty, you will love it.&lt;/p&gt;

&lt;p&gt;Let me know what you think about it in the comments. If you found it useful, don't forget to like and follow for more Python tutorials!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Connect with the author on &lt;a href="https://www.linkedin.com/in/akhilesh-mishra-0ab886124/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>logging</category>
    </item>
    <item>
      <title>Step-by-Step Guide to Setting OIDC With Terraform for GitHub Actions Workflows with AWS</title>
      <dc:creator>Akhilesh Mishra</dc:creator>
      <pubDate>Sun, 22 Jun 2025 10:47:22 +0000</pubDate>
      <link>https://forem.com/aws-builders/step-by-step-guide-to-setting-oidc-for-github-actions-workflows-with-aws-4ogn</link>
      <guid>https://forem.com/aws-builders/step-by-step-guide-to-setting-oidc-for-github-actions-workflows-with-aws-4ogn</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Your GitHub Actions Secrets Are the Weakest Link in Your AWS Security Chain&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Are you still using AWS access keys and secrets to authenticate your GitHub Actions with AWS in 2025?&lt;/p&gt;

&lt;p&gt;Please don't. Unless you want to wake up someday with 1000 GPU machines mining Bitcoin in your account at your expense, footing a million-dollar bill.&lt;/p&gt;

&lt;p&gt;Using long-term secrets can be a security nightmare that could expose your cloud account to a possible security incident.&lt;/p&gt;

&lt;p&gt;You might be just one exposed GitHub secret away from an AWS billing catastrophe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then what should you do?
&lt;/h2&gt;

&lt;p&gt;You should use &lt;strong&gt;OpenID Connect (OIDC)&lt;/strong&gt;, a modern, more secure way to authenticate your GitHub Actions workflows with AWS without storing long-lived credentials.&lt;/p&gt;

&lt;p&gt;You can configure the OIDC to work with certain GitHub org and GitHub repos, or you can go more granular and allow it with certain branches, tag formats, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the heck is OIDC?
&lt;/h2&gt;

&lt;p&gt;OIDC enables token-based authentication between GitHub Actions and AWS, eliminating the need to store long-lived access keys. Establishing a trusting relationship with temporary credentials significantly enhances security while simplifying the authentication process.&lt;/p&gt;

&lt;h3&gt;
  
  
  The better way to authenticate GitHub Actions with AWS is with OpenID Connect (OIDC).
&lt;/h3&gt;

&lt;p&gt;Setting up OIDC for your AWS account is very simple,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Step 1: Create an OIDC Identity Provider in AWS&lt;/li&gt;
&lt;li&gt;Step 2: Create an IAM Role for GitHub Actions&lt;/li&gt;
&lt;li&gt;Step 3: Attach the Permissions your CICD workflow needs to the Role&lt;/li&gt;
&lt;li&gt;Step 4: Update Your GitHub Actions Workflow to use the Identity provider ARN instead of access keys and secrets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can configure it from the AWS console, with AWS CLI commands or use Terraform to configure it.&lt;/p&gt;

&lt;p&gt;I will use AWS CLI to configure the OIDC first and later with Terraform(with more flexible options).&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-time implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Scenario
&lt;/h3&gt;

&lt;p&gt;I have a 3-tier application running on an EKS cluster. &lt;br&gt;
I want to build and deploy new versions of applications using the GitHub Action workflow. For simplicity, I will run kubectl commands from the workflow to deploy the new version of the application.&lt;/p&gt;
&lt;h3&gt;
  
  
  Workflow
&lt;/h3&gt;

&lt;p&gt;Look for the change in frontend/backend code and only build the images upon the code change and push them to their respective ECR repos.&lt;/p&gt;

&lt;p&gt;Configure the kubectl and authenticate to the EKS cluster&lt;br&gt;
Use the newly built images (for backend/frontend) and deploy them to the frontend/backend service.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setting Up OIDC Between GitHub Actions and AWS: A Step-by-Step Guide
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Create the file structure below
&lt;/h3&gt;

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

&lt;p&gt;Run the below command to create the directory structure&lt;br&gt;
touch configure-oidc-github.sh eks-policy.json trust-policy.json&lt;br&gt;
Copy the code to their respective files.&lt;br&gt;
&lt;code&gt;trust-policy.json&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Principal": {
          "Federated": "arn:aws:iam::$AWS_ACCOUNT_ID:oidc-provider/$OIDC_PROVIDER"
        },
        "Action": "sts:AssumeRoleWithWebIdentity",
        "Condition": {
          "StringLike": {
            "$OIDC_PROVIDER:sub": "repo:$GITHUB_ORG/$GITHUB_REPO:*"
          }
        }
      }
    ]
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;eks-policy.json&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Action": [
          "eks:DescribeCluster",
          "eks:ListClusters"
        ],
        "Resource": "*"
      },
      {
        "Effect": "Allow",
        "Action": [
          "ecr:GetAuthorizationToken",
          "ecr:BatchCheckLayerAvailability",
          "ecr:GetDownloadUrlForLayer",
          "ecr:BatchGetImage",
          "ecr:InitiateLayerUpload",
          "ecr:UploadLayerPart",
          "ecr:CompleteLayerUpload",
          "ecr:PutImage"
        ],
        "Resource": "*"
      }
    ]
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;configure-oidc-github.sh&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash
set -e

export OIDC_PROVIDER="token.actions.githubusercontent.com"
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)
export GITHUB_ORG="akhileshmishrabiz" # GitHub Org/Owner
export GITHUB_REPO="DevOpsDojo"  # Repo you want to allow to use OIDC with AWS

# Create the OIDC provider
aws iam create-open-id-connect-provider \
  --url https://$OIDC_PROVIDER \
  --client-id-list sts.amazonaws.com \
  --thumbprint-list "6938fd4d98bab03faadb97b34396831e3780aea1"

aws iam create-role \
  --role-name GitHubActionsEKSDeployRole \
  --assume-role-policy-document file://trust-policy.json

aws iam create-policy \
  --policy-name GitHubActionsEKSPolicy \
  --policy-document file://eks-policy.json

# Attach the policy to the role
aws iam attach-role-policy \
  --role-name GitHubActionsEKSDeployRole \
  --policy-arn arn:aws:iam::$AWS_ACCOUNT_ID:policy/GitHubActionsEKSPolicy

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

&lt;/div&gt;



&lt;p&gt;This script will configure the OIDC to allow my GitHub workflows in the repo &lt;a href="https://github.com/akhileshmishrabiz/DevOpsDojo" rel="noopener noreferrer"&gt;https://github.com/akhileshmishrabiz/DevOpsDojo&lt;/a&gt; to use OIDC to authenticate with AWS. &lt;/p&gt;

&lt;h3&gt;
  
  
  Let me explain the steps.
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Setting the environment variable
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export OIDC_PROVIDER="token.actions.githubusercontent.com"
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity - query "Account" - output text)
export GITHUB_ORG="akhileshmishrabiz"
export GITHUB_REPO="DevOpsDojo"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Creating the OIDC IAM provider
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws iam create-open-id-connect-provider \
--url https://$OIDC_PROVIDER \
--client-id-list sts.amazonaws.com \
--thumbprint-list "6938fd4d98bab03faadb97b34396831e3780aea1"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Creating an IAM Role for GitHub Actions
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws iam create-role \
 --role-name GitHubActionsEKSDeployRole \
--assume-role-policy-document file://trust-policy.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Creating an IAM policy for the GitHub Action role
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws iam create-policy \
--policy-name GitHubActionsEKSPolicy \
--policy-document file://eks-policy.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Attaching IAM policy with the role
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws iam attach-role-policy \
--role-name GitHubActionsEKSDeployRole \
--policy-arn arn:aws:iam::$AWS_ACCOUNT_ID:policy/GitHubActionsEKSPolicy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuring the OIDC provider for GitHub Action with AWS CLI.
&lt;/h2&gt;

&lt;p&gt;Install the AWS CLI if you haven't already done&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"
sudo installer -pkg AWSCLIV2.pkg -target /
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure the AWS credentials - with Access key and security key&lt;/p&gt;

&lt;p&gt;&lt;code&gt;aws configure&lt;br&gt;
&lt;/code&gt;# This will as you access and secret key&lt;br&gt;
Run the script to&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chmod u+ x configure-oidc-github.sh
./configure-oidc-github.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/akhileshmishrabiz/DevOpsDojo/tree/main/infra/aws-oidc-github-cli" rel="noopener noreferrer"&gt;Note: You can find the entire code for OIDC with AWS CLI here.&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will allow us to use the role &lt;code&gt;arn:aws:iam::366140438193:role/GitHubActionsEKSDeployRole&lt;/code&gt;&lt;br&gt;
to authenticate with the GitHub repo &lt;code&gt;https://github.com/akhileshmishrabiz/DevOpsDojo&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Terraform implementation
&lt;/h2&gt;

&lt;p&gt;In a production environment, we mostly use Terraform to deploy the OIDC provider for GitHub Action.&lt;/p&gt;

&lt;p&gt;Let me show how you can set up the OIDC provider with the help of Terraform and how to allow more than one repository.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Create aproviders.tf file and paste the below code into it. This will configure the Terraform version and AWS provider.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create the files,variables.tf, output.tf, main.tf, iam.tfand paste the below code into them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a terraform.tfvars file to create a map of repositories and branches.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deploy the Terraform code, it will return the Role ARN that we can use with GitHub Action to authenticate with AWS.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This will create an IAM role to use with GitHub Action, IAM policies with permissions you want your GitHub Action workflow to have, and attach them to the role. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;IAM roles's trust policy will allow the particular repositories and branches to use this role to authenticate with AWS.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;providers.tf&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform {
  required_version = "1.8.1"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~&amp;gt; 5.0"
    }
  }
}

provider "aws" {
  region = "ap-south-1"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;variables.tf&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Variables
variable "aws_region" {
  description = "AWS region"
  type        = string
  default     = "ap-south-1"
}

variable "github_repositories" {
  description = "List of GitHub repositories to grant access to"
  type = list(object({
    org    = string
    repo   = string
    branch = optional(string, "*")
  }))
  default = [
    {
      org    = "akhileshmishrabiz"
      repo   = "DevOpsDojo"
      branch = "*"
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;**main.tf&lt;br&gt;
**&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# OIDC Provider
resource "aws_iam_openid_connect_provider" "github_actions" {
  url = "https://token.actions.githubusercontent.com"

  client_id_list = [
    "sts.amazonaws.com"
  ]

  thumbprint_list = [
    "6938fd4d98bab03faadb97b34396831e3780aea1"
  ]

  tags = {
    Name = "GitHub-Actions-OIDC-Provider"
  }
}

# IAM Role
resource "aws_iam_role" "github_actions_eks_deploy_role" {
  name = "GitHubActionsEKSDeployRole"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Federated = aws_iam_openid_connect_provider.github_actions.arn
        }
        Action = "sts:AssumeRoleWithWebIdentity"
        Condition = {
          StringLike = {
            "token.actions.githubusercontent.com:sub" = [
              for repo in var.github_repositories : 
                "repo:${repo.org}/${repo.repo}:${repo.branch}"
            ]
          }
        }
      }
    ]
  })

  tags = {
    Name = "GitHub-Actions-EKS-Deploy-Role"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;iam.tf&lt;br&gt;
&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# IAM Policy
resource "aws_iam_policy" "github_actions_eks_policy" {
  name        = "GitHubActionsEKSPolicy"
  description = "Policy for GitHub Actions to access EKS and ECR"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "eks:DescribeCluster",
          "eks:ListClusters"
        ]
        Resource = "*"
      },
      {
        Effect = "Allow"
        Action = [
          "ecr:GetAuthorizationToken",
          "ecr:BatchCheckLayerAvailability",
          "ecr:GetDownloadUrlForLayer",
          "ecr:BatchGetImage",
          "ecr:InitiateLayerUpload",
          "ecr:UploadLayerPart",
          "ecr:CompleteLayerUpload",
          "ecr:PutImage"
        ]
        Resource = "*"
      }
    ]
  })

  tags = {
    Name = "GitHub-Actions-EKS-Policy"
  }
}

# Policy Attachment
resource "aws_iam_role_policy_attachment" "github_actions_eks_policy_attachment" {
  role       = aws_iam_role.github_actions_eks_deploy_role.name
  policy_arn = aws_iam_policy.github_actions_eks_policy.arn
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;terraform.tfvars&lt;br&gt;
&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;github_repositories = [
  {
    org    = "akhileshmishrabiz"
    repo   = "DevOpsDojo"
    branch = "*"  # All branches
  },
  {
    org    = "akhileshmishrabiz"
    repo   = "Devops-zero-to-hero" 
    branch = "main"  # Only main branch
  }
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/akhileshmishrabiz/Devops-zero-to-hero/tree/main/AWS-Projects/oidc" rel="noopener noreferrer"&gt;You can find the Terraform code to set up OIDC for Github Action here.&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Apply the Terraform
&lt;/h3&gt;

&lt;p&gt;This example uses the Terraform version 1.8.1 , make sure you have the same Terraform version installed. &lt;/p&gt;

&lt;p&gt;If you have some other version of Terraform installed then update the providers.tf&lt;/p&gt;

&lt;p&gt;If you want to use multiple versions of Terraform then use tfenv CLI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform init
terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkose69uvbkj4vrgzltao.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkose69uvbkj4vrgzltao.webp" alt="Image description" width="800" height="246"&gt;&lt;/a&gt;&lt;br&gt;
This will allow us to use the role arn:aws:iam::366140438193:role/GitHubActionsEKSDeployRole&lt;br&gt;
to authenticate with the GitHub repo &lt;a href="https://github.com/akhileshmishrabiz/DevOpsDojo" rel="noopener noreferrer"&gt;https://github.com/akhileshmishrabiz/DevOpsDojo&lt;/a&gt; on all branches. &lt;br&gt;
And &lt;a href="https://github.com/akhileshmishrabiz/Devops-zero-to-hero" rel="noopener noreferrer"&gt;https://github.com/akhileshmishrabiz/Devops-zero-to-hero&lt;/a&gt; on the main branch.&lt;/p&gt;
&lt;h3&gt;
  
  
  Let's use this OIDC provider in our GitHub Action workflow
&lt;/h3&gt;

&lt;p&gt;Using the OIDC provider to authenticate GitHub Action workflow with AWS to push the docker images to ECR repositories.&lt;/p&gt;

&lt;p&gt;For this example, I will use my public GitHub Repo &lt;a href="https://github.com/akhileshmishrabiz/DevOpsDojo" rel="noopener noreferrer"&gt;https://github.com/akhileshmishrabiz/DevOpsDojo&lt;/a&gt;&lt;br&gt;
This repo has a 3-tier app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flask backend&lt;/li&gt;
&lt;li&gt;React frontend&lt;/li&gt;
&lt;li&gt;RDS Postgres database
I will use GitHub Action to build the backend and frontend images and push them to AWS ECR repositories. GitHub Action will use OIDC to authenticate with AWS.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Let me create 2 ECR repositories
&lt;/h3&gt;
&lt;h1&gt;
  
  
  Create ECR repositories for frontend and backend
&lt;/h1&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws ecr create-repository --repository-name devopsdozo/frontend
aws ecr create-repository --repository-name devopsdozo/backend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faemqkf64xecyllqd0q0f.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faemqkf64xecyllqd0q0f.webp" alt="Image description" width="800" height="549"&gt;&lt;/a&gt;&lt;br&gt;
I want to use the workflow to build the backend and frontend docker images and push them to ECR repositories.&lt;/p&gt;

&lt;p&gt;If you look at the below GitHub Action workflow, you can see that I have used &lt;strong&gt;GitHubActionsEKSDeployRole&lt;/strong&gt; instead of access and secret keys.&lt;br&gt;
&lt;code&gt;role-to-assume:arn:aws:iam::366140438193:role/GitHubActionsEKSDeployRole&lt;/code&gt;&lt;br&gt;
 instead of AWS access and security keys.&lt;/p&gt;

&lt;p&gt;When GitHub sends an auth request to AWS, it verifies the trust policy attached to the role to check if it is allowed to authenticate the workflow for the specific GitHub repo, and branch. &lt;/p&gt;

&lt;p&gt;If it finds the conditions to be true, it will generate a short-term token that GH workflow uses to authenticate with AWS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Build and Push Docker Images

on:
  push:
    branches: [ main ]
  workflow_dispatch:  # Enable manual triggering

env:
  AWS_REGION: ap-south-1
  ECR_REPOSITORY_FRONTEND: devopsdozo/frontend
  ECR_REPOSITORY_BACKEND: devopsdozo/backend
  IMAGE_TAG: latest

jobs:
  build-and-push:
    name: Build and Push Images
    runs-on: ubuntu-latest

    permissions:
      id-token: write
      contents: read

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v3
        with:
          aws-region: ${{ env.AWS_REGION }}
          role-to-assume: arn:aws:iam::366140438193:role/GitHubActionsEKSDeployRole

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1

      - name: Build, tag, and push frontend image to Amazon ECR
        working-directory: ./frontend
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        run: |
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY_FRONTEND:$IMAGE_TAG \
                     --platform=linux/amd64 .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY_FRONTEND:$IMAGE_TAG
          echo "Frontend image pushed to ECR: $ECR_REGISTRY/$ECR_REPOSITORY_FRONTEND:$IMAGE_TAG"

      - name: Build, tag, and push backend image to Amazon ECR
        working-directory: ./backend
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        run: |
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY_BACKEND:$IMAGE_TAG \
                     --platform=linux/amd64 .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY_BACKEND:$IMAGE_TAG
          echo "Backend image pushed to ECR: $ECR_REGISTRY/$ECR_REPOSITORY_BACKEND:$IMAGE_TAG"

      - name: Summary
        run: |
          echo "### Docker Images Built and Pushed" &amp;gt;&amp;gt; $GITHUB_STEP_SUMMARY
          echo "✅ Frontend: ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY_FRONTEND }}:${{ env.IMAGE_TAG }}" &amp;gt;&amp;gt; $GITHUB_STEP_SUMMARY
          echo "✅ Backend: ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY_BACKEND }}:${{ env.IMAGE_TAG }}" &amp;gt;&amp;gt; $GITHUB_STEP_SUMMARY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fowl46iyt7k8hpuyhaivx.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fowl46iyt7k8hpuyhaivx.webp" alt="Image description" width="800" height="462"&gt;&lt;/a&gt;&lt;br&gt;
Note: &lt;a href="https://github.com/akhileshmishrabiz/DevOpsDojo/blob/main/.github/workflows/build-push-with-oidc.yaml" rel="noopener noreferrer"&gt;You can find a better version of this workflow here.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see from the screenshot above that GH workflows are authenticated with AWS using the OIDC, build the Docker images, and push to ECR.&lt;/p&gt;

&lt;p&gt;This is all for this blog post.&lt;br&gt;
Let's connect&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.linkedin.com/in/akhilesh-mishra-0ab886124/" rel="noopener noreferrer"&gt;Connect with me on LinkedIn&lt;/a&gt;
**
**- &lt;a href="https://x.com/livingdevops" rel="noopener noreferrer"&gt;Connect with me on Twitter&lt;/a&gt; (&lt;a class="mentioned-user" href="https://dev.to/livingdevops"&gt;@livingdevops&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tutorial</category>
      <category>devops</category>
      <category>aws</category>
      <category>security</category>
    </item>
    <item>
      <title>Stop Writing Ugly Docker Build Commands Today - Do This Instead</title>
      <dc:creator>Akhilesh Mishra</dc:creator>
      <pubDate>Tue, 17 Jun 2025 13:54:53 +0000</pubDate>
      <link>https://forem.com/livingdevops/stop-writing-ugly-docker-build-commands-today-do-this-instead-4hfj</link>
      <guid>https://forem.com/livingdevops/stop-writing-ugly-docker-build-commands-today-do-this-instead-4hfj</guid>
      <description>&lt;p&gt;Why Docker Bake is the Smarter Way to Build and Manage Docker Images&lt;/p&gt;

&lt;p&gt;Do you enjoy remembering the docker build commands with all the different flags, and arguments?&lt;/p&gt;

&lt;p&gt;Well, I never did. I had to google the flag and arguments whenever I needed to build images.&lt;/p&gt;

&lt;p&gt;It gets worse when I have to build multiple docker images for mono repositories with different platforms. &lt;/p&gt;

&lt;p&gt;Then, you will be using way more flags. That means way more googling or prompting AI bots.&lt;/p&gt;

&lt;p&gt;What if I tell you a better way that allows you to write docker build as code and build everything with just one short command?&lt;/p&gt;

&lt;p&gt;Well, you are in luck as good people at Docker finally released their long-awaited, build orchestration tool, Docker Bake in General Availability. &lt;/p&gt;

&lt;p&gt;It is available with the Docker Desktop version 4.38.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing Docker bake
&lt;/h2&gt;

&lt;p&gt;Docker Bake allows you to define build stages and deployment environments in a declarative file, making complex builds easier to manage. It also leverages BuildKit's parallelization and optimization features to speed up build times.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let me show you why I love Docker Bake
&lt;/h2&gt;

&lt;p&gt;Let's take the example of a mono repo with a dummy frontend and backend application code with its Dockerfile.&lt;/p&gt;

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

&lt;p&gt;If I had to build the docker images for both of these applications and push these two, I would have to run these commands run 2 commands&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Frontend build
docker build --build-arg NODE_VERSION=20 -t \
366140438193.dkr.ecr.ap-south-1.amazonaws.com/frontend:latest \
-f frontend/frontend.Dockerfile frontend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Backend Build
docker build --build-arg GO_VERSION=1.21 -t \
366140438193.dkr.ecr.ap-south-1.amazonaws.com/backend:latest \
-f backend/backend.Dockerfile backend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if you want to push these images to ECR repos, you will be running&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker push 366140438193.dkr.ecr.ap-south-1.amazonaws.com/frontend:latest
docker push 366140438193.dkr.ecr.ap-south-1.amazonaws.com/backend:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I used multiple flags to build the app, and 4 commands to push the image and you don't have these commands version-controlled as these are just commands.&lt;/p&gt;

&lt;p&gt;Now what if I can tell you that you don't need to remember these flags and store these commands just like Terraform infra templates?&lt;/p&gt;

&lt;p&gt;Yes, you can do it all with just one command - Docker buildx bake&lt;br&gt;
Let me show you how Docker Bake makes all this a piece of cake.&lt;/p&gt;

&lt;p&gt;Create a file docker-bake.hcl into the root path and paste the below into that file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# docker-bake.hcl
group "default" {
  targets = ["frontend", "backend"]
}

target "frontend" {
  context = "./frontend"
  dockerfile = "frontend.Dockerfile"
  args = {
    NODE_VERSION = "20"
  }
  tags = ["366140438193.dkr.ecr.ap-south-1.amazonaws.com/frontend:latest"]
}

target "backend" {
  context = "./backend"
  dockerfile = "backend.Dockerfile"
  args = {
    GO_VERSION = "1.21"
  }
  tags = ["366140438193.dkr.ecr.ap-south-1.amazonaws.com/backend:latest"]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your project will look like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fofk302pqw5cnnuzf21rx.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fofk302pqw5cnnuzf21rx.webp" alt="Image description" width="688" height="364"&gt;&lt;/a&gt;&lt;br&gt;
Now just run one command&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker buildx bake&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;Checking if the new Docker images exist by running the docker images command.&lt;/p&gt;

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

&lt;p&gt;You can see that the docker build bake command built both images using the HCL config file. &lt;/p&gt;

&lt;p&gt;You can push the docker-bake.hcl to your GitHub repo, no one ever needs to use remember docker build commands or its flags.&lt;/p&gt;

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

&lt;p&gt;You can also push the images to remote ECR repositories with --push argument&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker buildx bake --push  &lt;br&gt;
&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Digging deep into Docker bake
&lt;/h2&gt;
&lt;h3&gt;
  
  
  If you have a docker command like below:
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build \
  -f Dockerfile \
  -t myapp:latest \
  --build-arg foo=bar \
  --no-cache \
  --platform linux/amd64,linux/arm64 \
  .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It's equivalent Docker Bake file will be like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# docker-bake.hcl
target "myapp" {
  context = "."
  dockerfile = "Dockerfile"
  tags = ["myapp:latest"]
  args = {
    foo = "bar"
  }
  no-cache = true
  platforms = ["linux/amd64", "linux/arm64"]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A target in a Bake file represents a build invocation. &lt;/p&gt;

&lt;p&gt;It holds all the information you would normally pass on to a docker build command using flags.&lt;/p&gt;

&lt;p&gt;To build a target with Bake, pass the name of the target to the bake command - &lt;br&gt;
&lt;code&gt;docker buildx bake webapp&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
If you don't define any target, the bake command will build the default target.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;target "default" {
  dockerfile = "webapp.Dockerfile"
  tags = ["docker.io/username/webapp:latest"]
  context = "https://github.com/username/webapp"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can run &lt;br&gt;
&lt;code&gt;docker buildx bake &lt;br&gt;
&lt;/code&gt;&lt;br&gt;
and it will build the image&lt;/p&gt;

&lt;p&gt;Let's consider this bake config&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;group "all" {
  targets = ["webapp", "api", "tests"]
}

target "webapp" {
  dockerfile = "webapp.Dockerfile"
  tags = ["docker.io/username/webapp:latest"]
  context = "https://github.com/username/webapp"
}

target "api" {
  dockerfile = "api.Dockerfile"
  tags = ["docker.io/username/api:latest"]
  context = "https://github.com/username/api"
}

target "tests" {
  dockerfile = "tests.Dockerfile"
  contexts = {
    webapp = "target:webapp",
    api = "target:api",
  }
  output = ["type=local,dest=build/tests"]
  context = "."
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To build multiple targets at once, run&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker buildx bake webapp api tests&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
You can group targets using the group block&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker buildx bake all&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you noticed in the example I talked about at the start, I used a group block with a default target and I used docker build bake to build both images.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
group "default" {
  targets = ["frontend", "backend"]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Inheritance in Bake
&lt;/h4&gt;

&lt;p&gt;Inheritance allows you to define common configurations and reuse them across multiple targets. Consider the below config&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;target "common" {
    context = "."
    platforms = ["linux/amd64", "linux/arm64"]
}

target "backend" {
    inherits = ["common"]
    dockerfile = "backend.Dockerfile"
    args = {
        GO_VERSION = "1.21"
    }
}

target "frontend" {
    inherits = ["common"]
    dockerfile = "frontend.Dockerfile"
    args = {
        NODE_VERSION = "20"
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Common target sets context and multi-platform builds.
&lt;/h4&gt;

&lt;p&gt;backend and frontend inherit from common, ensuring they both build for multiple platforms.&lt;/p&gt;

&lt;h4&gt;
  
  
  Overriding Values in Inheritance
&lt;/h4&gt;

&lt;p&gt;When a target inherits another target, it can override any of the inherited attributes. &lt;/p&gt;

&lt;p&gt;For example, the following target overrides the args attribute from the inherited target:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;target "base" {
    context = "."
    dockerfile = "Dockerfile"
    args = {
        APP_ENV = "development"
    }
}

target "production" {
    inherits = ["base"]
    args = {
        APP_ENV = "production"
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Using Variables in Bake - Just like Terraform
&lt;/h4&gt;

&lt;p&gt;You can define and use variables in a Bake file to set attribute values, interpolate them into other values, and perform arithmetic operations.&lt;/p&gt;

&lt;p&gt;Consider the below config&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;group "default" {
  targets = [ "frontend" ]
}

variable "NODE_VERSION" {
    default = "20"
}

variable "tag" {
    default = "latest"
}

target "frontend" {
    context = "."
    dockerfile = "frontend.Dockerfile"
    args = {
        NODE_VERSION = NODE_VERSION
    }
    tags = ["myapp-frontend:${tag}"]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Printing the Bake file with the --print flag shows the interpolated value in the resolved build configuration.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker buildx bake --print&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa6jy1p086t0gh4kaihbz.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa6jy1p086t0gh4kaihbz.webp" alt="Image description" width="800" height="629"&gt;&lt;/a&gt;&lt;br&gt;
You can do a lot of things like Validating multiple conditions, Escaping variable interpolation&lt;/p&gt;
&lt;h4&gt;
  
  
  Airthematic expression and Ternary conditional with Docker bake
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;variable "FOO" {
  default = 3
}

variable "IS_FOO" {
  default = true
}

target "app" {
  args = {
    v1 = FOO &amp;gt; 5 ? "higher" : "lower"
    v2 = IS_FOO ? "yes" : "no"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


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

&lt;p&gt;Using built-in and user-defined functions with Docker bake&lt;/p&gt;

&lt;p&gt;We can use a user-defined function to generate a tag with a timestamp(with a built-in function)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// User-defined function to generate a tag with a timestamp
function "generate_tag" {
    params = [version]
    result = format("%s-%s", version, replace(timestamp(), "[-:TZ]", "")) // Correct concatenation
}

// Define a variable for version
variable "APP_VERSION" {
    default = "1.0.0"
}

// Define a target using the custom function and built-in function
target "myapp" {
    context = "."
    dockerfile = "Dockerfile"
    tags = ["myapp:${generate_tag(APP_VERSION)}"]
    args = {
        BUILD_DATE = timestamp()
    }
}

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

&lt;/div&gt;



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

&lt;h4&gt;
  
  
  Remote Bake file definition
&lt;/h4&gt;

&lt;p&gt;You can build Bake files directly from a remote Git repository or an HTTPS URL&lt;/p&gt;

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

&lt;h4&gt;
  
  
  Bake file reference
&lt;/h4&gt;

&lt;p&gt;You can write Bake files in HCL, YAML (Docker Compose files), or JSON.&lt;/p&gt;

&lt;p&gt;I used docker-bake.hcl as a file name but you can use any filename you want and pass that file as an argument to the bake command.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker buildx bake --file ../docker/bake.hcl&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
By default, Bake uses the following lookup order to find the configuration file.&lt;/p&gt;

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

&lt;p&gt;Docker bake is a powerful tool, there are so many things you can do with it. It offers great flexibility with options like using a matrix, docker-compose like config, overriding configuration, cache, and so many things.&lt;/p&gt;

&lt;p&gt;Go check it out and try to use it in your workflow. I am sure you will love it.&lt;/p&gt;

&lt;p&gt;Thanks for reading, do share your thoughts in the comments. Would love to hear any feedback/suggestions&lt;/p&gt;

</description>
      <category>docker</category>
      <category>programming</category>
    </item>
    <item>
      <title>Building a production Lambda function that monitors IAM access keys and sends automated email alerts using boto3 and AWS SES.</title>
      <dc:creator>Akhilesh Mishra</dc:creator>
      <pubDate>Tue, 17 Jun 2025 13:12:00 +0000</pubDate>
      <link>https://forem.com/aws-builders/building-a-production-lambda-function-that-monitors-iam-access-keys-and-sends-automated-email-32ld</link>
      <guid>https://forem.com/aws-builders/building-a-production-lambda-function-that-monitors-iam-access-keys-and-sends-automated-email-32ld</guid>
      <description>&lt;h2&gt;
  
  
  Scenario
&lt;/h2&gt;

&lt;p&gt;In my project, we have over 10+ users with access keys. Most of the access keys were 300–400 days old.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Having access keys lying around for a long period poses a security risk, which is unacceptable. They’re not just a minor security issue — they’re multimillion-dollar liabilities.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The issue I can see is that no one remembers to rotate the access keys, so I thought of building a Lambda function that could remind the team to rotate the keys after a certain number of days.&lt;/p&gt;

&lt;p&gt;Different organizations enforce varying rotation policies — some require rotation every 30 days, others allow 60 or 90 days before access keys must be refreshed.&lt;/p&gt;

&lt;p&gt;My solution needed to accommodate these different requirements while being easy to deploy and maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to expect in this blog post
&lt;/h2&gt;

&lt;p&gt;In this blog post, I’ll walk you through the complete implementation of an automated access key rotation reminder system. You’ll learn how to build a Lambda function that monitors key ages, sends targeted notifications, and helps your team maintain robust security hygiene without the overhead of manual tracking.&lt;/p&gt;

&lt;p&gt;✅ &lt;a href="https://github.com/akhileshmishrabiz/Devops-zero-to-hero/tree/main/AWS-Projects/lambda-iam-key-rotation" rel="noopener noreferrer"&gt;&lt;strong&gt;You can find the entire code for this blog post on my public GitHub repo&lt;/strong&gt;.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing the Python code
&lt;/h2&gt;

&lt;p&gt;I used AWS Python SDK, boto3, for the automation. Here is the approach I took.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;List the users and access keys&lt;/li&gt;
&lt;li&gt;Calculate the age&lt;/li&gt;
&lt;li&gt;Compare it with age standards, and determine if this key should be rotated.&lt;/li&gt;
&lt;li&gt;If the key should be rotated, build the email body that includes the link to the user&lt;/li&gt;
&lt;li&gt;Send the email using AWS SES(Simple Email Service)&lt;/li&gt;
&lt;li&gt;Building the code&lt;/li&gt;
&lt;li&gt;Before we deploy a fully functioning Lambda, I will build and test the code locally.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Note: Also, configure the AWS credentials -&amp;gt; aws configure locally&lt;/p&gt;

&lt;p&gt;I will use boto3, datetime, and email Python modules. datetime and email are Python built-in modules, but we need to install boto3, which is the AWS-managed library.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install the boto3
I will use a Python virtual environment and install boto3 with that
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python3 -m venv .venv

# activate the virtual environemnts on my mac. There is a different commnad
#  windows. 
source .venv/bin/activate

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Install boto3 with pip
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;pip install boto3&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now that we have the installation done, let's write the code step by step.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. List the users
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import boto3
def get_users():
    iam_client = boto3.client('iam')
    response = iam_client.list_users()
    return [user['UserName'] for user in response['Users']]

print(get_users())

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

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  2. Get the access keys details
&lt;/h3&gt;

&lt;p&gt;I will use the datetime module to calculate the age from the created date parameter of the access key&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def get_access_keys_age(username):
    iam_client = boto3.client('iam')
    response = iam_client.list_access_keys(UserName=username).get('AccessKeyMetadata', [])

    access_keys_info = []
    for item in response:
        if item['Status'] == 'Active':
            access_key_id = item['AccessKeyId']
            create_date = item['CreateDate'].date()
            age = (date.today() - create_date).days
            access_keys_info.append((access_key_id, age))

    return access_keys_info

print(get_access_keys_age('cliuser-akhilesh'))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;As you can see, the user cliuser-akhilesh has 2 access keys, one with 22 days' age and the other with 2 days.&lt;/p&gt;

&lt;p&gt;For this use case, I will set the access key expiry age as 20 days, and I would want to send email from 15 days (I would want to have 5 days as a buffer to ensure people responsible for rotating email get enough time to address this)&lt;/p&gt;

&lt;h3&gt;
  
  
  3.Check if the expired key
&lt;/h3&gt;

&lt;p&gt;I want a function that returns an HTML message if the keys are expired. I would include a dynamic link of the AWS IAM user for which the keys have expired.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;username = "cliuser-akhilesh" 
Expiry_days = 20
reminder_email_age = Expiry_days - 5

def if_key_expired(access_key_id, age, reminder_email_age):
    if age &amp;gt;= reminder_email_age:
        return f'''
    &amp;lt;p&amp;gt;Reminder: Access key &amp;lt;strong&amp;gt;{access_key_id}&amp;lt;/strong&amp;gt; is &amp;lt;strong&amp;gt;{age}&amp;lt;/strong&amp;gt; days old. Please rotate it.&amp;lt;/p&amp;gt;
    &amp;lt;p&amp;gt;For more details, visit the &amp;lt;a href="https://us-east-1.console.aws.amazon.com/iam/home?region=us-east-1#/users/details/{username}?section=security_credentials"&amp;gt; Rotate this key here&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;
    '''
    return None

print(if_key_expired("AKIA4ZPZU3T7QIPRKR5X", 22, reminder_email_age))

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

&lt;/div&gt;



&lt;p&gt;That HTML will look like this&lt;/p&gt;

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

&lt;h3&gt;
  
  
  4.Process users
&lt;/h3&gt;

&lt;p&gt;This will return the email body, only for users with access keys about to expire. We will only build an email for these users/access_keys and send an email&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def process_users():
    email_body_list = []
    users = get_users()
    for user in users:
        access_keys_info = get_access_keys_age(user)
        for keys in access_keys_info:
            access_key_id, age = keys   
            email_body = if_key_expired(access_key_id, age, reminder_email_age)
            if email_body:
                email_body_list.append(email_body)       
    return email_body_list

print(type(process_users()))
print(process_users())
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Build an email with the Python email library
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

def build_email_message(to_email, from_email, subject, body):
    msg = MIMEMultipart()
    msg['From'] = from_email
    msg['To'] = to_email
    msg['Subject'] = subject

    body_part = MIMEText(body, 'html')
    msg.attach(body_part)

    return msg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Send an email using the AWS SES service
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Create ses clinet
ses_client = boto3.client('ses')

def send_email(msg, to_emails):
    response = ses_client.send_raw_email(
        Source=msg["From"],
        Destinations=to_emails,
        RawMessage={"Data": msg.as_string()},
    )
    return response.get('MessageId', None)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7. Now let’s put it all together
&lt;/h3&gt;

&lt;p&gt;This function will find all the users with expiring access keys and send an email. I used my emails for this demo; in real scenarios, you can send an email to the whole team.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def main():
    subject = f"AWS Access Key Rotation Reminder -user {username}"
    to_email = "akhileshmishratoemail@gmail.com"
    from_email = "akhileshmishrafromemail@gmail.com"
    for email_body in process_users():
        email_msg = build_email_message(to_email, from_email, subject, email_body)
        email_sent =send_email(email_msg, [to_email])
        print(f"Email sent with Message ID: {email_sent}")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I will go to Lambda and test it. I should get an email.&lt;br&gt;
And here it is&lt;/p&gt;

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

&lt;p&gt;Now that we have tested the code on the local machine, we are ready to deploy it on Lambda.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Only one part will change on Lambda, the main function will look something like this.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def main(event, context):
    subject = f"AWS Access Key Rotation Reminder -user {username}"
    to_email = "aditiyamishranit@gmail.com"
    from_email = "akhileshmishra121990@gmail.com"
    for email_body in process_users():
        email_msg = build_email_message(to_email, from_email, subject, email_body)
        email_sent =send_email(email_msg, [to_email])
        print(f"Email sent with Message ID: {email_sent}")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main function will be the entry function for lambda.&lt;/p&gt;

&lt;h3&gt;
  
  
  AWS SES
&lt;/h3&gt;

&lt;p&gt;You need to validate the identities in AWS before you can send an email to them. Since you will be using the SES sandbox account, you need to validate to_email and from_email.&lt;/p&gt;

&lt;p&gt;Go to Amazon SES &amp;gt; Configuration: Identities&lt;/p&gt;

&lt;p&gt;Create and validate identities&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Terraform implementation
&lt;/h2&gt;

&lt;p&gt;I will follow the steps below to write a Terraform function to deploy the Lambda function.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Lambda will expect zipped code, so I will be archiving the code in a zip format using the Terraform archive_file data source&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lambda will need access to AWS IAM to get the access key details, so I will be creating an IAM role with an IAM policy with access to list users, get access key data, and send email&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create the Lambda function&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a cron job that will run this Lambda daily&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h4&gt;
  
  
  - Archiving the code
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# zip the code
data "archive_file" "lambda_zip" {
  type        = "zip"
  source_dir  = "${path.module}/lambda/iam-key-rotation"
  output_path = "${path.module}/iam-key-rotation.zip"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;path.module&lt;/code&gt; reference to the path of the Terraform config. We use it to write the relative path for the code files.&lt;/p&gt;

&lt;h4&gt;
  
  
  - IAM role for the Lambda
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# iam role
resource "aws_iam_role" "lambda_role" {
  name = "iam-key-rotation-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "lambda.amazonaws.com"
        }
      }
    ]
  })    

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  - IAM policy for the Lambda
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_iam_policy" "lambda_policy" {
  name        = "iam-key-rotation-policy"
  description = "Policy for Lambda function to rotate IAM keys"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = [
          "iam:ListAccessKeys",
          "iam:ListUsers",
        ]
        Effect   = "Allow"
        Resource = "*"
      },
      {
        Action = [
          "ses:SendEmail",
          "ses:SendRawEmail",
        ]
        Effect   = "Allow"
        Resource = "*"
      }
    ]
  })
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Attach the policy to the role
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#### # attach policy to role
resource "aws_iam_role_policy_attachment" "lambda_policy_attachment" {
  role       = aws_iam_role.lambda_role.name
  policy_arn = aws_iam_policy.lambda_policy.arn
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  - Lambda function
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# lambda 
resource "aws_lambda_function" "my_lambda_function" {
    function_name    = "iam-key-rotation"
    role             = aws_iam_role.lambda_role.arn
    handler          = "main.main"
    runtime          = "python3.13"
    timeout          = 60
    memory_size      = 128

    # Use the Archive data source to zip the code
    filename         = data.archive_file.lambda_zip.output_path
    source_code_hash = data.archive_file.lambda_zip.output_base64sha256
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;source_code_hash will enable Lambda to update the Lambda code whenever a change happens in the Python code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;timeout is set to 60 seconds, if not set, will fall back to the default 3 seconds&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;handler Use the format python_code.python_function. In this use case, main.py is the code that runs when Lambda is invoked, and main() is the entry-level function. Hence, handler = main.main&lt;br&gt;
To enable the cron job trigger, we use an AWS eventbridge rule.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_cloudwatch_event_rule" "cron_lambdas" {
  name                = "cronjob"
  description         = "to triggr lambda daily 7.15 pm ist"
  schedule_expression = "cron(40 13 * * ? *)"
}
resource "aws_cloudwatch_event_target" "cron_lambdas" {
  rule = aws_cloudwatch_event_rule.cron_lambdas.name
  arn  = aws_lambda_function.my_lambda_function.arn
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, this cron will need permissions to invoke the lambda on schedule&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Invoke lambda permission
resource "aws_lambda_permission" "cron_lambdas" {
  statement_id  = "key-rotation-lambda-allow"
  action        = "lambda:InvokeFunction"
  principal     = "events.amazonaws.com"
  function_name = aws_lambda_function.my_lambda_function.arn
  source_arn    = aws_cloudwatch_event_rule.cron_lambdas.arn
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lambda code is set. I will use the Terraform provider AWS and a remote state file&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;providers.tf&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform {
  required_version = "1.8.1"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "&amp;gt;= 5.32.0"
    }
  }
}

provider "aws" {
  region = "ap-south-1"
}

# remote backend
terraform {
  backend "s3" {
    bucket         = "state-bucket-879381234673"
    key            = "lambda-blog/terraform.tfstate"
    region         = "ap-south-1"
    encrypt        = true
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can deploy the lambda function&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform init
terraform plan
terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h2&gt;
  
  
  Better version of Python and Lambda code
&lt;/h2&gt;

&lt;p&gt;To ensure the automation is flexible and maintainable, I’ll implement a configuration-driven approach using environment variables.&lt;/p&gt;

&lt;p&gt;This eliminates hardcoded values and allows dynamic configuration management through Terraform, making the solution easily adaptable across different environments and organizations.&lt;/p&gt;

&lt;p&gt;Here is the final version of the code.&lt;/p&gt;

&lt;p&gt;main.py&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import boto3
from datetime import date
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import os

from_email = os.environ.get('FROM_EMAIL')
to_email = os.environ.get('TO_EMAIL')
Expiry_days = int(os.environ.get('EXPIRY_DAYS', 90))  # Default to 90 days if not set
reminder_email_age = Expiry_days - 5

def get_users():
    iam_client = boto3.client('iam')
    response = iam_client.list_users()
    return [user['UserName'] for user in response['Users']]

def get_access_keys_age(username):
    iam_client = boto3.client('iam')
    response = iam_client.list_access_keys(UserName=username).get('AccessKeyMetadata', [])

    access_keys_info = []
    for item in response:
        if item['Status'] == 'Active':
            access_key_id = item['AccessKeyId']
            create_date = item['CreateDate'].date()
            age = (date.today() - create_date).days
            access_keys_info.append((access_key_id, age))

    return access_keys_info

def if_key_expired(username, access_key_id, age, reminder_email_age):
    if age &amp;gt;= reminder_email_age:
        return f'''
    &amp;lt;p&amp;gt;Reminder: Access key &amp;lt;strong&amp;gt;{access_key_id}&amp;lt;/strong&amp;gt; is &amp;lt;strong&amp;gt;{age}&amp;lt;/strong&amp;gt; days old. Please rotate it.&amp;lt;/p&amp;gt;
    &amp;lt;p&amp;gt;For more details, visit the &amp;lt;a href="https://us-east-1.console.aws.amazon.com/iam/home?region=us-east-1#/users/details/{username}?section=security_credentials"&amp;gt; Rotate this key here&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;
    return None

def process_users():
    email_body_list = []
    users = get_users()
    for user in users:
        access_keys_info = get_access_keys_age(user)
        for keys in access_keys_info:
            access_key_id, age = keys   
            email_body = if_key_expired(user, access_key_id, age, reminder_email_age)
            if email_body:
                email_body_list.append(email_body)       
    return email_body_list

def build_email_message(to_email, from_email, subject, body):
    msg = MIMEMultipart()
    msg['From'] = from_email
    msg['To'] = to_email
    msg['Subject'] = subject

    body_part = MIMEText(body, 'html')
    msg.attach(body_part)
    return msg

def send_email(msg, to_emails):
    ses_client = boto3.client('ses')
    response = ses_client.send_raw_email(
        Source=msg["From"],
        Destinations=to_emails,
        RawMessage={"Data": msg.as_string()},
    )
    return response.get('MessageId', None)

def main(event, context):
    subject = f"AWS Access Key Rotation Reminder"
    for email_body in process_users():
        email_msg = build_email_message(to_email, from_email, subject, email_body)
        email_sent =send_email(email_msg, [to_email])
        print(f"Email sent with Message ID: {email_sent}")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And updated the Terraform resource for Lambda&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# lambda 
resource "aws_lambda_function" "my_lambda_function" {
    function_name    = "iam-key-rotation"
    role             = aws_iam_role.lambda_role.arn
    handler          = "lambda_function.lambda_handler"
    runtime          = "python3.13"
    timeout          = 60
    memory_size      = 128

    # Use the Archive data source to zip the code
    filename         = data.archive_file.lambda_zip.output_path
    source_code_hash = data.archive_file.lambda_zip.output_base64sha256
    environment {
      variables = {
        "to_email" = "akhileshmishratoemail@gmail.com"
        "from_email" = "akhileshmishra1from@gmail.com"
        "Expiry_days" = 20
      }
    }   
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ &lt;a href="https://github.com/akhileshmishrabiz/Devops-zero-to-hero/tree/main/AWS-Projects/lambda-iam-key-rotation" rel="noopener noreferrer"&gt;You can find the entire code for this blog post on my public GitHub repo.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://livingdevops.com/devops/aws-lambda-tutorial-python-terraform/" rel="noopener noreferrer"&gt;This blog is originally published on my personal blog, livingdevops&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's connect
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.linkedin.com/in/akhilesh-mishra-0ab886124/" rel="noopener noreferrer"&gt;- Connect with me on Linkedin&lt;/a&gt;&lt;br&gt;
**&lt;br&gt;
**&lt;a href="https://x.com/livingdevops" rel="noopener noreferrer"&gt;- Connect with me on twitter (@livingdevops)&lt;br&gt;
&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>lambda</category>
      <category>terraform</category>
      <category>python</category>
    </item>
  </channel>
</rss>
