<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>DevOps on The Cloud Optimist</title>
        <link>https://antoinedelia.github.io/cloud-optimist/pr-144/en/categories/devops/</link>
        <description>Recent content in DevOps on The Cloud Optimist</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en-US</language>
        <lastBuildDate>Tue, 17 Feb 2026 07:30:00 +0100</lastBuildDate><atom:link href="https://antoinedelia.github.io/cloud-optimist/pr-144/en/categories/devops/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>The Day I Accidentally Deleted An Api and What It Taught Me About Devops</title>
        <link>https://antoinedelia.github.io/cloud-optimist/pr-144/en/posts/2026/the-day-i-accidentally-deleted-an-api-and-what-it-taught-me-about-devops/</link>
        <pubDate>Tue, 17 Feb 2026 07:30:00 +0100</pubDate>
        
        <guid>https://antoinedelia.github.io/cloud-optimist/pr-144/en/posts/2026/the-day-i-accidentally-deleted-an-api-and-what-it-taught-me-about-devops/</guid>
        <description>&lt;img src="https://antoinedelia.github.io/cloud-optimist/pr-144/fr/posts/2026/the-day-i-accidentally-deleted-an-api-and-what-it-taught-me-about-devops/deleted_api.png" alt="Featured image of post The Day I Accidentally Deleted An Api and What It Taught Me About Devops" /&gt;&lt;p&gt;April 4, 2024, had started off so well. A hot coffee in hand, VS Code humming with activity, and smooth electronic music in my ears—all the ingredients for a perfect day that surely couldn&amp;rsquo;t foreshadow the coming disaster.&lt;/p&gt;
&lt;p&gt;Today, I&amp;rsquo;d like to tell you how a seemingly quiet day became a defining moment in my DevOps career!&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://antoinedelia.github.io/cloud-optimist/pr-144/img/the-day-i-accidentally-deleted-an-api-and-what-it-taught-me-about-devops/calm_before_storm.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Calm before the storm&#34;
	
	
&gt;&lt;/p&gt;
&lt;h1 id=&#34;incident&#34;&gt;&lt;a href=&#34;#incident&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Incident
&lt;/h1&gt;&lt;h2 id=&#34;the-context&#34;&gt;&lt;a href=&#34;#the-context&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;The Context
&lt;/h2&gt;&lt;p&gt;At the time, I was working in a team tasked with providing a global API Gateway on AWS, featuring several endpoints managed by different teams. Specifically, setting up this API Gateway was the initial phase of the project. It included a DNS record pointing to the API, the API Gateway itself, and a Cognito Authorizer configured with several clients.&lt;/p&gt;
&lt;p&gt;Once this API was ready, external teams could deploy their own endpoints onto it. To facilitate this, a CI/CD pipeline was in place, using CloudFormation to attach the endpoints directly to the existing API.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I’m keeping it as simple as possible here, but if you’re interested in the technical nitty-gritty, feel free to reach out!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;On my end, besides being responsible for the global API infrastructure, I occasionally worked on or bootstrapped certain services.&lt;/p&gt;
&lt;h2 id=&#34;the-beginning-of-the-drama&#34;&gt;&lt;a href=&#34;#the-beginning-of-the-drama&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;The Beginning of the Drama
&lt;/h2&gt;&lt;p&gt;If you’ve ever worked with CloudFormation, you might be familiar with the dreaded &lt;code&gt;UPDATE_ROLLBACK_FAILED&lt;/code&gt; status. This happens when you try to update a CloudFormation stack, but it encounters an error. It then tries to roll back, but if that rollback fails too, your stack gets stuck in &lt;code&gt;UPDATE_ROLLBACK_FAILED&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This state is particularly annoying because you cannot trigger any new deployments until you’ve resolved the underlying issue.&lt;/p&gt;
&lt;p&gt;That is exactly what happened on that fateful April 4, 2024. One of our services got stuck in this state, and I started investigating the &amp;ldquo;why&amp;rdquo; and the &amp;ldquo;how.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;After a few minutes, I noticed the problem stemmed from the API resource itself. Without overthinking it, and in an effort to unblock the situation quickly, I headed straight to the AWS Console, specifically to the API Gateway service. I found the resource in question and rushed to delete it.&lt;/p&gt;
&lt;p&gt;At that exact moment, something very strange happened. An unexpected behavior that made my blood run cold. Instead of staying on the same page, AWS redirected me to the main API Gateway dashboard—the page that lists your available APIs. It now displayed a count of &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I realized then that I hadn&amp;rsquo;t deleted a specific resource &lt;em&gt;within&lt;/em&gt; the API, but the entire API Gateway itself.&lt;/p&gt;
&lt;h2 id=&#34;anatomy-of-a-failure&#34;&gt;&lt;a href=&#34;#anatomy-of-a-failure&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Anatomy of a Failure
&lt;/h2&gt;&lt;p&gt;I never thought I’d make such a rookie mistake. And I imagine as you read this, you’re thinking the same thing.&lt;/p&gt;
&lt;p&gt;Because to get it that wrong, &lt;strong&gt;you really have to try!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let me reconstruct the crime scene for you. Here is what I saw when I decided to delete a resource from the API Gateway.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://antoinedelia.github.io/cloud-optimist/pr-144/img/the-day-i-accidentally-deleted-an-api-and-what-it-taught-me-about-devops/api_gateway_view.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;API Gateway resource view&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Any resemblance to an actual situation is entirely coincidental.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In my shoes, which button would you have clicked? Easy! The &lt;em&gt;Delete&lt;/em&gt; button right next to the resource!&lt;/p&gt;
&lt;p&gt;As for me (and I still don&amp;rsquo;t know what possessed me), I chose to click the &lt;em&gt;API actions&lt;/em&gt; button. After all, I did want to perform an &amp;ldquo;action&amp;rdquo;!&lt;/p&gt;
&lt;p&gt;And what happens when you click that button?&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://antoinedelia.github.io/cloud-optimist/pr-144/img/the-day-i-accidentally-deleted-an-api-and-what-it-taught-me-about-devops/api_actions.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;API Actions dropdown menu&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;Yikes! Right away, you can see it&amp;rsquo;s not what we want at all. But do you think that stopped me? Not a chance! I came here to delete something, and when I saw the word &lt;em&gt;Delete&lt;/em&gt;, I stopped thinking. I should have kept reading, because then I would have seen the word &lt;em&gt;API&lt;/em&gt; right next to it.&lt;/p&gt;
&lt;p&gt;Fortunately, our friends at AWS thought of everything! When you click &lt;em&gt;Delete API&lt;/em&gt;, they still ask if you are absolutely sure. A nice dialog box pops up, showing the name of the API and asking you to type the word &lt;em&gt;confirm&lt;/em&gt; to validate the operation.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://antoinedelia.github.io/cloud-optimist/pr-144/img/the-day-i-accidentally-deleted-an-api-and-what-it-taught-me-about-devops/delete_api_dialog.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Delete API confirmation dialog&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s all well and good, but the AWS engineers underestimated my impatience. In that moment, the confirmation request wasn&amp;rsquo;t a warning; it was an obstacle to my goal. In a heartbeat, I typed &lt;em&gt;confirm&lt;/em&gt; and hit enter.&lt;/p&gt;
&lt;p&gt;So, here is the last thing I saw before finally realizing the gravity of my mistake:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://antoinedelia.github.io/cloud-optimist/pr-144/img/the-day-i-accidentally-deleted-an-api-and-what-it-taught-me-about-devops/successfully_deleted_api.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;API successfully deleted&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;A vision of pure horror.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I wanted to walk you through this process to make one thing clear: you can put all the safeguards in the world in place, but &lt;strong&gt;you can&amp;rsquo;t protect a system against an impatient individual&lt;/strong&gt;, because they won&amp;rsquo;t stop to read the warnings.&lt;/p&gt;
&lt;p&gt;So, the next time you have to perform an &amp;ldquo;innocuous&amp;rdquo; action, take the time to read and ensure you are actually on the right path.&lt;/p&gt;
&lt;p&gt;That being said, let’s move on to the crucial stage: resolving the incident!&lt;/p&gt;
&lt;h1 id=&#34;resolution&#34;&gt;&lt;a href=&#34;#resolution&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Resolution
&lt;/h1&gt;&lt;h2 id=&#34;the-click&#34;&gt;&lt;a href=&#34;#the-click&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;The Click
&lt;/h2&gt;&lt;p&gt;Back to the moment of impact. After realizing what had happened, I had a sudden jolt—a moment of clarity (without the aid of any white powder, I promise). I knew what I had to do: resolve the incident, but more importantly, document every single action I took.&lt;/p&gt;
&lt;p&gt;A few weeks prior, I had looked into the &lt;a class=&#34;link&#34; href=&#34;https://about.gitlab.com/blog/postmortem-of-database-outage-of-january-31/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;2017 GitLab incident&lt;/a&gt;, which took the service down for hours and resulted in data loss for some users. That’s how I discovered the concept of a &lt;strong&gt;Post-Mortem&lt;/strong&gt; and why they are so vital in these situations. But I’ll save that for the next section.&lt;/p&gt;
&lt;p&gt;In the meantime, if you&amp;rsquo;re interested in these topics, I highly recommend &lt;a class=&#34;link&#34; href=&#34;https://www.youtube.com/@kevinfaang/videos&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Kevin Fang&amp;rsquo;s YouTube channel&lt;/a&gt; where he, in his own words, &amp;ldquo;reads postmortems and makes low-quality videos about them.&amp;rdquo;&lt;/p&gt;
&lt;h2 id=&#34;rollback-impact-and-communication&#34;&gt;&lt;a href=&#34;#rollback-impact-and-communication&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Rollback, Impact, and Communication
&lt;/h2&gt;&lt;p&gt;My first thought was that there might be a way to trigger a rollback directly within AWS to minimize the impact on users. If I had actually read the warning message earlier, I would have known that was impossible (but if I had read it, I wouldn&amp;rsquo;t have been in this mess anyway).&lt;/p&gt;
&lt;p&gt;With no &amp;ldquo;undo&amp;rdquo; button in sight, I accepted that I was facing a major incident with a global impact. I first set out to map out the full extent of the damage. In my case, not only was the API down (thanks, Captain Obvious), but no new deployments could be made until the API was back online.&lt;/p&gt;
&lt;p&gt;Finally, I made sure to notify the internal team. This kept them in the loop and let them know I was actively working on a fix.&lt;/p&gt;
&lt;h2 id=&#34;analysis-and-troubleshooting&#34;&gt;&lt;a href=&#34;#analysis-and-troubleshooting&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Analysis and Troubleshooting
&lt;/h2&gt;&lt;p&gt;It was time to roll up my sleeves and find a way to redeploy the API Gateway.&lt;/p&gt;
&lt;p&gt;First, I went to the CloudFormation service, remembering that this API was originally deployed through it. I tried updating the stack, hoping it would bring my dear API back like magic.&lt;/p&gt;
&lt;p&gt;Obviously, it wasn&amp;rsquo;t going to be that simple. Updating the stack was impossible because the manual deletion had put the stack into a &amp;ldquo;hybrid&amp;rdquo; state that it couldn&amp;rsquo;t reconcile.&lt;/p&gt;
&lt;p&gt;Since updating was out of the question, the logical next step was to delete the stack and redeploy it from scratch. That’s when the real trouble started. This CloudFormation stack produced several Outputs. &lt;strong&gt;Two of these were required by &amp;ldquo;child&amp;rdquo; stacks&lt;/strong&gt; that had deployed their endpoints on the API. Consequently, CloudFormation blocked me from deleting the stack because it would break all the others.&lt;/p&gt;
&lt;p&gt;After brainstorming alternatives, this heavy interdependence led me to a tough decision: I had to delete all the &amp;ldquo;child&amp;rdquo; stacks in CloudFormation—a total of 81 stacks.&lt;/p&gt;
&lt;p&gt;To make matters worse, these child stacks didn&amp;rsquo;t have identifiable tags that would have allowed us to automate the cleanup. Fortunately, most of them used a recognizable name prefix, which allowed me to clear out the bulk of them manually.&lt;/p&gt;
&lt;p&gt;Did I mention &lt;strong&gt;interdependencies&lt;/strong&gt;? Because we&amp;rsquo;re not done! Some stacks had deployed S3 buckets. And guess what? CloudFormation won&amp;rsquo;t delete a stack if the S3 bucket isn&amp;rsquo;t empty. Naturally, 14 stacks got stuck in &lt;code&gt;DELETE_FAILED&lt;/code&gt;. Luckily, the fix is straightforward: back up each bucket, empty it, and retry the stack deletion.&lt;/p&gt;
&lt;h2 id=&#34;deploying-the-api-light-at-the-end-of-the-tunnel&#34;&gt;&lt;a href=&#34;#deploying-the-api-light-at-the-end-of-the-tunnel&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Deploying the API: Light at the End of the Tunnel?
&lt;/h2&gt;&lt;p&gt;Having finally cleared the web of interdependencies, it was time to delete the original API Gateway stack and redeploy it.&lt;/p&gt;
&lt;p&gt;The deletion went smoothly (thank God), but—naturally—the creation did not.&lt;/p&gt;
&lt;p&gt;First, the stack itself. A YML file existed in a GitHub repo, but &lt;strong&gt;it hadn&amp;rsquo;t been updated in ages&lt;/strong&gt;. I knew I was better off using the stack definition stored within CloudFormation (and yes, I had saved a copy—I&amp;rsquo;m not that crazy).&lt;/p&gt;
&lt;p&gt;This stack didn&amp;rsquo;t just deploy the API Gateway; it handled several AWS resources, including Lambdas. These Lambdas were still running on Python 3.7, a version that &lt;a class=&#34;link&#34; href=&#34;https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html#runtimes-deprecated&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;AWS no longer allows for creating new Lambdas&lt;/a&gt;. Fortunately, a quick upgrade to Python 3.12 was enough to satisfy the AWS gods.&lt;/p&gt;
&lt;p&gt;And, to my surprise, the stack finally deployed without a hitch!&lt;/p&gt;
&lt;p&gt;But to keep a long story short, there was still work to do. Several critical resources for the API were missing from the CloudFormation stack. These resources had been created manually in AWS over time, ignoring all Infrastructure as Code best practices (&lt;em&gt;cries in Terraform&lt;/em&gt;). To restore service as quickly as possible (we&amp;rsquo;re DevOps, after all!), I recreated these resources manually once more.&lt;/p&gt;
&lt;p&gt;Finally, several key components had the ARN of the old API hardcoded. This required a bit of &amp;ldquo;digital archaeology&amp;rdquo; to find every spot where a manual update to the new API was needed.&lt;/p&gt;
&lt;p&gt;Ultimately, after several hours of troubleshooting, the API was back up and running, and developers could resume their deployments.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The incident began on April 4, 2024, at 3:24 PM and was resolved on April 5, 2024, at 8:46 AM.&lt;/strong&gt;&lt;/p&gt;
&lt;h1 id=&#34;post-mortem-and-lessons-learned&#34;&gt;&lt;a href=&#34;#post-mortem-and-lessons-learned&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Post-Mortem and Lessons Learned
&lt;/h1&gt;&lt;p&gt;Throughout the incident, I took intensive notes on every action taken. Having heard of the &lt;strong&gt;Post-Mortem&lt;/strong&gt; principle, I knew this incident was the perfect candidate.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re unfamiliar with the concept, a Post-Mortem is a document that retraces the steps of an incident, its impact, and its root cause. But most importantly—and most interestingly—it includes a &lt;strong&gt;Lessons Learned&lt;/strong&gt; section. If you take this part seriously, it will be your best ally in building a more robust architecture.&lt;/p&gt;
&lt;p&gt;In this section, you note three key points: what went well, what went wrong, and where you got lucky. And above all, &lt;strong&gt;be honest!&lt;/strong&gt; Even if some points seem silly or make you look incompetent (and I’m saying this as the guy who manually deleted an API, so take it with a grain of salt), the goal isn&amp;rsquo;t to point fingers (the &amp;ldquo;blameless culture&amp;rdquo;). It’s about understanding the flaws in the system so we can fix them.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;The cost of failure is education.&amp;rdquo; — Devin Carraway (&lt;a class=&#34;link&#34; href=&#34;https://sre.google/sre-book/postmortem-culture/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Source&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This might still feel a bit abstract, so let me share my lessons learned from this incident.&lt;/p&gt;
&lt;h2 id=&#34;what-went-well&#34;&gt;&lt;a href=&#34;#what-went-well&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;What went well
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Two things went well&lt;/strong&gt; during this incident.&lt;/p&gt;
&lt;p&gt;First, the resolution was handled by &lt;strong&gt;a team member who knew the architecture inside out&lt;/strong&gt;. This allowed for a quick understanding of what needed to be restored to get the service back online.&lt;/p&gt;
&lt;p&gt;Second, there was &lt;strong&gt;excellent communication throughout&lt;/strong&gt;. When the problem occurred, there was no attempt to hide it. Frequent updates were shared to report on progress. This is crucial—not only does it provide visibility, but communication often leads to helpful tips (like a colleague pointing you toward documentation you didn&amp;rsquo;t know existed).&lt;/p&gt;
&lt;h2 id=&#34;what-went-wrong&#34;&gt;&lt;a href=&#34;#what-went-wrong&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;What went wrong
&lt;/h2&gt;&lt;p&gt;This is the part that hurts. As I mentioned, you have to swallow your pride and highlight everything that could have been handled better.&lt;/p&gt;
&lt;p&gt;For this incident, &lt;strong&gt;four things went wrong&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;To start, &lt;strong&gt;the API infrastructure wasn&amp;rsquo;t consolidated in a single file or folder&lt;/strong&gt;; it was scattered across multiple GitHub repos. This made it very difficult to get a bird&amp;rsquo;s-eye view of everything required for the API to function.&lt;/p&gt;
&lt;p&gt;Next, there was a major issue with &lt;strong&gt;drift&lt;/strong&gt;. This refers to the differences between your actual infrastructure and how it’s defined in your code. Ideally, no manual changes should ever occur, and everything should go through your IaC files. Had this been the case, a simple redeployment would have restored the service instantly.&lt;/p&gt;
&lt;p&gt;Another issue was &lt;strong&gt;the heavy interdependence between resources&lt;/strong&gt;. Many relied on CloudFormation stack outputs. Removing the parent stack essentially crippled the ability to manage the rest of the infrastructure.&lt;/p&gt;
&lt;p&gt;Finally, identifying resources tied to our infrastructure was difficult. &lt;strong&gt;Our stack deployed resources without any associated tags&lt;/strong&gt;, making it a scavenger hunt to find every piece of the puzzle.&lt;/p&gt;
&lt;h2 id=&#34;where-we-got-lucky&#34;&gt;&lt;a href=&#34;#where-we-got-lucky&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Where we got lucky
&lt;/h2&gt;&lt;p&gt;This might sound positive, but it isn&amp;rsquo;t! This category covers things that went well &lt;em&gt;only&lt;/em&gt; because of luck. Realize that at any moment, these could have been in the &amp;ldquo;What went wrong&amp;rdquo; column. Be glad this time, but don&amp;rsquo;t let your guard down!&lt;/p&gt;
&lt;p&gt;In this case, &lt;strong&gt;we got lucky in three ways&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;First, the incident was &lt;strong&gt;identified immediately&lt;/strong&gt; (that’s the one perk of making a massive blunder yourself). But it could have been much worse! If the API had been deleted via an automated script, we had no monitoring in place to alert us.&lt;/p&gt;
&lt;p&gt;Second, the person who deleted the API had deep knowledge of the project (yes, I&amp;rsquo;m talking about myself—I have to give myself &lt;em&gt;some&lt;/em&gt; credit). This allowed for an immediate transition into resolution mode, but it could have been someone else who was totally lost.&lt;/p&gt;
&lt;p&gt;Finally, &lt;strong&gt;this was our &amp;ldquo;Dev&amp;rdquo; API&lt;/strong&gt;. The Production API was perfectly fine (a detail I intentionally saved for the end—you know, for the storytelling). So while the impact was minimal, the same incident could have happened in Prod with the same recovery nightmares. And that would have been much more expensive.&lt;/p&gt;
&lt;h2 id=&#34;preparing-for-the-future&#34;&gt;&lt;a href=&#34;#preparing-for-the-future&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Preparing for the Future
&lt;/h2&gt;&lt;p&gt;Now that you&amp;rsquo;ve listed the problems encountered, as a good DevOps engineer, you must learn from them. Note everything that can be improved, but above all, &lt;strong&gt;set a plan!&lt;/strong&gt; Otherwise, these are just empty words.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;A goal without a plan is just a wish.&amp;rdquo; — Antoine de Saint-Exupéry&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In my case, the three key takeaways were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Consolidation of Infrastructure as Code:&lt;/strong&gt; Everything must be deployable in the blink of an eye. This is a project I would complete in the following months.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Improved Monitoring and Alerting:&lt;/strong&gt; If this ever happens again, we need to be alerted immediately so we can react fast.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clearer Documentation:&lt;/strong&gt; Any team member should be able to handle such an incident, and that starts with reliable, understandable docs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All these lessons were tracked as GitHub issues, and I made sure to knock them out over the following months.&lt;/p&gt;
&lt;h1 id=&#34;conclusion&#34;&gt;&lt;a href=&#34;#conclusion&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Conclusion
&lt;/h1&gt;&lt;p&gt;As you&amp;rsquo;ve seen, accidents happen. The key is to make sure they benefit you and your organization. Use them as an opportunity to learn and patch vulnerabilities that were previously undetected (some companies even &lt;a class=&#34;link&#34; href=&#34;https://en.wikipedia.org/wiki/Chaos_engineering&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;purposely create chaos&lt;/a&gt; for this very reason).&lt;/p&gt;
&lt;p&gt;Thanks for reading to the end! I’ll leave you here—I’ve got some other infrastructures to delete!&lt;/p&gt;
</description>
        </item>
        <item>
        <title>Why Tagging your AWS Resources is a Must?</title>
        <link>https://antoinedelia.github.io/cloud-optimist/pr-144/en/posts/2025/why-tagging-your-aws-resources-is-a-must/</link>
        <pubDate>Tue, 24 Jun 2025 07:30:00 +0200</pubDate>
        
        <guid>https://antoinedelia.github.io/cloud-optimist/pr-144/en/posts/2025/why-tagging-your-aws-resources-is-a-must/</guid>
        <description>&lt;img src="https://antoinedelia.github.io/cloud-optimist/pr-144/fr/posts/2025/why-tagging-your-aws-resources-is-a-must/aws_tagging.jpeg" alt="Featured image of post Why Tagging your AWS Resources is a Must?" /&gt;&lt;h1 id=&#34;introduction&#34;&gt;&lt;a href=&#34;#introduction&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Introduction
&lt;/h1&gt;&lt;p&gt;The more services we deploy on AWS, the easier it is to get lost. At first, we know all the services we use by heart, the number of Lambdas or EC2 instances running. But little by little, it&amp;rsquo;s easy to feel overwhelmed, especially when different projects pile up. Quickly finding which resources belong to which project, or identifying those that weren&amp;rsquo;t properly cleaned up after a PoC, can quickly become a headache. And that&amp;rsquo;s without even mentioning cost visibility!
Fortunately, there&amp;rsquo;s a simple yet incredibly powerful practice: &lt;strong&gt;tagging&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In this article, I want to show you that a good tagging strategy is &lt;strong&gt;crucial for your organization, the way you handle cost, and to secure&lt;/strong&gt; your AWS account.&lt;/p&gt;
&lt;p&gt;Ready? Tag along with me!&lt;/p&gt;
&lt;h1 id=&#34;why-are-tags-useful&#34;&gt;&lt;a href=&#34;#why-are-tags-useful&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Why are tags useful?
&lt;/h1&gt;&lt;p&gt;Imagine labels on moving boxes. Without them, it&amp;rsquo;s impossible to know what&amp;rsquo;s inside or which room they belong to. Tags on AWS are the same! A tag is a piece of &lt;strong&gt;information&lt;/strong&gt; (in key-value format) that you assign to your resources (EC2 instances, S3 buckets, RDS databases, etc.).
A good tagging strategy allows you to, among other things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Identify orphan resources&lt;/strong&gt;: This is the classic scenario. A resource without a &lt;code&gt;Project&lt;/code&gt; or &lt;code&gt;Owner&lt;/code&gt; tag? There&amp;rsquo;s a good chance it has been forgotten and is consuming resources (and therefore money) for nothing. Listing untagged (or poorly tagged) resources is an essential first step in cleaning up.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Allocate costs&lt;/strong&gt;: By tagging your resources with a project identifier, cost center, or team, you can then use AWS Cost Explorer to filter your expenses and understand precisely which projects consume the most. This is essential for internal chargebacks or simply for optimizing your budget.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automate actions&lt;/strong&gt;: Tags can serve as triggers for automation scripts (for example, backing up all EC2 instances with the tag &lt;code&gt;Backup=Daily&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Manage access and security&lt;/strong&gt;: The &lt;a class=&#34;link&#34; href=&#34;https://aws.amazon.com/iam&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;AWS IAM&lt;/a&gt; service can use tags to grant granular permissions.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In short, tagging is the foundation of &lt;strong&gt;good Cloud governance&lt;/strong&gt;. For more details, I invite you to consult the &lt;a class=&#34;link&#34; href=&#34;https://aws.amazon.com/solutions/guidance/tagging-on-aws/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;tagging guide proposed by AWS&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Fortunately, we can associate multiple tags with the same resource. But this raises the question: how many tags are necessary?&lt;/p&gt;
&lt;p&gt;This will depend on your company and each project, but overall, there are some tags that won&amp;rsquo;t hurt, no matter your situation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Project&lt;/strong&gt;: The name of the project linked to the resource. Usually, the GitHub repository name works well.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Environment&lt;/strong&gt;: The desired environment (dev, uat, prod, &amp;hellip;). Even if you have separate AWS accounts, this will help you identify if a resource got lost during deployment.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Owner&lt;/strong&gt;: The owner of the resource. This could be a person, but more ideally a team (frontend, backend, security, &amp;hellip;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Depending on your use case, you&amp;rsquo;ll surely have other ideas for tags, but with the ones above, you&amp;rsquo;ll already have a good start! And in case of doubt, feel free to read the &lt;a class=&#34;link&#34; href=&#34;https://docs.aws.amazon.com/tag-editor/latest/userguide/best-practices-and-strats.html&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;tagging best practices from AWS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s now look at some concrete examples of using tags in AWS.&lt;/p&gt;
&lt;h2 id=&#34;aws-cost-explorer-tracking-costs-thanks-to-tags&#34;&gt;&lt;a href=&#34;#aws-cost-explorer-tracking-costs-thanks-to-tags&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;AWS Cost Explorer: Tracking costs thanks to tags
&lt;/h2&gt;&lt;p&gt;One of the most concrete benefits of tagging is the &lt;strong&gt;visibility it provides over your expenses&lt;/strong&gt;. &lt;a class=&#34;link&#34; href=&#34;https://aws.amazon.com/aws-cost-management/aws-cost-explorer/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;AWS Cost Explorer&lt;/a&gt; is the tool of choice for this.&lt;/p&gt;
&lt;p&gt;Once your resources are correctly tagged (for example, with the &lt;code&gt;Project&lt;/code&gt; tag), you need to activate these tags for cost allocation in the AWS Billing and Cost Management console (Billing and Cost Management -&amp;gt; Cost Organization -&amp;gt; Cost Allocation Tags). Be aware that there might be a delay before the activated tags appear in Cost Explorer.&lt;/p&gt;
&lt;p&gt;Once activated, you can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Filter by tag&lt;/strong&gt;: In Cost Explorer, you can filter your costs by the value of a specific tag.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Group by tag&lt;/strong&gt;: You can also choose to group your expenses by tag. This will give you an overview of the cost distribution across different projects, environments, etc.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Create budgets based on tags&lt;/strong&gt;: With AWS Budgets, you can set alert thresholds for costs associated with specific tags, helping you avoid nasty surprises.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;https://antoinedelia.github.io/cloud-optimist/pr-144/img/why-tagging-your-aws-resources-is-a-must/cost_explorer_tag_filtering.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Example of filtering by tag&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Example of filtering by tag for my blog: the costs are minimal, am I a FinOps expert?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This ability to dissect your AWS bill by tags transforms cost management from an obscure chore into a transparent and controllable exercise. It&amp;rsquo;s a must for any organization concerned about its Cloud budget. You can now add &lt;em&gt;FinOps&lt;/em&gt; to your LinkedIn bio!&lt;/p&gt;
&lt;h2 id=&#34;aws-resource-explorer-keeping-an-eye-on-deployed-resources&#34;&gt;&lt;a href=&#34;#aws-resource-explorer-keeping-an-eye-on-deployed-resources&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;AWS Resource Explorer: Keeping an eye on deployed resources
&lt;/h2&gt;&lt;p&gt;Now that we are convinced of the usefulness of tags, how do we &lt;strong&gt;list all the resources that use a specific tag?&lt;/strong&gt; We will see this together, with one part in the AWS console, and another part in the terminal (for all the geeks reading this article!).&lt;/p&gt;
&lt;h3 id=&#34;visual-exploration-in-the-aws-console&#34;&gt;&lt;a href=&#34;#visual-exploration-in-the-aws-console&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Visual exploration in the AWS console
&lt;/h3&gt;&lt;p&gt;If you are not yet familiar with &lt;a class=&#34;link&#34; href=&#34;https://aws.amazon.com/resourceexplorer/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;AWS Resource Explorer&lt;/a&gt;, now is the time to discover it! This relatively recent service allows you to search and discover your AWS resources across all regions of your account, using a simple interface, much like a search engine.&lt;/p&gt;
&lt;p&gt;The main advantage of Resource Explorer is its ability to give you a unified view. No more jumping from region to region. You activate indexing, and then you can search for your resources by name, ID, and of course&amp;hellip; by tag!&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s an excellent tool for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Getting a quick overview.&lt;/li&gt;
&lt;li&gt;Visually exploring resources associated with a specific tag.&lt;/li&gt;
&lt;li&gt;Quickly identifying resources without having to code anything.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To use it, activate it in the desired regions (or all of them), let it index your resources, and then use the search bar with a syntax like &lt;code&gt;tag.key:Project tag.value:Cloud*&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://antoinedelia.github.io/cloud-optimist/pr-144/img/why-tagging-your-aws-resources-is-a-must/resource_explorer_search_tags.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Searching resources by tag&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Example of a tag search for my blog: only three resources manage this blog!&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&#34;a-more-technical-approach-with-aws-cli&#34;&gt;&lt;a href=&#34;#a-more-technical-approach-with-aws-cli&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;A more technical approach with AWS CLI
&lt;/h3&gt;&lt;p&gt;For those who, like me, like to have control via the command line, or who need to automate these searches, the &lt;a class=&#34;link&#34; href=&#34;https://aws.amazon.com/cli/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;AWS CLI&lt;/a&gt; remains a key ally. More specifically, it&amp;rsquo;s the &lt;a class=&#34;link&#34; href=&#34;https://docs.aws.amazon.com/cli/latest/reference/resourcegroupstaggingapi/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;resourcegroupstaggingapi&lt;/a&gt; service that will interest us.&lt;/p&gt;
&lt;p&gt;The key command is &lt;a class=&#34;link&#34; href=&#34;https://docs.aws.amazon.com/cli/latest/reference/resourcegroupstaggingapi/get-resources.html&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;get-resources&lt;/a&gt;. Here is a typical example to list the ARNs of all resources having the tag Project with the value &amp;ldquo;Cloud Antoine Delia&amp;rdquo;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;aws resourcegroupstaggingapi get-resources &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    --tag-filters &lt;span class=&#34;s2&#34;&gt;&amp;#34;Key=Project,Values=Cloud Antoine Delia&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; jq &lt;span class=&#34;s2&#34;&gt;&amp;#34;[.ResourceTagMappingList[].ResourceARN]&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Which gives us:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;s2&#34;&gt;&amp;#34;arn:aws:s3:::antoiXXXXXXXXXXXXX&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s break it down a bit:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;aws resourcegroupstaggingapi get-resources&lt;/code&gt;: This is the API call; so far, so good.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--tag-filters &amp;quot;Key=Project,Values=Cloud Antoine Delia&amp;quot;&lt;/code&gt;: This is where we specify our filter. We are looking for the tag Project with the value &amp;ldquo;Cloud Antoine Delia&amp;rdquo;. You can add multiple filters.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;| jq &amp;quot;[.ResourceTagMappingList[].ResourceARN]&amp;quot;&lt;/code&gt;: &lt;a class=&#34;link&#34; href=&#34;https://jqlang.org/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;jq&lt;/a&gt; is a wonderful tool for manipulating JSON on the command line. Here, we use it to cleanly extract the list of ARNs of the found resources (you can do without it, but why make life difficult?).&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Hey, wait a minute! In the console, you show us three resources, and now there&amp;rsquo;s only one! Where&amp;rsquo;s the catch?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Well spotted! You should know that when you make your API call, you are using &lt;strong&gt;a default region&lt;/strong&gt;. However, if you have resources in various regions, you will need to specify it. Thus, if we add &lt;code&gt;--region us-east-1&lt;/code&gt; just before the jq pipe, we indeed get our two missing resources.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;s2&#34;&gt;&amp;#34;arn:aws:acm:us-east-1:6XXXXXXXXXXX0:certificate/f4ca3b13-XXXXXXXXXXXXX&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;s2&#34;&gt;&amp;#34;arn:aws:cloudfront::6XXXXXXXXXXX0:distribution/ERSXXXXXXXXXX&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Besides this detail that you must not forget, this command is extremely powerful because you can integrate it into scripts to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Generate regular reports on resources by project.&lt;/li&gt;
&lt;li&gt;Automatically detect resources that do not comply with your tagging policy.&lt;/li&gt;
&lt;li&gt;Combine with other AWS CLI commands to perform actions on the listed resources.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, you could list all your resources of a certain type (e.g., all your EC2 instances) and check which ones are missing essential tags.&lt;/p&gt;
&lt;p&gt;And if you&amp;rsquo;re wondering if AWS doesn&amp;rsquo;t already offer a service for this&amp;hellip; It does! But we will talk about that in a future article (for the curious, I&amp;rsquo;m talking about &lt;a class=&#34;link&#34; href=&#34;https://aws.amazon.com/config/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;AWS Config&lt;/a&gt;).&lt;/p&gt;
&lt;h2 id=&#34;aws-iam-securing-the-use-of-your-resources&#34;&gt;&lt;a href=&#34;#aws-iam-securing-the-use-of-your-resources&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;AWS IAM: Securing the use of your resources
&lt;/h2&gt;&lt;p&gt;Your resources are deployed in AWS, and now you want to give a team permission to manage all of that.
But here&amp;rsquo;s the thing, in your AWS account, you also have critical resources that must absolutely not be compromised.&lt;/p&gt;
&lt;p&gt;AWS IAM is here for you! Using a simple policy, you can specify that &lt;strong&gt;only resources with a certain tag can be modified&lt;/strong&gt; by a user or group.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s take the following example: you would like to allow a team to start or stop certain EC2 instances, but prevent them from accidentally stopping a critical EC2 instance!
You just need to add the following policy to your users:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;Version&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;2012-10-17&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;Statement&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nt&#34;&gt;&amp;#34;Sid&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;AllowStartStopEC2IfProjectCloud&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nt&#34;&gt;&amp;#34;Effect&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Allow&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nt&#34;&gt;&amp;#34;Action&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s2&#34;&gt;&amp;#34;ec2:StartInstances&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s2&#34;&gt;&amp;#34;ec2:StopInstances&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nt&#34;&gt;&amp;#34;Resource&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;arn:aws:ec2:*:*:instance/*&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nt&#34;&gt;&amp;#34;Condition&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;#34;StringEquals&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          &lt;span class=&#34;nt&#34;&gt;&amp;#34;aws:ResourceTag/Project&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Cloud Antoine Delia&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nt&#34;&gt;&amp;#34;Sid&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;AllowDescribeToSeeInstances&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nt&#34;&gt;&amp;#34;Effect&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Allow&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nt&#34;&gt;&amp;#34;Action&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;ec2:DescribeInstances&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nt&#34;&gt;&amp;#34;Resource&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;*&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Thus, your users will be autonomous in using their resources, without having the possibility to impact other resources.&lt;/p&gt;
&lt;h1 id=&#34;conclusion&#34;&gt;&lt;a href=&#34;#conclusion&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Conclusion
&lt;/h1&gt;&lt;p&gt;You&amp;rsquo;ve understood it: a rigorous tagging strategy &lt;strong&gt;isn&amp;rsquo;t an option; it&amp;rsquo;s a necessity&lt;/strong&gt; for operating serenely on AWS. Whether on the organizational, financial, or security management level, AWS gives you the means to take full advantage of your tags.&lt;/p&gt;
&lt;p&gt;So, a little piece of advice: if you haven&amp;rsquo;t already, &lt;strong&gt;define a clear tagging policy&lt;/strong&gt; in your organization, apply it, and use these tools to regularly check that everything is in order. You&amp;rsquo;ll thank me later!&lt;/p&gt;
</description>
        </item>
        <item>
        <title>How I Automated My Blog To The Cloud</title>
        <link>https://antoinedelia.github.io/cloud-optimist/pr-144/en/posts/2025/how-i-automated-my-blog-to-the-cloud/</link>
        <pubDate>Tue, 04 Mar 2025 07:30:00 +0100</pubDate>
        
        <guid>https://antoinedelia.github.io/cloud-optimist/pr-144/en/posts/2025/how-i-automated-my-blog-to-the-cloud/</guid>
        <description>&lt;img src="https://antoinedelia.github.io/cloud-optimist/pr-144/en/posts/2025/how-i-automated-my-blog-to-the-cloud/automated-blog.jpeg" alt="Featured image of post How I Automated My Blog To The Cloud" /&gt;&lt;h1 id=&#34;how-it-started-and-why-it-ended&#34;&gt;&lt;a href=&#34;#how-it-started-and-why-it-ended&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;How it started and why it ended
&lt;/h1&gt;&lt;p&gt;When I started blogging, I was still in engineering school. And the world of websites and blogs was still new to me. So when I wanted to create a personal blog, I turned to what I thought was the best solution at the time: WordPress.&lt;/p&gt;
&lt;p&gt;Like it or not, &lt;strong&gt;WordPress is quite easy to use&lt;/strong&gt; for beginners, even more for me who was still discovering how to host a website at the time.&lt;/p&gt;
&lt;p&gt;So, with a brand new OVH account, I was able to quickly setup my &lt;a class=&#34;link&#34; href=&#34;https://blog.antoinedelia.fr&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;personal blog&lt;/a&gt; in a few clicks!&lt;/p&gt;
&lt;p&gt;And when the time came where I wanted to create a blog dedicated to professional topics, I naturally came back to my ol&amp;rsquo; friend WordPress.&lt;/p&gt;
&lt;p&gt;But after some months, the glory of WordPress started to fade into a darker view. Sure, it was convenient to add a new post by going through WordPress&amp;rsquo; UI, but maintaining the website was always boresome. Moreover, a WordPress installation requires using a database, which incurred a higher bill at the end of each month.&lt;/p&gt;
&lt;p&gt;All of this for a blog that I knew did not receive many visits each day.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://antoinedelia.github.io/cloud-optimist/pr-144/img/how-i-automated-my-blog-to-the-cloud/struggling_blog.jpeg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;The struggle with WordPress&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;Surely there was a better way to do this.&lt;/p&gt;
&lt;h1 id=&#34;the-start-of-a-new-plan&#34;&gt;&lt;a href=&#34;#the-start-of-a-new-plan&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;The start of a new plan
&lt;/h1&gt;&lt;p&gt;I will tell you here the path that led me to the rebirth of my blog on which you are today. If you are only interested in the technical details, you can &lt;a class=&#34;link&#34; href=&#34;#technically-what-i-have-today&#34; &gt;move on to the next section&lt;/a&gt;. Otherwise, happy reading!&lt;/p&gt;
&lt;h2 id=&#34;discovering-hugo&#34;&gt;&lt;a href=&#34;#discovering-hugo&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Discovering Hugo
&lt;/h2&gt;&lt;p&gt;Before searching for a new tool, I tried to think of what I needed.&lt;/p&gt;
&lt;p&gt;My blog was essentially a simple webpage, a static website with no fancy JavaScript or stuff like that. Plus, there was no backend to handle API calls for user authentication for example, so &lt;strong&gt;I could simply not use any database at all&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;So what I needed was essentially a way to focus on the content of the blog, and make sure I could attach to it a nice theme, and that was it!&lt;/p&gt;
&lt;p&gt;But, as I&amp;rsquo;m an engineer, so in essence, a real bad designer, I wanted to avoid having to deal with CSS as much as possible. Again, I wanted to focus on the content, rather than the looks. So I searched and searched for the perfect tool. And I didn&amp;rsquo;t had to look too long to find it: &lt;a class=&#34;link&#34; href=&#34;https://gohugo.io/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Hugo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://antoinedelia.github.io/cloud-optimist/pr-144/img/how-i-automated-my-blog-to-the-cloud/hugo.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Hugo logo&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;Upon arriving on its website, I was greeted by an intriguing message message: &lt;strong&gt;The world’s fastest framework for building websites&lt;/strong&gt;. Surely, I was curious, and dug into the documentation. And oh boy, was I not disappointed.&lt;/p&gt;
&lt;p&gt;This was exactly what I wanted. A framework where I had to write in markdown format, that would be converted to HTML for me. Plus, it came with &lt;a class=&#34;link&#34; href=&#34;https://themes.gohugo.io/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;a bunch of themes&lt;/a&gt;, so I didn&amp;rsquo;t even had to bother about the design. I could just pick one that I liked, and move on.&lt;/p&gt;
&lt;p&gt;So, without losing any more time, I started to convert my WordPress blog to a Hugo site. I came across a handy script called &lt;a class=&#34;link&#34; href=&#34;https://github.com/SchumacherFM/wordpress-to-hugo-exporter&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;wordpress-to-hugo-exporter&lt;/a&gt; that converted my whole Wordpress database into compatible markdown syntax, and I was ready to go!&lt;/p&gt;
&lt;p&gt;Now I needed to host this website somewhere.&lt;/p&gt;
&lt;h2 id=&#34;a-new-challenger-aws&#34;&gt;&lt;a href=&#34;#a-new-challenger-aws&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;A new challenger: AWS
&lt;/h2&gt;&lt;p&gt;In parallel, I had an opportunity working for a company that focused on the Cloud, more specifically &lt;a class=&#34;link&#34; href=&#34;https://aws.amazon.com/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;AWS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://antoinedelia.github.io/cloud-optimist/pr-144/img/how-i-automated-my-blog-to-the-cloud/aws.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;AWS logo&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;At the time, Cloud was brand new to me. But I was hearing more and more about it, and I wanted to check what was all the fuss around it. Would this be a true life-changer, as people would call it, or would it be yet again another buzz word, and become a dying trend?&lt;/p&gt;
&lt;p&gt;Long story short, &lt;strong&gt;AWS was (and still is) amazing!&lt;/strong&gt; Not only was it an amazing discovery, I could simply not see a world without Cloud anymore.&lt;/p&gt;
&lt;p&gt;The more I learned about all these amazing AWS services, the more I thought: &lt;strong&gt;isn&amp;rsquo;t there a way for me to leverage the Cloud to host my blog?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;With that in mind, I started looking for options that AWS provided. And because of my recent discovery of Hugo, my website did not needed PHP anymore, nor any database to work. It was now a simple, static website, which could perfectly fit in an S3 bucket.&lt;/p&gt;
&lt;p&gt;Not only does AWS has a &lt;a class=&#34;link&#34; href=&#34;https://docs.aws.amazon.com/AmazonS3/latest/userguide/WebsiteHosting.html&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;documentation about hosting a static website on an S3 bucket&lt;/a&gt;, but &lt;a class=&#34;link&#34; href=&#34;https://aws.amazon.com/s3/pricing/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;the pricing for it was just a few cents&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I had found an easy and cost-effective way to host my website.&lt;/p&gt;
&lt;p&gt;The last point that was bothering me, is that to setup all of this, I had to create an S3 bucket, configure it, add a CDN with AWS CloudFront, take into account certificates with AWS ACM, and finally create a DNS record in AWS Route53. So many actions that, if done manually, could make it super difficult to reproduce this setup if I had to do it again.&lt;/p&gt;
&lt;p&gt;I needed a robust way to configure this infrastructure.&lt;/p&gt;
&lt;h2 id=&#34;terraform-to-the-rescue&#34;&gt;&lt;a href=&#34;#terraform-to-the-rescue&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Terraform to the rescue
&lt;/h2&gt;&lt;p&gt;When you start learning Cloud, you will usually hear about &lt;a class=&#34;link&#34; href=&#34;https://en.wikipedia.org/wiki/Infrastructure_as_code&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Infrastructure as Code&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The idea is simple: describe your Cloud infrastructure with&amp;hellip; code! (yes, as the name stands by).&lt;/p&gt;
&lt;p&gt;The benefits of using an IaC tool, is that you will be able to keep track of the changes done on your infrastructure (like you would do with code in GitHub).&lt;/p&gt;
&lt;p&gt;Now, let&amp;rsquo;s say that someone wants to update the configuration of an AWS service. This person goes to the AWS console and start making their changes. But after some trial and error, they decide to give up for now, and to go back to the initial state. And here is the problem: they don&amp;rsquo;t know exactly how it was before! And unless they took note of the initial state before making their changes, they will, again, have to do a bunch of trial and errors before going back to the beginning.&lt;/p&gt;
&lt;p&gt;All of that could have been prevented if the infrastructure was configured through an IaC tool to begin with. In such a case, a simple automated deploy could have done the trick!&lt;/p&gt;
&lt;p&gt;There are a bunch of IaC tools out there. But how can I not talk about the most popular one, the one that made IaC a standard in the Cloud ecosystem: &lt;a class=&#34;link&#34; href=&#34;https://www.terraform.io/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Terraform&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://antoinedelia.github.io/cloud-optimist/pr-144/img/how-i-automated-my-blog-to-the-cloud/terraform.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Terraform logo&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;Terraform, as you could have guessed, is an Infrastructure as Code tool with a declarative approach, meaning that you have to declare the state of the infrastructure you wish to deploy. To do that, you need ton use a specific language: hcl (which stands for HashiCorp Configuration Language). Here is an example to create an S3 bucket with Terraform.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-terraform&#34; data-lang=&#34;terraform&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;resource&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;aws_s3_bucket&amp;#34;&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;example&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;bucket&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;my-tf-test-bucket&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;tags&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;Name&lt;/span&gt;        &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;My bucket&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;Environment&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Dev&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s go back to my blog! Terraform seems to be the perfect tool to configure the infrastructure that will host my blog. All I had to do was to go through an architecture step to know which AWS services I needed, and how to interconnect them, before translating that to Terraform.&lt;/p&gt;
&lt;p&gt;And here is the architecture diagram I came up with.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://antoinedelia.github.io/cloud-optimist/pr-144/img/how-i-automated-my-blog-to-the-cloud/blog_architecture_diagram.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Architecture diagram of my blog hosted on AWS, deployed with Terraform&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;All of this was perfect, but I realized one thing. Every time I needed to add a post, or update the infrastructure, I had to clone my repository, make the appropriate changes, and do a lot of manual actions to update my blog.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s say that I was not too pleased with that, and I intended to fix that!&lt;/p&gt;
&lt;h2 id=&#34;forget-copy-and-paste-with-github-actions&#34;&gt;&lt;a href=&#34;#forget-copy-and-paste-with-github-actions&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Forget copy and paste with GitHub Actions
&lt;/h2&gt;&lt;p&gt;Like I said before, my website was ready. The last step I needed was about &lt;strong&gt;the deployment&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For now, when I had to create a new post, I had to manually build my Hugo website, and move the generated files to my S3 bucket. What a waste of time and energy!&lt;/p&gt;
&lt;p&gt;So I explored the topic of CI/CD pipelines to make my life easy (if you&amp;rsquo;re interested in this topic, I suggest reading &lt;a class=&#34;link&#34; href=&#34;https://antoinedelia.github.io/cloud-optimist/pr-144/en/posts/2025/how-we-built-a-cicd-strategy-that-onboards-100-python-projects-in-under-a-minute/&#34; &gt;my post about setting up a CI/CD strategy&lt;/a&gt;). While Jenkins tried to get my attention, I decided to go with the obvious choice: &lt;a class=&#34;link&#34; href=&#34;https://github.com/features/actions&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;GitHub Actions&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://antoinedelia.github.io/cloud-optimist/pr-144/img/how-i-automated-my-blog-to-the-cloud/github_actions.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;GitHub Actions logo&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;GitHub Actions are simple CI/CD pipelines that you can define directly in your GitHub repository. The main benefit, is that you do not need any additional account, nor to install anything: all is included! In addition, GitHub Actions use an easy-to-read YAML syntax (in opposition to Jenkins and its horrifying groovy!).&lt;/p&gt;
&lt;p&gt;So I created a pipeline that was able to configure Terraform in case I made any modifications, and could also build and deploy my website automatically in my S3 bucket when I updated or added a post.&lt;/p&gt;
&lt;p&gt;All the steps were looking good, mission accomplished!&lt;/p&gt;
&lt;h1 id=&#34;technically-what-i-have-today&#34;&gt;&lt;a href=&#34;#technically-what-i-have-today&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Technically: What I Have Today
&lt;/h1&gt;&lt;p&gt;My blog is now fully automated and deploys to AWS!&lt;/p&gt;
&lt;p&gt;If you want to dive into it, everything is publicly available on my GitHub repository: &lt;a class=&#34;link&#34; href=&#34;https://github.com/antoinedelia/cloud-optimist&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://github.com/antoinedelia/cloud-optimist&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Now, let me show you more details about it.&lt;/p&gt;
&lt;h2 id=&#34;project-structure&#34;&gt;&lt;a href=&#34;#project-structure&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Project Structure
&lt;/h2&gt;&lt;p&gt;The project structure is as follow:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cloud-optimist/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── .github/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   └── workflows/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│       └── main.yml  &lt;span class=&#34;c1&#34;&gt;# GitHub Actions&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── cloud/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   └── ...  &lt;span class=&#34;c1&#34;&gt;# My Hugo blog&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── terraform/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   └── ...  &lt;span class=&#34;c1&#34;&gt;# The Terraform configuration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;└── README.md
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;As you can see, I split my project into three key directories:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.github&lt;/code&gt; : contains the &lt;code&gt;main.yml&lt;/code&gt; file which defines the GitHub Actions CI/CD steps&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cloud&lt;/code&gt; : contains my Hugo blog&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform&lt;/code&gt; : contains all the Terraform infrastructure&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;the-github-actions&#34;&gt;&lt;a href=&#34;#the-github-actions&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;The GitHub Actions
&lt;/h2&gt;&lt;p&gt;My GitHub Actions is rather simple, and can be divided into multiple steps.&lt;/p&gt;
&lt;h3 id=&#34;the-initialization-step&#34;&gt;&lt;a href=&#34;#the-initialization-step&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;The Initialization Step
&lt;/h3&gt;&lt;p&gt;Let&amp;rsquo;s have a look at the top of the file.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;Build and Deploy&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;on&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;push&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;branches&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;master&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;pull_request&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;jobs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;build-and-deploy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;Build &amp;amp; Deploy&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;runs-on&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;ubuntu-latest&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;environment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;production&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;defaults&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;shell&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;bash&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;working-directory&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;cloud&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Here, I ask GitHub to run the pipeline on the &lt;code&gt;master&lt;/code&gt; branch, or if I have a Pull Request. Let me reassure you, I do not deploy anything to production with a Pull Request. You&amp;rsquo;ll see below that this only allows me to perform some &amp;ldquo;dry-run&amp;rdquo; checks and ensure no issues are found before I can safely deploy to production via the &lt;code&gt;master&lt;/code&gt; branch.&lt;/p&gt;
&lt;p&gt;I also ask GitHub to use &lt;code&gt;bash&lt;/code&gt; and to use the &lt;code&gt;cloud&lt;/code&gt; directory as the default working directory.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s see what&amp;rsquo;s next:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;steps&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;uses&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;actions/checkout@v3&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;uses&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;dorny/paths-filter@v2&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;filter&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;with&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;filters&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;sd&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          web:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            - &amp;#39;cloud/**&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          terraform:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            - &amp;#39;terraform/**&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This step is crucial if you wish to fasten your CI/CD and ultimately save cost.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m using the &lt;a class=&#34;link&#34; href=&#34;https://github.com/dorny/paths-filter&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;dorny/paths-filter&lt;/a&gt; actions which allows me to &lt;strong&gt;detect which files were modified in the last commit&lt;/strong&gt;. In my case, I look for the &lt;code&gt;cloud&lt;/code&gt; and &lt;code&gt;terraform&lt;/code&gt; directories. This way, if no changes were detected on the Terraform side, no need to trigger the Terraform steps in my pipeline. In the same way, if the directory &lt;code&gt;cloud&lt;/code&gt; is untouched, no need to try to build and deploy the contents of my blog. This will save you few cents related to the data transfer pricing to your AWS S3 bucket (no need to thanks me!).&lt;/p&gt;
&lt;p&gt;The next steps speak for themselves.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# Checkout the repository to the GitHub Actions runner&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Checkout&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;uses&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;actions/checkout@v2&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Update Git Submodules&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;working-directory&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;./&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;git submodule update --init --recursive&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;I simply get the content of my repository and update the submodules. This last step was useful back when I was using submodules to handle my Hugo themes. I now use the &lt;a class=&#34;link&#34; href=&#34;https://gohugo.io/hugo-modules/use-modules/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Hugo Modules&lt;/a&gt;, which could let me remove this step altogether.&lt;/p&gt;
&lt;h3 id=&#34;the-terraform-step&#34;&gt;&lt;a href=&#34;#the-terraform-step&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;The Terraform Step
&lt;/h3&gt;&lt;p&gt;Let&amp;rsquo;s continue with Terraform:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;28
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;29
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;30
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# Install the latest version of Terraform CLI and configure the Terraform CLI configuration file with a Terraform Cloud user API token&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Setup Terraform&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;steps.filter.outputs.terraform == &amp;#39;true&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;uses&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;hashicorp/setup-terraform@v1&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;with&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;cli_config_credentials_token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;${{ secrets.TF_API_TOKEN }}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# Initialize a new or existing Terraform working directory by creating initial files, loading any remote state, downloading modules, etc.&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Terraform Init&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;steps.filter.outputs.terraform == &amp;#39;true&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;working-directory&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;./terraform&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;terraform init&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# Checks that all Terraform configuration files adhere to a canonical format&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Terraform Format&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;steps.filter.outputs.terraform == &amp;#39;true&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;working-directory&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;./terraform&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;terraform fmt -check&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# Generates an execution plan for Terraform&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Terraform Plan&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;steps.filter.outputs.terraform == &amp;#39;true&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;working-directory&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;./terraform&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;terraform plan&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# On push to master, build or change infrastructure according to Terraform configuration files&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Terraform Apply&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;working-directory&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;./terraform&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;steps.filter.outputs.terraform == &amp;#39;true&amp;#39; &amp;amp;&amp;amp; github.ref == &amp;#39;refs/heads/master&amp;#39; &amp;amp;&amp;amp; github.event_name == &amp;#39;push&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;terraform apply -auto-approve&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Now that&amp;rsquo;s a long step! Let&amp;rsquo;s break it down.&lt;/p&gt;
&lt;p&gt;The first thing that should&amp;rsquo;ve catch your attention is this line:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;steps.filter.outputs.terraform == &amp;#39;true&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Do you remember? This is what allows us to tell if files were updated in the &lt;code&gt;terraform&lt;/code&gt; directory. Here, I check if I need to run these steps or not.&lt;/p&gt;
&lt;p&gt;Then, I am running some ordinary Terraform commands: &lt;code&gt;terraform init&lt;/code&gt; to initialize my project, &lt;code&gt;terraform fmt -check&lt;/code&gt; to ensure my code is fancy, and finally &lt;code&gt;terraform plan&lt;/code&gt; to look at the changes planned.&lt;/p&gt;
&lt;p&gt;Last but not least, &lt;code&gt;terraform apply -auto-approve&lt;/code&gt;, which will deploy the changes to my AWS infrastructure. But if you look closely, you will notice I added two extra checks:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;l&#34;&gt;github.ref == &amp;#39;refs/heads/master&amp;#39; &amp;amp;&amp;amp; github.event_name == &amp;#39;push&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This ensures that &lt;strong&gt;this step will only be ran if the event is a &lt;code&gt;push&lt;/code&gt; on the &lt;code&gt;master&lt;/code&gt; branch&lt;/strong&gt;. This way, no need to worry about an unwanted deployment when I simply play with my infrastructure on another branch or in a Pull Request. Phew!&lt;/p&gt;
&lt;h3 id=&#34;the-hugo-and-aws-steps&#34;&gt;&lt;a href=&#34;#the-hugo-and-aws-steps&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;The Hugo and AWS Steps
&lt;/h3&gt;&lt;p&gt;Now that my infrastructure is ready, it is time to publish my blog!&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Build&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;uses&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;actions/setup-node@v2&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;steps.filter.outputs.web == &amp;#39;true&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;sudo wget https://github.com/gohugoio/hugo/releases/download/v0.142.0/hugo_extended_0.142.0_linux-amd64.deb -O hugo.deb&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;sudo dpkg --install ./hugo.deb&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;hugo&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Deploy to AWS&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;uses&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;jakejarvis/s3-sync-action@master&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;steps.filter.outputs.web == &amp;#39;true&amp;#39; &amp;amp;&amp;amp; github.ref == &amp;#39;refs/heads/master&amp;#39; &amp;amp;&amp;amp; github.event_name == &amp;#39;push&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;with&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;--&lt;span class=&#34;l&#34;&gt;acl public-read --follow-symlinks --delete&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;env&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;AWS_S3_BUCKET&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;${{ secrets.AWS_S3_BUCKET }}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;${{ secrets.AWS_ACCESS_KEY_ID }}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;${{ secrets.AWS_SECRET_ACCESS_KEY }}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;AWS_REGION&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;eu-west-1&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;SOURCE_DIR&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;cloud/public/&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;As for Terraform, we check if any modifications were done on the blog.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;steps.filter.outputs.web == &amp;#39;true&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Then, I&amp;rsquo;m retrieving an extended version of Hugo straight from the GitHub release (here the v0.142.0). I&amp;rsquo;m installing it and build my blog using the &lt;code&gt;hugo&lt;/code&gt; command.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This step could be simplified using the &lt;a class=&#34;link&#34; href=&#34;https://github.com/peaceiris/actions-hugo&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;peaceiris/actions-hugo&lt;/a&gt; actions&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Finally, I&amp;rsquo;m using the &lt;a class=&#34;link&#34; href=&#34;https://github.com/jakejarvis/s3-sync-action&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;jakejarvis/s3-sync-action&lt;/a&gt; actions (I realized while writing this post that this action has been archived a couple of days before, ouch! &amp;ndash; I&amp;rsquo;ll add an edit later to mention an alternative) to move my blog&amp;rsquo;s files into my S3 bucket.&lt;/p&gt;
&lt;p&gt;Of course, you&amp;rsquo;ll have to store some credentials in your GitHub repository so that your GitHub Actions can be authorized to use AWS, but nothing too crazy.&lt;/p&gt;
&lt;p&gt;We are now done with the GitHub Actions part!&lt;/p&gt;
&lt;h2 id=&#34;the-terraform-part&#34;&gt;&lt;a href=&#34;#the-terraform-part&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;The Terraform part
&lt;/h2&gt;&lt;p&gt;Regarding Terraform, I&amp;rsquo;ll keep it short, as this is not too complex.&lt;/p&gt;
&lt;p&gt;There is one personal choice here, is that &lt;strong&gt;I prefer to split my &lt;code&gt;.tf&lt;/code&gt; files based on the AWS services used&lt;/strong&gt;, rather than putting them all in a single &lt;code&gt;main.tf&lt;/code&gt; file, which could make it hard to read. In the end, I have an &lt;code&gt;s3.tf&lt;/code&gt; file for all resources linked to the S3 service, a &lt;code&gt;cloudfront.tf&lt;/code&gt; file for everything related to CloudFront, and so on.&lt;/p&gt;
&lt;p&gt;One important information, is that &lt;strong&gt;it is necessary to define at least the &lt;code&gt;us-east-1&lt;/code&gt; region&lt;/strong&gt;, as you must deploy your ACM certificates there. For the rest, I&amp;rsquo;m deploying all my resources in the &lt;code&gt;eu-west-1&lt;/code&gt; region. To make this split, I&amp;rsquo;m using the &lt;a class=&#34;link&#34; href=&#34;https://developer.hashicorp.com/terraform/language/providers/configuration#alias-multiple-provider-configurations&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Terraform alias&lt;/a&gt;. Here&amp;rsquo;s an example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-terraform&#34; data-lang=&#34;terraform&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# The default configuration: all resources starting with `aws_` will use this provider
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;provider&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;aws&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;region&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;eu-west-1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;provider&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;aws&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;region&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;us-east-1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;alias&lt;/span&gt;  &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;us_east_1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;resource&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;aws_s3_bucket&amp;#34;&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;site&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;bucket&lt;/span&gt;        &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;bucket_name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;force_destroy&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;resource&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;aws_acm_certificate&amp;#34;&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;cert&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;provider&lt;/span&gt;                  &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;aws&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;us_east_1&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;  # Will be deployed in us-east-1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;domain_name&lt;/span&gt;               &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;domain_name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;subject_alternative_names&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;*.&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;domain_name&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;validation_method&lt;/span&gt;         &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;DNS&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id=&#34;the-hugo-part&#34;&gt;&lt;a href=&#34;#the-hugo-part&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;The Hugo part
&lt;/h2&gt;&lt;p&gt;My Hugo blog is based on the &lt;a class=&#34;link&#34; href=&#34;https://themes.gohugo.io/themes/hugo-theme-stack/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Stack theme&lt;/a&gt;. There is also a &lt;a class=&#34;link&#34; href=&#34;https://github.com/CaiJimmy/hugo-theme-stack-starter&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;GitHub template&lt;/a&gt; available which allows you to quickly setup your own blog based on this theme.&lt;/p&gt;
&lt;p&gt;Not a lot to say about this part, except that I encourage you to have a look at the &lt;a class=&#34;link&#34; href=&#34;https://gohugo.io/hugo-modules/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Hugo Modules&lt;/a&gt;, which will save you from having to deal with submodules (and will make it easier when you need to update your theme).&lt;/p&gt;
&lt;h1 id=&#34;potential-improvements&#34;&gt;&lt;a href=&#34;#potential-improvements&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Potential Improvements
&lt;/h1&gt;&lt;p&gt;While I&amp;rsquo;m extremely satisfied with the end results, I noted a few points that could be improved in the future.&lt;/p&gt;
&lt;p&gt;First off, I saw that Hugo has a &lt;code&gt;deploy&lt;/code&gt; command that could, well, deploy your blog directly to an S3 bucket. Perfect! So I need to learn a bit more about &lt;a class=&#34;link&#34; href=&#34;https://gohugo.io/hosting-and-deployment/hugo-deploy/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Hugo Deploy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Finally, even though using AWS as my hosting platform is not expensive (less than 2$ a month), it is not free. But it would be completely possible to use &lt;a class=&#34;link&#34; href=&#34;https://pages.github.com/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;GitHub Pages&lt;/a&gt; to make this blog accessible, while taking the advantage of GitHub as the hosting platform (on this point, I&amp;rsquo;m a bit reluctant, as then I would not be able to show-off with my beautifully made AWS diagrams&amp;hellip;).&lt;/p&gt;
&lt;h1 id=&#34;conclusion&#34;&gt;&lt;a href=&#34;#conclusion&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Conclusion
&lt;/h1&gt;&lt;p&gt;Here we are, you know everything!&lt;/p&gt;
&lt;p&gt;From my beginnings with WordPress, which, while being useful, showed some weaknesses on the backup, maintainability and deployment part.&lt;/p&gt;
&lt;p&gt;Of my research to find the perfect technical suite: AWS, Terraform, Hugo and GitHub Actions, all of that to enable a speedy deployment at almost no cost!&lt;/p&gt;
&lt;p&gt;When I look back to my previous setup and compare to what I have today, I am so happy to have done the switch!&lt;/p&gt;
&lt;p&gt;Now, it&amp;rsquo;s up to you! You have all the information to create your own blog and deploy all of this with a breeze!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Happy blogging !&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://antoinedelia.github.io/cloud-optimist/pr-144/img/how-i-automated-my-blog-to-the-cloud/happy_blog.jpeg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Successfully migrated my blog to the Cloud&#34;
	
	
&gt;&lt;/p&gt;
</description>
        </item>
        <item>
        <title>How We Built a CI/CD Strategy That Onboards 100&#43; Python Projects in Under a Minute</title>
        <link>https://antoinedelia.github.io/cloud-optimist/pr-144/en/posts/2025/how-we-built-a-cicd-strategy-that-onboards-100-python-projects-in-under-a-minute/</link>
        <pubDate>Sun, 19 Jan 2025 16:45:00 +0200</pubDate>
        
        <guid>https://antoinedelia.github.io/cloud-optimist/pr-144/en/posts/2025/how-we-built-a-cicd-strategy-that-onboards-100-python-projects-in-under-a-minute/</guid>
        <description>&lt;img src="https://antoinedelia.github.io/cloud-optimist/pr-144/en/posts/2025/how-we-built-a-cicd-strategy-that-onboards-100-python-projects-in-under-a-minute/ci-cd-strategy.jpeg" alt="Featured image of post How We Built a CI/CD Strategy That Onboards 100&#43; Python Projects in Under a Minute" /&gt;&lt;p&gt;We all know CI/CD is important. In fact, it seems impossible to imagine a world where we would ship a project without checking the quality of its code, and having a detailed test suite. Moreover, to enable developers to focus on development, all of this should be automated.&lt;/p&gt;
&lt;p&gt;Well, things are not always so easy.&lt;/p&gt;
&lt;p&gt;Today, I’ll share how we transitioned from having no CI/CD strategy to onboarding 100+ Python projects in under a minute.&lt;/p&gt;
&lt;h1 id=&#34;the-real-world&#34;&gt;&lt;a href=&#34;#the-real-world&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;The Real World
&lt;/h1&gt;&lt;p&gt;When I joined my current company, I noticed we were managing a large number of Python projects. But when I tried to check for the CI/CD of these, well it was a bit of a mess. Some projects had CI/CD in place, but not too many. And the ones with one were not using the same rules to verify code compliance. It was clear that putting in place a CI/CD for any given project was treated as best effort. And, to be honest, I cannot blame this on anyone.&lt;/p&gt;
&lt;p&gt;Indeed, while CI/CD is widely recognized as a core component of any project, implementing it in the real world often proves challenging.&lt;/p&gt;
&lt;p&gt;But I knew we could change that somehow. So before jumping into a strategy to put in place, I wanted to observe what had prevented people to implement a CI/CD to begin with.&lt;/p&gt;
&lt;h2 id=&#34;lack-of-permissions&#34;&gt;&lt;a href=&#34;#lack-of-permissions&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Lack of Permissions
&lt;/h2&gt;&lt;p&gt;The first thing that I realized, is that not all developers had the same level of access to our Jenkins instance. So while some were able to create new pipelines for their projects, some couldn&amp;rsquo;t. In large organizations, it is not uncommon to have this kind of scenario.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Lesson learned: a lack of permissions should not be a drawback to use a CI/CD pipeline.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;lack-of-knowledge&#34;&gt;&lt;a href=&#34;#lack-of-knowledge&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Lack of Knowledge
&lt;/h2&gt;&lt;p&gt;When people had the ability to create a pipeline, some did not, as they simply did not have the proper knowledge to do so. Here, I need to mention that our company uses a Jenkins instance, so developers familiar with other CI/CD tools (e.g., GitHub Actions, CircleCI) could not fully transfer these knowledge to Jenkins. Moreover, we did not have a clear documentation on the process to follow to create a new pipeline, so beginners, fearing to break something, would simply do not take the risk to mess with it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Lesson learned: we should ensure people without proper Jenkins knowledge can use a CI/CD pipeline.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;lack-of-time&#34;&gt;&lt;a href=&#34;#lack-of-time&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Lack of Time
&lt;/h2&gt;&lt;p&gt;It takes effort to put in place a CI/CD at the beginning of a project, something that might be overlooked by managers that want to ship a product as fast as possible. Moreover, it is sometimes difficult to quantify the return on investment of putting in place a CI/CD pipeline. And if it is difficult to prove this can bring business value, it will end up in the &amp;ldquo;we&amp;rsquo;ll deal with this later&amp;rdquo; box. And we know all too well that the tasks that end up in this box will never see the light of day again.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Lesson learned: setting up a CI/CD pipeline for a new or existing project should be easy and straightforward.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;lack-of-clear-guidelines&#34;&gt;&lt;a href=&#34;#lack-of-clear-guidelines&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Lack of Clear Guidelines
&lt;/h2&gt;&lt;p&gt;Finally, I had a look at the projects that &lt;em&gt;did&lt;/em&gt; have a CI/CD pipeline. They were working fine, but I could clearly see that they lacked a common vision. Some of them used the same formatter (black), but they not always used the same line-length. Some included a testing stage, some didn&amp;rsquo;t. Not only did this led to projects not having the same code quality and compliance, I also thought that this could potentially led to confusion for a newcomer, not knowing which standard to use.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Lesson learned: people should use the same CI/CD pipeline to ensure everyone follows the same guidelines (and we should ensure these guidelines are documented somewhere).&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 id=&#34;the-global-vision&#34;&gt;&lt;a href=&#34;#the-global-vision&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;The Global Vision
&lt;/h1&gt;&lt;p&gt;After reviewing what could go wrong, it is now important to think of a solution that could address all of these, while following the company&amp;rsquo;s best practices, and using the tools at our disposal.&lt;/p&gt;
&lt;h2 id=&#34;formatting&#34;&gt;&lt;a href=&#34;#formatting&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Formatting
&lt;/h2&gt;&lt;p&gt;The first stage should use a formatter to ensure every line of code in our codebase looks the same. This makes sure we are not ending up with different coding standards across our projects.&lt;/p&gt;
&lt;p&gt;Previously, we used black as our formatter. But after hearing all the good news and testing the new cool kid in the block, we decided to switch to ruff, as it has the same benefits as black, but with a faster execution.&lt;/p&gt;
&lt;h2 id=&#34;linting&#34;&gt;&lt;a href=&#34;#linting&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Linting
&lt;/h2&gt;&lt;p&gt;The next stage should use a linter to find potential issues with our code. This makes sure we avoid complexity in our code, as well as identifying code smells or security issues.&lt;/p&gt;
&lt;p&gt;In the past, I used flake8 a lot. But given that we were already using ruff, and as it can also act as a linter, it was a no-brainer to keep it for this task.&lt;/p&gt;
&lt;p&gt;There are many rules that ruff can apply. We decided to use some of them by default, while letting developers the choice to update the ones their project would follow.&lt;/p&gt;
&lt;h2 id=&#34;unit-tests&#34;&gt;&lt;a href=&#34;#unit-tests&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Unit Tests
&lt;/h2&gt;&lt;p&gt;Tests are a critical part of the development of any project. It ensures we ship quality code to production, while also being able to trust that our code would run just fine.&lt;/p&gt;
&lt;p&gt;We decided to use pytest to run these tests. The default code directory would be called &lt;code&gt;src&lt;/code&gt;, and all tests should be in a &lt;code&gt;tests&lt;/code&gt; folder, with files being prefixed by &lt;code&gt;test_&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;code-coverage&#34;&gt;&lt;a href=&#34;#code-coverage&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Code Coverage
&lt;/h2&gt;&lt;p&gt;Closely related to unit tests, code coverage ensures we are able to know how much of our code has been tested. This could quickly tell us if we sufficiently tested our code, as well as pointing out the remaining lines to cover.&lt;/p&gt;
&lt;p&gt;As we were using pytest, we decided to use pytest-cov to generate a coverage report, as it integrates nicely with pytest.&lt;/p&gt;
&lt;p&gt;We set the minimum coverage threshold at 50%. Anything lower would risk overlooking significant portions of code, while setting it higher might discourage developers from writing the necessary tests.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://antoinedelia.github.io/cloud-optimist/pr-144/img/how-we-built-a-cicd-strategy-that-onboards-100-python-projects-in-under-a-minute/detailed-view-of-the-code-coverage-step.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Detailed view of the code coverage step&#34;
	
	
&gt;&lt;/p&gt;
&lt;h2 id=&#34;organization-folders-for-jenkins&#34;&gt;&lt;a href=&#34;#organization-folders-for-jenkins&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Organization Folders for Jenkins
&lt;/h2&gt;&lt;p&gt;We had our different stages ready. Now all we needed to do, was find a way to globally apply said pipeline to our Python repositories. So I tried looking for a way to easily do that in Jenkins.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s when I stumbled upon &lt;a class=&#34;link&#34; href=&#34;https://www.jenkins.io/doc/book/using/best-practices/#use-organization-folders&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Organization Folders&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Organization Folders are designed for scenarios like ours: automatically scan an organization (as in, a GitHub organization), filter the repositories you want, and apply a Jenkins pipeline to them.&lt;/p&gt;
&lt;p&gt;In our example, we are able to look for all repositories with the &amp;ldquo;python&amp;rdquo; topic, and identify them as Python projects. They will then be automatically built. If a new repository is created with this topic, it will also get picked up by Jenkins.&lt;/p&gt;
&lt;p&gt;So, in less than 5 seconds, your project could be onboarded, without having to create it in Jenkins. All is done automatically so you can focus on your code.&lt;/p&gt;
&lt;h2 id=&#34;examples-and-documentation&#34;&gt;&lt;a href=&#34;#examples-and-documentation&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Examples and Documentation
&lt;/h2&gt;&lt;p&gt;All of this was great, but I was fearing of one last obstacle. What would happen if developers adopted the CI/CD pipeline only to find stages failing, with no clear documentation to resolve issues? They would probably give up or try to fix it later, which would destroy the initial goal.&lt;/p&gt;
&lt;p&gt;So I knew that if I wanted to onboard people in this, we needed to deliver clear documentation with direct examples, so they would be able to understand why these errors might appear, and how to fix them.&lt;/p&gt;
&lt;p&gt;Especially on the unit tests stage, as I know this is always a daunting task to start with. So I prepared a project in advance with some unit tests that I knew they could have a look at to take some inspiration, or that directly covered some tricky parts (mocking boto3 API calls, etc.).&lt;/p&gt;
&lt;p&gt;The last step was to make a presentation on all of the above. This was key to give meaning to people, so they could really understand the point of doing all this, while making sure they had all the keys to be autonomous.&lt;/p&gt;
&lt;h1 id=&#34;what-we-have-today&#34;&gt;&lt;a href=&#34;#what-we-have-today&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;What We Have Today
&lt;/h1&gt;&lt;p&gt;From a developer&amp;rsquo;s view, all he has to do to get his Python project onboarded, is to add the &lt;code&gt;python&lt;/code&gt; topic in his repository, and ensures a &lt;code&gt;pyproject.toml&lt;/code&gt; file is created at the root of the repository.&lt;/p&gt;
&lt;p&gt;These two requirements are here to tell Jenkins which project it should take into account. Moreover, the &lt;code&gt;pyproject.toml&lt;/code&gt; file is mandatory for the ruff stages in the pipeline.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://antoinedelia.github.io/cloud-optimist/pr-144/img/how-we-built-a-cicd-strategy-that-onboards-100-python-projects-in-under-a-minute/two-simple-steps-to-get-onboarded-with-cicd-pipelines.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Two simple steps to get onboarded with CI/CD pipelines&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;With that done, his Python project will now check for formatting issues, linting errors, validation of unit tests, and code coverage.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://antoinedelia.github.io/cloud-optimist/pr-144/img/how-we-built-a-cicd-strategy-that-onboards-100-python-projects-in-under-a-minute/jenkins-python-cicd-pipeline.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Jenkins Python CI/CD pipeline&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;All in all, we are now able to setup CI/CD pipelines in less than a minute, whether you have a new or existing project!&lt;/p&gt;
&lt;h1 id=&#34;some-flaws&#34;&gt;&lt;a href=&#34;#some-flaws&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Some Flaws
&lt;/h1&gt;&lt;p&gt;While this simplifies things a lot, there is still room for improvements.&lt;/p&gt;
&lt;h2 id=&#34;the-pipeline-cannot-be-enforced&#34;&gt;&lt;a href=&#34;#the-pipeline-cannot-be-enforced&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;The pipeline cannot be enforced
&lt;/h2&gt;&lt;p&gt;Currently, this CI/CD pipeline cannot be enforced because developers can simply remove the &lt;code&gt;python&lt;/code&gt; topic to bypass it. And while this is fine at first, as we do not want to block developers in their work, at some point, the goal is still to make sure we are applying the same best practices in all Python projects.&lt;/p&gt;
&lt;p&gt;This might be resolved in the future by the use of &lt;a class=&#34;link&#34; href=&#34;https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/about-rulesets&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;GitHub Rulesets&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;They essentially act quite the same as branch protection rules, expect you define these rules at the organization level.&lt;/p&gt;
&lt;p&gt;This way, we could be able to protect our main branches for all our repositories that matches a specific custom properties, and require them to successfully pass the CI/CD pipeline before they are able to merge.&lt;/p&gt;
&lt;h2 id=&#34;the-pipelines-stages-can-be-ignored&#34;&gt;&lt;a href=&#34;#the-pipelines-stages-can-be-ignored&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;The pipeline&amp;rsquo;s stages can be ignored
&lt;/h2&gt;&lt;p&gt;The &lt;code&gt;pyproject.toml&lt;/code&gt; is used to say to ruff which format it should apply, or which rules to follow for the linting part.&lt;/p&gt;
&lt;p&gt;And because we are currently using the &lt;code&gt;pyproject.toml&lt;/code&gt; inside each repository, a developer could just update the rules on his own, bypassing all the guidelines we were trying to apply in the first place.&lt;/p&gt;
&lt;p&gt;Again, while we are allowing this for now to account for the number of fixes to resolve at first, in the end, we might want to prevent this from happening.&lt;/p&gt;
&lt;p&gt;We could either use a common and fixed &lt;code&gt;pyproject.toml&lt;/code&gt; file, or add it in the GitHub&amp;rsquo;s CODEOWNERS file to ensure it cannot be modified without strict approval.&lt;/p&gt;
&lt;h1 id=&#34;whats-next&#34;&gt;&lt;a href=&#34;#whats-next&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;What&amp;rsquo;s Next?
&lt;/h1&gt;&lt;p&gt;With these in place, we can now think about the evolution of this pipeline.&lt;/p&gt;
&lt;p&gt;For example, our company has a SonarQube instance. We would be interested to add a stage that could scan the repository for code smells.&lt;/p&gt;
&lt;p&gt;We are also exploring the use of mkdocs, so that projects can share a common style for documentation.&lt;/p&gt;
&lt;p&gt;And one of my personal favorites, we might want to explore the use of &lt;code&gt;uv&lt;/code&gt; to install requirements, as it is significantly faster than the old &lt;code&gt;pip&lt;/code&gt; guy!&lt;/p&gt;
&lt;p&gt;You might have also noticed that while I talked about CI/CD throughout this post, at no point do we have a step that, well, deploys anything (which basically leaves us with a CI pipeline). We are already thinking about a way to build and deploy Python packages to our Artifactory repository manager, which would finally make this a CI/CD pipeline!&lt;/p&gt;
&lt;p&gt;Finally, we only covered Python in this post, but the same logic could apply to other types of projects. To give you an example, we are also working on a CI/CD pipeline for Terraform.&lt;/p&gt;
&lt;h1 id=&#34;conclusion&#34;&gt;&lt;a href=&#34;#conclusion&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Conclusion
&lt;/h1&gt;&lt;p&gt;By tackling permissions, knowledge gaps, and inconsistent guidelines, we built a unified CI/CD strategy that now supports over 100 Python projects. It’s proof that with the right approach, automation is achievable for any organization&lt;/p&gt;
&lt;p&gt;It’s been a long but rewarding journey!&lt;/p&gt;
&lt;p&gt;I hope this post proved the value of CI/CD, helped you understand what could prevent it from being applied, and gave you some ideas on how to implement a similar strategy in your organization!&lt;/p&gt;
</description>
        </item>
        
    </channel>
</rss>
