<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wc="https://wilhelm.codes/ns/feed/1.0"><channel><title>Wilhelm Codes · Cost-Optimisation</title><link>https://wilhelm.codes/tags/cost-optimisation/</link><description>Slinger of nibbles, bits and bytes. Over 25 years of professional experience as a software engineer. Love making glowing rectangles go "beep-boop".</description><generator>Hugo</generator><language>en-US</language><managingEditor>0xdeadbeef@devilmayco.de (Wilhelm Murdoch)</managingEditor><webMaster>0xdeadbeef@devilmayco.de (Wilhelm Murdoch)</webMaster><lastBuildDate>Sat, 01 Aug 2026 22:15:00 +1000</lastBuildDate><atom:link href="https://wilhelm.codes/tags/cost-optimisation/index.xml" rel="self" type="application/rss+xml"/><item><title>Opened the bill expecting a fright and found a NAT gateway I have not …</title><link>https://wilhelm.codes/bits/blurb-a-nat-gateway-i-forgot/</link><pubDate>Sat, 01 Aug 2026 22:15:00 +1000</pubDate><author>0xdeadbeef@devilmayco.de (Wilhelm Murdoch)</author><guid>https://wilhelm.codes/bits/blurb-a-nat-gateway-i-forgot/</guid><category>aws</category><category>cost-optimisation</category><wc:kind>bit</wc:kind><wc:bit>blurb</wc:bit><wc:untitled>true</wc:untitled><description>Opened the bill expecting a fright and found a NAT gateway I have not needed since March. It has been quietly charging me rent to forward packets nowhere. Coding is my passion.</description><content:encoded>&lt;p>Opened the bill expecting a fright and found a NAT gateway I have not needed since March. It has been quietly charging me rent to forward packets nowhere. Coding is my passion.&lt;/p>
</content:encoded></item><item><title>Save Money by Keeping Your AWS Account Clean</title><link>https://wilhelm.codes/blog/save-money-by-keeping-your-aws-account-clean/</link><pubDate>Tue, 06 Sep 2022 00:00:00 +0000</pubDate><author>0xdeadbeef@devilmayco.de (Wilhelm Murdoch)</author><guid>https://wilhelm.codes/blog/save-money-by-keeping-your-aws-account-clean/</guid><category>aws</category><category>devops</category><category>security</category><category>cost-optimisation</category><wc:kind>post</wc:kind><description>At a prior role, I managed just under 20 AWS accounts. Their uses varied from production workloads, to dedicated CI/CD environments, sandboxed areas for our engineers to experiment in, log aggregation, the list goes on.</description><content:encoded><![CDATA[<p>At a prior role, I managed just under 20 AWS accounts. Their uses varied from production workloads, to dedicated CI/CD environments, sandboxed areas for our engineers to experiment in, log aggregation, the list goes on.</p>
<p>Because we had so many to manage and not a whole lot of people power ( it was just 2 of us ), it got easy for a lot of hidden costs to pop up here and there if we weren&rsquo;t vigilant enough with housekeeping.</p>
<p>We liked to keep roughly 95% of <em>all</em> static infrastructure nicely tucked away in Terraform, but over the years you get so many small extant changes that the dirt begins to pile up and, along with it, costs. Just a little bit here and there, but the cumulative effect becomes more and more obvious as time moves on.</p>
<p>So, what do you do? Do you manually audit every account? Every <em>region</em> in every account? AWS doesn’t give you an easy way to view <em>all</em> resources across the entirety of your org outside of what you can glean from the billing console. So, we do what comes natural; find a way to make the glowing rectangle do the job for you.</p>
<p>The goal here isn&rsquo;t to find a solution that does <em>everything</em>, but just enough to make our jobs easier.</p>
<h2 id="enter-aws-nuke">
  <a class="heading-link" href="#enter-aws-nuke">Enter AWS Nuke<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>I found <a href="https://github.com/rebuy-de/aws-nuke">this utility</a> sometime last year when asked to find a way to trim some extra fat off our monthly bill. It is <em>excellent</em> and works precisely as advertised. It wound up only saving us a few hundred bucks per month, which is a drop in the bucket considering our total monthly spend, but saving money isn’t the only benefit.</p>
<h3 id="added-security">
  <a class="heading-link" href="#added-security">Added Security<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h3>
<p>Cleaning up unused resources is a low touch way to keeping up your security posture. Fewer forgotten things, means a smaller attack surface.</p>
<p>The longer you leave something unchecked, the harder it becomes to manage. Entropy affects everything and while code can be immutable, the world acting upon it is not.</p>
<p>Think back and ask yourself how many times you’ve forgotten about a service with ageing dependencies, or an EC2 instance you haven’t patched in a while, or some critical, but undocumented, component stored away in some forgotten area of one of your accounts.</p>
<p>If you don’t need it, or don’t plan on actively maintaining it, find a way to get rid of it.</p>
<h3 id="soft-dollar-cost">
  <a class="heading-link" href="#soft-dollar-cost">Soft-Dollar Cost<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h3>
<p>People tend to forget the hidden cost of maintaining lots of things; it requires human attention. Have a lot of random things you need to manage? Well, the more things there are the more time you spend on them. That literally translates to money spent at the end of the billing period and it won&rsquo;t be showing up on the invoice.</p>
<p>Wouldn’t you rather spend your time on more important things at work? Reduce hidden costs by cleaning up after yourself.</p>
<h2 id="ok-lets-nuke-some-stuff">
  <a class="heading-link" href="#ok-lets-nuke-some-stuff">Ok, Let&rsquo;s Nuke Some Stuff<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>First things first, determine the account id you&rsquo;re going to use.</p>
<p>This is a highly-destructive operation, so make sure you&rsquo;re targeting the right account. Get yourself some credentials either through privileged IAM or an SSO user session. Assume from here on out that <code>11111111111</code> is your target account and ensure your user has administrative privileges. You&rsquo;ll need this level of authorization if you&rsquo;re going to be doing this kind of deep cleaning.</p>
<p>Ensure your credentials are associated with the proper account by doing something similar to the following. As a side note, if you&rsquo;re using a credentials sourced by SSO, you&rsquo;re going to need the <code>AWS_SESSION_TOKEN</code>, otherwise it&rsquo;s safe to ignore.</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">AWS_ACCESS_KEY_ID</span><span class="o">=</span><span class="s2">&#34;***&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">AWS_SECRET_ACCESS_KEY</span><span class="o">=</span><span class="s2">&#34;***&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">AWS_SESSION_TOKEN</span><span class="o">=</span><span class="s2">&#34;***&#34;</span>
</span></span><span class="line"><span class="cl">aws sts get-caller-identity
</span></span><span class="line"><span class="cl"><span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;UserId&#34;</span>: <span class="s2">&#34;XXXXXXXXXXXXXXXX:DEADBEEF&#34;</span>,
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Account&#34;</span>: <span class="s2">&#34;11111111111&#34;</span>,
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Arn&#34;</span>: <span class="s2">&#34;arn:aws:sts::11111111111:assumed-role/AWSReservedSSO_AdministratorAccess/DEADBEEF&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span></span></span></code></pre></div>
</div>
<h2 id="configuration">
  <a class="heading-link" href="#configuration">Configuration<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>There may be resources you wish to keep while cleaning your account. AWS Nuke exposes comprehensive filtering capabilities that allow you to ignore, or target, specific sets of resources. In our case, it would be stuff like GuardDuty, AWS Config, CloudTrail and S3 buckets dedicated to Terraform state storage. For resources we don&rsquo;t want to wipe, the following configuration provides a basic example of how you may bootstrap the cleaning process.</p>
<p>Ultimately, this will be highly specific to your unique use case, but it&rsquo;s a great way to demonstrate how it all works. Let&rsquo;s go through all the important sections:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">YAML</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">regions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">global</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">eu-north-1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">ap-south-1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">eu-west-3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">eu-west-2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">eu-west-1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">ap-northeast-3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">ap-northeast-2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">ap-northeast-1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">sa-east-1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">ca-central-1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">ap-southeast-1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">ap-southeast-2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">eu-central-1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">us-east-1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">us-east-2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">us-west-1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">us-west-2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">account-blocklist</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl">- <span class="m">12345678910</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">resource-types</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">excludes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">CloudWatchAlarm</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">Route53ResolverRule</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">Route53HostedZone</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">S3Object</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">S3Bucket</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">GuardDutyDetector</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">SNSSubscription</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">IAMSAMLProvider</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">CloudTrailTrail</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">accounts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">11111111111</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">filters</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">IAMPolicy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">contains</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="l">AWS-Chatbot-NotificationsOnly-Policy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">IAMRolePolicy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">contains</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="l">CloudTrailCloudWatchLogsRole</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">contains</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="l">OrganizationAccountAccessRole</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">IAMRole</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">contains</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="l">AWSReservedSSO</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">contains</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="l">CloudWatchAlarmToSlackRole</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">CloudTrailCloudWatchLogsRole</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">OrganizationAccountAccessRole</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">IAMRolePolicyAttachment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">contains</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="l">AWSReservedSSO</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">contains</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="l">CloudWatchAlarmToSlackRole</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">SNSTopic</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">contains</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="l">CloudWatchAlarms</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">CloudWatchLogsLogGroup</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">CloudTrail/DefaultLogGroup</span></span></span></code></pre></div>
</div>
<h4 id="regions">
  <a class="heading-link" href="#regions"><code>regions</code><span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h4>
<p>Lists the AWS regions to screen for resources. By default, we allow the tool to cycle through all supported regions. Unfortunately, there is no support for &ldquo;all regions&rdquo;, so this is a list that will have to be manually maintained. Also, depending on the amount of resources, or use-case for this tool, you may not want to scan all regions. Trim this list to suit your purposes.</p>
<p><code>global</code> refers to <em>services</em> which are considered global and not tied to any specific region. For example, IAM users, policies and roles.</p>
<h4 id="account-blocklist">
  <a class="heading-link" href="#account-blocklist"><code>account-blocklist</code><span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h4>
<p>A list of AWS accounts to completely avoid. If you&rsquo;re not confident in your account selections, this is a great place to put accounts that run production workloads as an added failsafe. Remember, be explicit in your selections and use this tool <em>only</em> in accounts you wish to clean.</p>
<h4 id="resource-typesexcludes">
  <a class="heading-link" href="#resource-typesexcludes"><code>resource-types.excludes</code><span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h4>
<p>A list of AWS resources we wish to ignore. For us, these are typically associated with the resources we&rsquo;ve primed using Terraform. In this case, we just skip them completely. Trim this list to suit your purposes, or just manually delete these resources in the AWS console if you&rsquo;re unsure.</p>
<h4 id="accounts">
  <a class="heading-link" href="#accounts"><code>accounts</code><span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h4>
<p>Contains the id of the account you wish to clean. Within this block, you will see filters used to skip certain resources. In our case, the filter almost always contains a list of things we wish to keep in all accounts. Resources like CloudWatch alarms, GuardDuty and AWS Config settings, etc&hellip;</p>
<p>You can read more about configuring <code>aws-nuke</code> in the associated repository&rsquo;s <a href="https://github.com/rebuy-de/aws-nuke/blob/main/README.md">README</a>. Feel free to customise this file locally to suit your own needs. There are a number of ways to filter and target resources with a high degree of precision.</p>
<h2 id="using-the-tool">
  <a class="heading-link" href="#using-the-tool">Using the Tool<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>By default, <code>aws-nuke</code> does not perform any destructive operations. You must explicitly add the <code>--no-dry-run</code> flag with a subsequent run after performing the initial scan of the target account.</p>
<p>You can install the tool locally on your machine, but I prefer using Docker to invoke the tool:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker run --rm -it <span class="se">\
</span></span></span><span class="line"><span class="cl">    -v ~/aws-nuke-config.yml:/home/aws-nuke/config.yml <span class="se">\
</span></span></span><span class="line"><span class="cl">    -e AWS_ACCESS_KEY_ID <span class="se">\
</span></span></span><span class="line"><span class="cl">    -e AWS_SECRET_ACCESS_KEY <span class="se">\
</span></span></span><span class="line"><span class="cl">    -e AWS_SESSION_TOKEN <span class="se">\
</span></span></span><span class="line"><span class="cl">    quay.io/rebuy/aws-nuke:main <span class="se">\
</span></span></span><span class="line"><span class="cl">    --config /home/aws-nuke/config.yml</span></span></code></pre></div>
</div>
<p>You will be asked to manually type the alias of the target account for confirmation. For our purposes, the alias for account <code>11111111111</code> is <code>aws-nuke-account</code>:</p>
<div class="code-block">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">aws-nuke version v2.19.0.15.gb46fbe0 - Wed Oct  5 10:01:13 UTC 2022 - b46fbe0e9f63266f56b5afd9635b4e4d5a3108d4
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Do you really want to nuke the account with the ID 11111111111 and the alias &#39;aws-nuke-account&#39;?
</span></span><span class="line"><span class="cl">Do you want to continue? Enter account alias to continue.
</span></span><span class="line"><span class="cl">&gt; aws-nuke-account</span></span></code></pre></div>
</div>
<p>You will then see a list of AWS resources as <code>aws-nuke</code> cycles through each region:</p>
<div class="code-block">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">global - IAMRole - RDSBucketAccessRole - [Name: &#34;RDSBucketAccessRole&#34;, Path: &#34;/service-role/&#34;] - filtered by config
</span></span><span class="line"><span class="cl">global - IAMRole - S3BucketAccessRole - [Name: &#34;S3BucketAccessRole&#34;, Path: &#34;/&#34;] - would remove
</span></span><span class="line"><span class="cl">global - IAMRole - EBSSnapshotReplicationRole - [Name: &#34;EBSSnapshotReplicationRole&#34;, Path: &#34;/&#34;] - would remove
</span></span><span class="line"><span class="cl">global - IAMRole - VPCFlowLogsRole - [Name: &#34;VPCFlowLogsRole&#34;, Path: &#34;/&#34;] - would remove
</span></span><span class="line"><span class="cl">global - IAMRole - OrganizationAccountAccessRole - [Name: &#34;OrganizationAccountAccessRole&#34;, Path: &#34;/&#34;] - filtered by config
</span></span><span class="line"><span class="cl">us-west-2 - EC2RouteTable - rtb-0000000000 - [tag:ManagedBy: &#34;terraform&#34;, tag:Name: &#34;VPCRouteTablePublic&#34;, tag:Purpose: &#34;vpc-route-table&#34;] - would remove
</span></span><span class="line"><span class="cl">us-west-2 - EC2RouteTable - rtb-1111111111 - [tag:ManagedBy: &#34;terraform&#34;, tag:Name: &#34;VPCRouteTablePrivate&#34;, tag:Purpose: &#34;vpc-route-table&#34;] - would remove
</span></span><span class="line"><span class="cl">Scan complete: 161 total, 83 nukeable, 78 filtered.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">The above resources would be deleted with the supplied configuration. Provide --no-dry-run to actually destroy resources.</span></span></code></pre></div>
</div>
<p>It is important during the dry run that you closely-examine each resource that has been flagged for removal. Here are some common flags you should be paying attention to:</p>
<ol>
<li><code>would remove</code>: these resources will be removed by AWS Nuke on a subsequent run with <code>--no-dry-run</code>.</li>
<li><code>filtered by config</code>: these are resources that have been skipped as a result of your filtering configurations.</li>
<li><code>cannot delete *</code>: marks AWS-managed resources that simply cannot be deleted.</li>
</ol>
<p>If you notice something is marked as <code>would remove</code>, but should be kept, add a filter for it to the configuration file. Take an iterative approach until you&rsquo;re satisfied with the scanning results.</p>
<p>Once you have scanned the list and updated any relevant filters, you can execute the nuking process by passing the <code>--no-dry-run</code> flag. You will be presented with the same steps as above as well as a final confirmation message similar to the initial dry run scan.</p>
<h2 id="in-conclusion-">
  <a class="heading-link" href="#in-conclusion-">In Conclusion &hellip;<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>This is a very destructive process that will permanently remove selected resources. Always make sure you have backups of selected data sources and they are properly-filtered within your configuration file.</p>
<p>There may also be times when resources just aren&rsquo;t deleted as expected. That&rsquo;s ok as this process isn&rsquo;t 100% perfect. AWS Nuke is still under heavy development and AWS updates APIs around various services constantly. The point isn&rsquo;t for this tool to do 100% of the heavy lifting, but to get you mostly there.</p>
<p>90% complete still means you&rsquo;ve successfully avoided 90% of the work. 😊</p>
<h2 id="100-completion-speed-run">
  <a class="heading-link" href="#100-completion-speed-run">100% Completion Speed-Run<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>Just stop paying the bill. Seriously. Just stop paying for the account. Cancel the card. Delete those billing alerts and just walk away. If you stop paying, AWS will eventually just tear down your account with everything in it. All of it. The entire account. Credit score be damned!</p>
<p><em>obviously, do not do this&hellip; Or, go nuts! After all, don&rsquo;t you sometimes wish you could burn it all down and just walk away? j/k</em></p>
<p><img
  src="https://cdn.hashnode.com/res/hashnode/image/upload/v1664982128441/xnlblxy1F.gif"
  
  
  
  
  alt="burn-koala.gif"
  
  loading="lazy"
  decoding="async"
/>
</p>
<p>Hope you&rsquo;ve learned something useful!</p>
<p>Behave! (mostly) 🙉🙈🙊</p>]]></content:encoded></item><item><title>Deleting Massive S3 Buckets the Easy Way</title><link>https://wilhelm.codes/blog/deleting-massive-s3-buckets-the-easy-way/</link><pubDate>Sat, 04 Dec 2021 00:00:00 +0000</pubDate><author>0xdeadbeef@devilmayco.de (Wilhelm Murdoch)</author><guid>https://wilhelm.codes/blog/deleting-massive-s3-buckets-the-easy-way/</guid><category>aws</category><category>devops</category><category>cost-optimisation</category><wc:kind>post</wc:kind><description>I&amp;rsquo;m in the middle of decommissioning a service at the moment and I have to do the typical process of performing final snapshots and cleaning up extant resources. It&amp;rsquo;s incredibly tedious, so let&amp;rsquo;s walk through it.</description><content:encoded><![CDATA[<p>I&rsquo;m in the middle of decommissioning a service at the moment and I have to do the typical process of performing final snapshots and cleaning up extant resources. It&rsquo;s incredibly tedious, so let&rsquo;s walk through it.</p>
<p>For this particular service, we have an  <a href="https://en.wikipedia.org/wiki/Extract,_transform,_load">ETL</a>  process that stores raw data points for downstream ingestion by some other&hellip; thing. Anyway, the storage mechanism for this is a cross-region replicated S3 bucket containing just over 10 <em>million</em> objects.</p>
<p><img
  src="https://wilhelm.codes/blog/deleting-massive-s3-buckets-the-easy-way/image-1_hu_523c534cb631cb43.webp"
  srcset="/blog/deleting-massive-s3-buckets-the-easy-way/image-1_hu_523c534cb631cb43.webp 736w, /blog/deleting-massive-s3-buckets-the-easy-way/image-1_hu_7c8db834d83bb9da.webp 1104w"
  sizes="(max-width: 48rem) 100vw, 736px"
  width="736"
  height="105"
  alt=""
  
  loading="lazy"
  decoding="async"
/>
</p>
<p>All associated buckets and objects must be deleted. 😬</p>
<p>Have you ever had to delete a bucket with this many objects? Not exactly straight-forward. Even by AWS standards.</p>
<h2 id="whats-the-problem">
  <a class="heading-link" href="#whats-the-problem">What&rsquo;s the problem?<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>As you may or may not know, you can&rsquo;t delete a bucket that still contains objects. For smaller buckets, this isn&rsquo;t much of an issue as you can saunter on over to the &ldquo;empty bucket&rdquo; screen. This will take you to a confirmation interstitial where you have to sit and wait until the deletion process finishes. Though, you had better not close that tab unless you want to start the process all over.</p>
<p><img
  src="https://wilhelm.codes/blog/deleting-massive-s3-buckets-the-easy-way/image-2_hu_d77a85a743ac841d.webp"
  srcset="/blog/deleting-massive-s3-buckets-the-easy-way/image-2_hu_d77a85a743ac841d.webp 736w"
  sizes="(max-width: 48rem) 100vw, 736px"
  width="736"
  height="164"
  alt=""
  
  loading="lazy"
  decoding="async"
/>
</p>
<p>Not exactly a feasible solution for our use case. No, for larger buckets there really is only one pragmatic solution.</p>
<h2 id="lifecycle-policies">
  <a class="heading-link" href="#lifecycle-policies">Lifecycle Policies!<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>You may have noticed the tone of this article as being somewhat snarky. Well, that&rsquo;s because what follows shouldn&rsquo;t be nearly as convoluted as it is. This process should be a single button with a confirmation step where the work is done asynchronously.</p>
<p>That being said, I know better than to question another team&rsquo;s design decisions. Who knows what weirdness they encountered during implementing this functionality. In all fairness, they do provide the mechanisms necessary drop millions, or even billions, of objects. The main concern is it&rsquo;s just not at all intuitive.</p>
<p>So, let&rsquo;s get to it! 🦾</p>
<h3 id="expire-those-objects">
  <a class="heading-link" href="#expire-those-objects">Expire those objects!<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h3>
<p>Alright, we&rsquo;re going to have to perform the following steps for the first policy:</p>
<ol>
<li>Expire all current objects.</li>
<li>Permanently delete all non-current versions of objects.</li>
<li>Delete expired object delete markers.</li>
</ol>
<p>First and foremost, give this policy and name and confirm your intent:</p>
<p><img
  src="https://wilhelm.codes/blog/deleting-massive-s3-buckets-the-easy-way/image-3_hu_b6980a9240ff48db.webp"
  srcset="/blog/deleting-massive-s3-buckets-the-easy-way/image-3_hu_b6980a9240ff48db.webp 736w"
  sizes="(max-width: 48rem) 100vw, 736px"
  width="736"
  height="381"
  alt=""
  
  loading="lazy"
  decoding="async"
/>
</p>
<p>Select the following actions. We will need to select the last option in the list, but due to conflicting settings you will have to add it in separate, secondary, lifecycle policy:</p>
<p><img
  src="https://wilhelm.codes/blog/deleting-massive-s3-buckets-the-easy-way/image-4_hu_43009cfd5d2b0edc.webp"
  srcset="/blog/deleting-massive-s3-buckets-the-easy-way/image-4_hu_43009cfd5d2b0edc.webp 736w"
  sizes="(max-width: 48rem) 100vw, 736px"
  width="736"
  height="252"
  alt=""
  
  loading="lazy"
  decoding="async"
/>
</p>
<p>Set the following option to <code>1</code> day:</p>
<p><img
  src="https://wilhelm.codes/blog/deleting-massive-s3-buckets-the-easy-way/image-5_hu_e108dbe3089f2a81.webp"
  srcset="/blog/deleting-massive-s3-buckets-the-easy-way/image-5_hu_e108dbe3089f2a81.webp 736w"
  sizes="(max-width: 48rem) 100vw, 736px"
  width="736"
  height="183"
  alt=""
  
  loading="lazy"
  decoding="async"
/>
</p>
<p>Again, set this option to <code>1</code> day as well. Unfortunately, this is the lowest value you can select. You also have the option to specify how many versions of each object to keep. This is only really relevant if you have object versioning activated for this bucket, but you&rsquo;re going do nuke it all, so just go ahead and leave this blank:</p>
<p><img
  src="https://wilhelm.codes/blog/deleting-massive-s3-buckets-the-easy-way/image-6_hu_65d62bb36ebf1a0c.webp"
  srcset="/blog/deleting-massive-s3-buckets-the-easy-way/image-6_hu_65d62bb36ebf1a0c.webp 736w"
  sizes="(max-width: 48rem) 100vw, 736px"
  width="736"
  height="205"
  alt=""
  
  loading="lazy"
  decoding="async"
/>
</p>
<p>Look at you! All done except looking over your work and saving your new lifecycle policy:</p>
<p><img
  src="https://wilhelm.codes/blog/deleting-massive-s3-buckets-the-easy-way/image-7_hu_55f5572133868bc9.webp"
  srcset="/blog/deleting-massive-s3-buckets-the-easy-way/image-7_hu_55f5572133868bc9.webp 736w"
  sizes="(max-width: 48rem) 100vw, 736px"
  width="736"
  height="385"
  alt=""
  
  loading="lazy"
  decoding="async"
/>
</p>
<h3 id="delete-those-objects">
  <a class="heading-link" href="#delete-those-objects">Delete those objects!<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h3>
<p>A secondary policy is necessary to <em>delete</em> the objects you&rsquo;ve just told AWS to expire. Begin the process by clicking the <code>Create lifecycle rule</code> button and performing the first step from the previous policy:</p>
<p><img
  src="https://wilhelm.codes/blog/deleting-massive-s3-buckets-the-easy-way/image-8_hu_4f4ef432e91449b1.webp"
  srcset="/blog/deleting-massive-s3-buckets-the-easy-way/image-8_hu_4f4ef432e91449b1.webp 736w"
  sizes="(max-width: 48rem) 100vw, 736px"
  width="736"
  height="379"
  alt=""
  
  loading="lazy"
  decoding="async"
/>
</p>
<p>Next, you&rsquo;ll select the last action in the list:</p>
<p><img
  src="https://wilhelm.codes/blog/deleting-massive-s3-buckets-the-easy-way/image-9_hu_f7f0ef62a2c4791d.webp"
  srcset="/blog/deleting-massive-s3-buckets-the-easy-way/image-9_hu_f7f0ef62a2c4791d.webp 736w"
  sizes="(max-width: 48rem) 100vw, 736px"
  width="736"
  height="252"
  alt=""
  
  loading="lazy"
  decoding="async"
/>
</p>
<p>Select the options to delete all expired objects. Optionally, you can also delete impartial uploads older than <code>1</code> day as well.</p>
<p><img
  src="https://wilhelm.codes/blog/deleting-massive-s3-buckets-the-easy-way/image-10_hu_a730d0abc759281f.webp"
  srcset="/blog/deleting-massive-s3-buckets-the-easy-way/image-10_hu_a730d0abc759281f.webp 736w"
  sizes="(max-width: 48rem) 100vw, 736px"
  width="736"
  height="332"
  alt=""
  
  loading="lazy"
  decoding="async"
/>
</p>
<p>And that&rsquo;s it! Click <code>Save</code> and view the summary screen. It should look similar to this:</p>
<p><img
  src="https://wilhelm.codes/blog/deleting-massive-s3-buckets-the-easy-way/image-11_hu_9f50e9fa7519c485.webp"
  srcset="/blog/deleting-massive-s3-buckets-the-easy-way/image-11_hu_9f50e9fa7519c485.webp 736w, /blog/deleting-massive-s3-buckets-the-easy-way/image-11_hu_3af2d55c3c466359.webp 1104w, /blog/deleting-massive-s3-buckets-the-easy-way/image-11_hu_968d3c77a5146951.webp 1472w"
  sizes="(max-width: 48rem) 100vw, 736px"
  width="736"
  height="115"
  alt=""
  
  loading="lazy"
  decoding="async"
/>
</p>
<h2 id="and-now-we-wait">
  <a class="heading-link" href="#and-now-we-wait">And now&hellip; we wait.<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>It will probably take an additional day for all pre-existing objects to be marked as &ldquo;expired&rdquo;. Next, AWS will trigger the lifecycle policies at 12am UTC. Depending on the size of your bucket, it could possibly take several days to empty. Just go on an extended ☕️ break.</p>
<p><em>Something to keep in mind is that you will not be charged for storage that has been marked as expired while AWS empties the associated bucket.</em></p>
<h2 id="and-thats-that">
  <a class="heading-link" href="#and-thats-that">And that&rsquo;s that!<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>Look, this works. But, it isn&rsquo;t exactly intuitive. In my opinion, you shouldn&rsquo;t have to google &ldquo;how to empty large S3 bucket&rdquo; or even go out of your way to create these policies. Ideally, this would all be neatly abstracted away from you and handled with a single button click and a confirmation interstitial ( one that you can walk away from ).</p>
<p>Anyway, it is what it is. I&rsquo;ll keep checking back over the next few days to chase up its progress.</p>
<p>Hope this helped! Happy hacking! 🤘</p>
<h2 id="several-days-later">
  <a class="heading-link" href="#several-days-later">Several days later&hellip;<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>Ok, it&rsquo;s been a few days and we&rsquo;re finally seeing progress on one of the buckets in question:</p>
<p><img
  src="https://wilhelm.codes/blog/deleting-massive-s3-buckets-the-easy-way/image-12_hu_f859c91a5bb22a7d.webp"
  srcset="/blog/deleting-massive-s3-buckets-the-easy-way/image-12_hu_f859c91a5bb22a7d.webp 736w, /blog/deleting-massive-s3-buckets-the-easy-way/image-12_hu_ebf55a8c550a1d40.webp 1104w, /blog/deleting-massive-s3-buckets-the-easy-way/image-12_hu_1d75d5fcea97b06b.webp 1472w"
  sizes="(max-width: 48rem) 100vw, 736px"
  width="736"
  height="159"
  alt=""
  
  loading="lazy"
  decoding="async"
/>
</p>
<p>All it takes is a little bit of patience.</p>]]></content:encoded></item></channel></rss>