<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Control Cloud]]></title><description><![CDATA[Control Cloud]]></description><link>https://controlcloud.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Tue, 27 Oct 2026 14:02:37 GMT</lastBuildDate><atom:link href="https://controlcloud.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How to schedule OCI instance stop and start after hours]]></title><description><![CDATA[Stopping OCI instances after hours has a native answer today: Resource Scheduler, an Oracle Cloud Infrastructure service that, according to its release…]]></description><link>https://controlcloud.hashnode.dev/how-to-schedule-oci-instance-stop-and-start-after-hours</link><guid isPermaLink="true">https://controlcloud.hashnode.dev/how-to-schedule-oci-instance-stop-and-start-after-hours</guid><category><![CDATA[Oracle Cloud]]></category><category><![CDATA[Devops]]></category><category><![CDATA[finops]]></category><category><![CDATA[Cloud]]></category><dc:creator><![CDATA[Gonzalo Gjuka]]></dc:creator><pubDate>Sun, 25 Oct 2026 22:59:00 GMT</pubDate><content:encoded><![CDATA[<p>Stopping OCI instances after hours has a native answer today: Resource Scheduler, an Oracle Cloud Infrastructure service that, according to its release notes, shipped on May 21, 2024. Before that, the recipe was a cron job running the OCI CLI on some server. Oracle still documents both options. This post covers what a stopped instance stops billing, how each option is set up, which permissions it needs and when something else makes sense.</p>
<h2>What a stopped OCI instance stops billing</h2>
<p>Before the schedule, the math. According to the "Resource Billing for Stopped Instances" page of the OCI documentation, it depends on the shape:</p>
<ul>
<li>Standard shapes: stopping the instance pauses billing. The stopped instance still counts toward your service limits. The table in Oracle's Compute FAQ includes the standard flexible shapes, billed per OCPU and per GB of memory.</li>
<li>Dense I/O and HPC: the stopped instance keeps billing, because it preserves its NVMe storage. To end the charge you have to terminate it.</li>
<li>GPU: the VM.GPU.A10 series pauses billing when stopped; every other GPU shape keeps billing, because it preserves its GPU resources.</li>
<li>Optimized: on VMs, stopping pauses billing; on bare metal it keeps billing.</li>
</ul>
<p>Two details change the number. Shutting the instance down from inside the operating system does not stop billing: the documentation asks you to stop it from the Console or the API as well. And the boot volume still exists while the instance is stopped: Oracle's Block Volumes FAQ states that it is billed at standard Block Volume pricing, on top of the instance price.</p>
<p>With an 8am to 8pm weekday window, a standard-shape instance stays stopped for 108 of the 168 hours in a week: 64% of the compute. The formula for any schedule, and what does not drop with everything off, is in <a href="https://controlcloud.dev/en/blog/apagar-dev-fuera-de-horario-cuanto-se-ahorra">shutting down dev after hours</a>. The <a href="https://controlcloud.dev/en/calculadora">savings calculator</a> does the math with your VMs.</p>
<h2>Option 1: Resource Scheduler, the native scheduler</h2>
<p>Resource Scheduler stops and starts resources according to the schedules you create from the Console, the CLI or the API. The "About Resource Scheduler" page lists the supported types: Compute instances, instance pools, Autonomous AI Database, Functions and Base DB Systems. What to know before creating the first one:</p>
<ul>
<li>One action per schedule. The CLI command accepts START_RESOURCE, STOP_RESOURCE or BACKUP_RESOURCE, so a working-hours window takes two schedules: one that stops at night and one that starts in the morning.</li>
<li>The recurrence is written in UNIX cron format or in iCalendar format (RFC 5545).</li>
<li>Resources are picked from a fixed list of OCIDs (static) or by filters on type, tags and age since creation (dynamic).</li>
<li>Each schedule handles up to 200 resources. The limit is fixed; with more instances, the documentation says to clone the schedule and split the list.</li>
<li>The documentation shows schedule times in UTC. For a team in Buenos Aires (UTC−3), stopping at 8pm means scheduling 23:00 UTC. From UTC−4 westward, an 8pm stop already falls on the next day in UTC, and the cron weekdays shift with it.</li>
</ul>
<p>For an 8am to 8pm window, Buenos Aires time, Monday to Friday, the two cron expressions look like this:</p>
<pre><code class="language-plaintext"># STOP schedule: 20:00 in Buenos Aires = 23:00 UTC
0 23 * * 1-5

# START schedule: 08:00 in Buenos Aires = 11:00 UTC
0 11 * * 1-5
</code></pre>
<h2>Permissions: two policies, not one</h2>
<p>This is the step most often missed. Creating the schedule does not give it the right to act: the Resource Scheduler IAM policies page states that, by default, a schedule cannot perform actions on the target resources and you have to grant it that permission. Two policies are needed, one for the group that manages schedules and one for the schedule itself as a principal:</p>
<pre><code class="language-plaintext">Allow group ResourceScheduleAdmins to manage resource-schedule-family in compartment dev

Allow any-user to manage instance in compartment id &lt;compartment_ocid&gt; where all{request.principal.type='resourceschedule', request.principal.id='&lt;schedule_ocid&gt;'}
</code></pre>
<p>The second policy is tied to the OCID of each schedule, so a new schedule needs its own condition. When an action fails, the schedule list shows Error as the last run status, and the error details appear on the resource details page.</p>
<h2>Option 2: the OCI CLI with cron</h2>
<p>The official how-to "Automatically Start and Stop a VM Instance" keeps the classic alternative: install and configure the OCI CLI on a server and schedule these two commands with cron or the operating system task scheduler:</p>
<pre><code class="language-shell">oci compute instance action --action STOP --instance-id &lt;instance_ocid&gt;
oci compute instance action --action START --instance-id &lt;instance_ocid&gt;
</code></pre>
<p>It works, but it adds pieces: a server that has to stay on exactly when the others go off, API credentials stored on that machine, and a schedule that lives in a crontab with no change history. If everything that needs a schedule is on OCI, Resource Scheduler does the same without that server. What happens when cron jobs multiply is covered in <a href="https://controlcloud.dev/en/blog/ventanas-operativas-non-prod-audit-log-vs-cron">operating windows in non-prod</a>.</p>
<h2>When native is enough and when it is not</h2>
<p>If your non-production environments live only on OCI, Resource Scheduler handles the schedule with no servers of your own to maintain, and you do not need another tool for that.</p>
<p>The picture changes when dev and QA also have VMs on another cloud. Each provider has its own scheduler, with a different schedule format, a different permission model and a different place to check whether something failed to stop last night. The schedule is configured once; what repeats every week is checking that it ran everywhere.</p>
<blockquote>
<p>Scheduling the shutdown is the easy part. The hard part is knowing, on Monday morning, what did not stop on Friday and why.</p>
</blockquote>
<h2>How Control Cloud handles it</h2>
<p>Control Cloud schedules the stop and start of non-production VMs across every connected cloud, from a single console. Each schedule is a cron expression with its own time zone, so 8pm means 8pm in your city with no UTC conversion, and it picks VMs by ID, by tag or by region.</p>
<p>Every run leaves a row in the audit log: how many VMs matched, how many were stopped, how many were already off and which errors occurred. If a run fails, or the schedule stops finding VMs because somebody renamed a tag, whoever created it gets an email alert. To connect a tenancy with read-only permissions and see what sits in each compartment, the <a href="https://controlcloud.dev/en/guias/oci">OCI guide</a> has the step by step.</p>
]]></content:encoded></item><item><title><![CDATA[Anatomy of a multi-cloud deploy that fails]]></title><description><![CDATA[Friday, 6:10pm. The terraform plan says "12 to add, 3 to change, 1 to destroy". You read it, nothing looks odd. Apply. Green bar. You get up for a coffee…]]></description><link>https://controlcloud.hashnode.dev/anatomy-of-a-multi-cloud-deploy-that-fails</link><guid isPermaLink="true">https://controlcloud.hashnode.dev/anatomy-of-a-multi-cloud-deploy-that-fails</guid><category><![CDATA[Devops]]></category><category><![CDATA[Terraform]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[SRE]]></category><dc:creator><![CDATA[Gonzalo Gjuka]]></dc:creator><pubDate>Fri, 23 Oct 2026 22:07:03 GMT</pubDate><content:encoded><![CDATA[<p>Friday, 6:10pm. The <code>terraform plan</code> says "12 to add, 3 to change, 1 to destroy". You read it, nothing looks odd. Apply. Green bar. You get up for a coffee and when you come back the alerts channel has 40 new messages. The load balancer is not responding and half the pods will not start. The plan was green. What happened?</p>
<h2>The deploy that "could not fail"</h2>
<p>The change was small: clean up an old subnet in one cloud and move a few workloads to another region. Terraform calculated the plan against its <code>tfstate</code> and saw no problem — because from where Terraform looks, there was none. The <code>state</code> knows which resources it created. It does not know who else, outside its world, depends on them.</p>
<p>And there is the trap: the resource you destroyed was the plan's "1 to destroy". A subnet that, according to the state, had nothing hanging off it. Except it did: a load balancer created six months earlier, by hand, from the console, in a hurry. That LB referenced the subnet. Terraform never knew, because that LB was never in its state.</p>
<p>Had the LB been in the same region and account, the cloud would most likely have blocked the delete with a dependency error — and the apply cuts out halfway, with the state half applied. The silent case, the one that "goes green and falls over later", is when the dependency crosses a boundary the integrity check does not look at: another region, another account, another cloud. There the delete goes through and the breakage shows up downstream.</p>
<h2>The dependency nobody saw</h2>
<p>Clouds are full of implicit dependencies that do not surface until you step on them:</p>
<ul>
<li>A subnet a load balancer or an instance is still using.</li>
<li>A gateway a route table points at — delete it and internet egress dies without warning.</li>
<li>A security group referenced by another group's rules.</li>
<li>A public IP attached to a resource you believed was free.</li>
</ul>
<p>None of these is a "Terraform bug". They are dependencies that live in the cloud's real graph, not in the <code>.tf</code> file. The <code>plan</code> is honest: it tells you what it will do with what it manages. The problem is everything else — what was created by hand, what another team manages, what lives in another account or another cloud.</p>
<h2>Why multi-cloud makes it worse</h2>
<p>Seeing the whole graph is already hard in a single cloud. With OCI + Azure + AWS at once, you multiply the problem:</p>
<ul>
<li>Three consoles, three mental models (Compartment vs Resource Group vs account), three ways of naming the same thing.</li>
<li>Cross-cloud dependencies (a DNS record in one pointing at an LB in another) are invisible to every <code>plan</code>, because each state only knows its own cloud.</li>
<li>Whoever approves the change rarely has all three consoles open at once to check by hand.</li>
</ul>
<p>The result is that at 6pm on a Friday nobody has a single view of "what depends on what". And without that view, validating a destructive change is guesswork.</p>
<h2>The pattern underneath: validating after instead of before</h2>
<p>The deploy failed because dependency validation happened at the worst possible moment: at runtime, after the apply, when the resource is already gone. By then the rollback is manual, with the clock running and half the system down.</p>
<blockquote>
<p>The right question is not "is the plan green?". It is "what breaks if I touch this?" — and that question has to be asked BEFORE the apply, not after.</p>
</blockquote>
<h2>The way out: pre-flight dependency checks</h2>
<p>The idea is old and comes from aviation: before takeoff, a checklist confirms that everything you depend on is where it should be. Applied to infrastructure:</p>
<ul>
<li>Before deleting a subnet, ask: is there an LB, an instance or a NIC referencing it? If so, stop.</li>
<li>Before deleting a gateway, look at which route tables use it.</li>
<li>Run the check against the cloud's REAL graph (what exists), not against the state (what Terraform believes exists).</li>
<li>And run it across clouds, not one at a time.</li>
</ul>
<p>This does not replace Terraform — it complements it. Terraform applies the change; the pre-flight tells you whether that change is safe to apply. It is the difference between "12 to add, 1 to destroy" and "1 to destroy — careful, that resource has a load balancer hanging off it in another region".</p>
<h2>See the graph before you touch it</h2>
<p>Control Cloud was born from this pain: it draws your OCI, Azure and other cloud resources on a single map, with dependencies visible, and runs the pre-flight checks before you apply a destructive change. At 6pm on a Friday you do not want to guess — you want to see.</p>
<p>If the green deploy that fell over anyway has ever happened to you, the exercise worth doing is looking at your infrastructure as a graph before the next apply: read-only permissions are enough to see what each resource depends on. Sometimes seeing it is all it takes to understand it.</p>
]]></content:encoded></item><item><title><![CDATA[BYO credentials: why your control plane should never ask for master keys]]></title><description><![CDATA[You sign up for a new tool to see your infrastructure in one place. First onboarding step: "Connect your cloud". And the form asks for the tenancy's admin…]]></description><link>https://controlcloud.hashnode.dev/byo-credentials-why-your-control-plane-should-never-ask-for-master-keys</link><guid isPermaLink="true">https://controlcloud.hashnode.dev/byo-credentials-why-your-control-plane-should-never-ask-for-master-keys</guid><category><![CDATA[Security]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[Devops]]></category><category><![CDATA[AWS]]></category><dc:creator><![CDATA[Gonzalo Gjuka]]></dc:creator><pubDate>Fri, 23 Oct 2026 22:05:52 GMT</pubDate><content:encoded><![CDATA[<p>You sign up for a new tool to see your infrastructure in one place. First onboarding step: "Connect your cloud". And the form asks for the tenancy's admin API key. The master one. The one that can create, delete and bill everything. You paste the key because you want to try the tool, and in that same gesture you handed a third party the keys to the kingdom. Stop right there.</p>
<h2>The request you should refuse</h2>
<p>A global admin credential is "god mode": it can read everything, but it can also drop a production database, open a port to the world, or spin up 200 instances that land on your invoice. When you hand it to a SaaS, that key now lives in another company's database, travels through their logs, is visible to their employees, and is inherited by whoever compromises that system. The blast radius of that single key is your entire account.</p>
<p>And it is almost never necessary. A tool that "visualises your infrastructure" needs to READ resources. It does not need to be able to destroy them. Asking for the master key is, almost always, engineering laziness on the product side: it is easier to ask for everything than to ask for exactly what you need.</p>
<h2>What BYO credentials means</h2>
<p>BYO ("bring your own") flips the model: instead of the tool telling you "give me a key that works for everything", you create a credential specific to that tool, with the minimum permission it needs, and hand that over. The key is yours, you created it, and you can switch it off at any moment without asking anyone.</p>
<ul>
<li>Least privilege: only the permissions the tool actually uses. If it only reads, it should only be able to read.</li>
<li>Narrow scope: limited to one compartment / resource group / project, not the whole account.</li>
<li>Its own identity: one credential per integration, not a human's personal key shared by everyone.</li>
<li>Revocable: you delete it and the integration stops working, without taking anything else down.</li>
</ul>
<h2>Read by default, write explicitly</h2>
<p>The healthy rule is that a tool starts read-only. Mapping, inventory, showing costs, drawing the dependency graph: all of that runs on read permissions. The day you want the tool to also apply a change, that is a separate, explicit decision, with its own write credential scoped to the specific resource. Not a permission that came free in the onboarding bundle.</p>
<h2>Revocable without breaking anything else</h2>
<p>This is the acid test of the BYO model: if you stop trusting the tool tomorrow, what does it cost you to cut it off? With a shared master key, revoking it breaks everything else that was using it and forces you into a chain of credential rotations. With a dedicated least-privilege credential, you delete it in 30 seconds and the rest of your account never notices.</p>
<blockquote>
<p>The question is not "do I trust this tool?". It is "what happens the day I stop trusting it?". If the answer is "I have to rotate my whole account's key", the model was already wrong.</p>
</blockquote>
<h2>How Control Cloud does it</h2>
<p>Control Cloud is BYO by default: you create a read-only credential in your cloud, scoped however you like, and load it in. We never ask for the tenancy admin key, and we never need it for what the product does: see your infrastructure, draw dependencies and warn you before a destructive change. You hold the key, you switch it off.</p>
<p>If some tool is asking for more permission than its function justifies, that is all the signal you need. Connect a cloud read-only and see how much can be done without handing over the keys to the kingdom.</p>
]]></content:encoded></item><item><title><![CDATA[Operating windows in non-prod: governance with an audit log vs scattered cron scripts]]></title><description><![CDATA[The arithmetic is easy: a QA environment that runs 24/7 but is only used from 9 to 7, Monday to Friday, is powered on three times longer than it needs to…]]></description><link>https://controlcloud.hashnode.dev/operating-windows-in-non-prod-governance-with-an-audit-log-vs-scattered-cron-scripts</link><guid isPermaLink="true">https://controlcloud.hashnode.dev/operating-windows-in-non-prod-governance-with-an-audit-log-vs-scattered-cron-scripts</guid><category><![CDATA[Devops]]></category><category><![CDATA[SRE]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[automation]]></category><dc:creator><![CDATA[Gonzalo Gjuka]]></dc:creator><pubDate>Fri, 23 Oct 2026 22:04:34 GMT</pubDate><content:encoded><![CDATA[<p>The arithmetic is easy: a QA environment that runs 24/7 but is only used from 9 to 7, Monday to Friday, is powered on three times longer than it needs to be. Shutting it down outside those hours can cut the non-prod bill in half or better. So somebody writes a cron that stops it at 8pm and starts it at 8am. It works. And that is where the real problem begins.</p>
<h2>The cron that worked too well</h2>
<p>The first cron goes well, so a second one shows up for another environment, on another machine. Then a third that somebody put on their own VM because it was faster than requesting access to the shared server. Six months later you have half a dozen scripts scattered around, each with its schedule hardcoded, none documented, and the knowledge of "what shuts down what" living in the head of the person who wrote it — who may not be on the team anymore.</p>
<ul>
<li>Nobody has a list of which environments have a window and which do not.</li>
<li>When an environment "woke up switched off" on a day it was needed, there is no fast way to find out which script touched it.</li>
<li>Changing a schedule means finding the right script, on the right machine, and editing it by hand without breaking cron syntax.</li>
<li>There is no record of who changed what or when: <code>crontab -e</code> leaves no history.</li>
</ul>
<h2>The hidden cost is not the money, it is the opacity</h2>
<p>The saving from shutting down non-prod is real and worth having. But scattered cron charges you for it another way: in debugging hours every time something "was not there when I needed it", in fear of touching the scripts in case you break something, and in the impossibility of answering simple questions like "why was this environment down on Tuesday at 3pm?". You traded a measurable cost (the invoice) for an invisible one (the team's time and its trust in the system).</p>
<blockquote>
<p>One cron that shuts an environment down is automation. Six crons with no inventory and no history are operational debt dressed up as savings.</p>
</blockquote>
<h2>What changes with governance and an audit log</h2>
<p>The alternative is not to stop shutting down non-prod — it is to centralise the decision and leave a trail. A governed operating window has three properties scattered cron never gives you:</p>
<ul>
<li>A single inventory: one view of which environments have a window, on what schedule, in which cloud.</li>
<li>An audit log: every shutdown, start-up and schedule change recorded with who and when. When something wakes up switched off, the answer is one line in the log, not an archaeological dig across six machines.</li>
<li>Risk-free change: adjusting a schedule means editing one value in one place, not editing crontab on a production box by hand.</li>
</ul>
<h2>The difference shows up the day something fails</h2>
<p>With scattered crons, the day an environment is not there when it was needed starts an incident: somebody has to guess which script shut it down, find it, and revert by hand. With a governed window and its audit log, that same event is a ten-second query: who, when, why. The automation does not go away — it gains a memory.</p>
<h2>See and govern the windows from one place</h2>
<p>Control Cloud treats operating windows as part of the control plane, not as loose scripts: you define each environment's schedule from a single place, across any of your clouds, and every action lands in the audit log. The saving from shutting down non-prod is still there; what you remove is the opacity.</p>
<p>If today you have scattered crons nobody dares touch, the first step is simply to see all your environments together and know which ones are wasting hours. From there, governing the window is the easy part.</p>
]]></content:encoded></item><item><title><![CDATA[Generated vs hand-written Terraform: when to choose each]]></title><description><![CDATA[You inherited an account with 300 resources created by hand over three years, and you were asked to "put it in Terraform". You have two paths: generate the…]]></description><link>https://controlcloud.hashnode.dev/generated-vs-hand-written-terraform-when-to-choose-each</link><guid isPermaLink="true">https://controlcloud.hashnode.dev/generated-vs-hand-written-terraform-when-to-choose-each</guid><category><![CDATA[Terraform]]></category><category><![CDATA[Devops]]></category><category><![CDATA[#IaC]]></category><category><![CDATA[Cloud]]></category><dc:creator><![CDATA[Gonzalo Gjuka]]></dc:creator><pubDate>Fri, 23 Oct 2026 22:00:59 GMT</pubDate><content:encoded><![CDATA[<p>You inherited an account with 300 resources created by hand over three years, and you were asked to "put it in Terraform". You have two paths: generate the code automatically from the infrastructure that already exists (import, reverse-engineering tools), or sit down and write it by hand. The easy answer is "generate it, obviously". The right answer is "it depends what you are trying to achieve".</p>
<h2>What generated Terraform gives you</h2>
<p>Generating the code has one enormous virtue: speed and fidelity. Instead of guessing the exact configuration of 300 resources, a tool reads them from the cloud and emits HCL describing them exactly as they are. It is the right path when your goal is to capture the current state quickly — for example, to bring something under control that has none today.</p>
<ul>
<li>Fast: minutes rather than days for a large estate.</li>
<li>Faithful: it reflects what is REALLY running, including the manual patches nobody documented.</li>
<li>A good starting point when there is nothing and you need to close the "infrastructure without code" gap now.</li>
</ul>
<p>The cost is that generated code tends to be ugly: automatic resource names, zero modules, everything flat, hardcoded values where variables belong, and the noise of every attribute the cloud sets by default. It is correct, but it is not code you want to maintain.</p>
<h2>What hand-written Terraform gives you</h2>
<p>Writing the code yourself forces you to understand what you are modelling. It is slow, but the result is code built to live: reusable modules, variables where they belong, names that mean something, and a structure that reflects how you want the infrastructure to be — not the historical accident of how it ended up.</p>
<ul>
<li>Maintainable: modules, variables, conventions, written so somebody else can read it in six months.</li>
<li>Intentional: you model the desired state instead of cloning the existing mess.</li>
<li>It teaches the team: writing it is the fastest way to genuinely understand what you have.</li>
</ul>
<blockquote>
<p>Generated Terraform answers "what do I have?". Hand-written Terraform answers "what do I want to have?". They are two different questions, and you almost always need both.</p>
</blockquote>
<h2>The pattern that works: generate to discover, write to maintain</h2>
<p>In practice the best path is not to pick one: it is to use them in sequence. You generate first to get a faithful, honest inventory of what exists — without that snapshot you are writing blind. Then you use that snapshot as the reference for writing, by hand and clean, the code you will actually maintain. The generated version is the discovery draft; the written one is the final artefact.</p>
<h2>The step almost everyone skips</h2>
<p>Both generating and writing assume you already know what is in your account. And that is exactly the problem: in three years of hand-built infrastructure, nobody holds the complete inventory in their head. Before choosing to generate or to write, step zero is to SEE — to have the real map of resources and dependencies, so you do not discover halfway through the project that there was a critical subnet nobody mentioned.</p>
<p>Control Cloud gives you that map: it draws what exists across your clouds with the dependencies visible, so you start the Terraform work — generating or writing — knowing what is really there. See first, code after.</p>
]]></content:encoded></item><item><title><![CDATA[Unused public IPs: what AWS and GCP charge and how to find them]]></title><description><![CDATA[A public IP looks free because it has no CPU and no disk. It is not. Since February 1, 2024, AWS bills USD 0.005 per hour for every public IPv4 address…]]></description><link>https://controlcloud.hashnode.dev/unused-public-ips-what-aws-and-gcp-charge-and-how-to-find-them</link><guid isPermaLink="true">https://controlcloud.hashnode.dev/unused-public-ips-what-aws-and-gcp-charge-and-how-to-find-them</guid><category><![CDATA[AWS]]></category><category><![CDATA[GCP]]></category><category><![CDATA[finops]]></category><category><![CDATA[Cloud]]></category><dc:creator><![CDATA[Gonzalo Gjuka]]></dc:creator><pubDate>Fri, 23 Oct 2026 19:15:57 GMT</pubDate><content:encoded><![CDATA[<p>A public IP looks free because it has no CPU and no disk. It is not. Since February 1, 2024, AWS bills USD 0.005 per hour for every public IPv4 address, whether it is attached to a resource or not. That is USD 3.65 per month per address (0.005 × 730 hours). One of them does not move the invoice; forty forgotten across three years and several accounts do: USD 146 per month that buys nothing.</p>
<h2>What each cloud charges for a public IP</h2>
<ul>
<li>AWS: USD 0.005 per hour for every public IPv4 address, including unassociated Elastic IPs and the public IPs AWS assigns to instances, load balancers and NAT gateways. The rule applies since February 1, 2024 and is stated in the official AWS announcement of the public IPv4 charge.</li>
<li>GCP: a reserved static external IP that is not assigned to anything costs USD 0.01 per hour; the same address in use by a standard VM costs USD 0.005 per hour, in US regions according to the Google Cloud network pricing page. Unused costs twice as much: USD 7.30 per month against USD 3.65.</li>
<li>Azure: a Standard public IP bills by the hour for as long as it exists, associated or not. An IP left hanging from a NIC with no VM bills the same as one in use.</li>
</ul>
<p>What sets an IP apart from other forgotten resources is that it shows up on no performance dashboard: it has no metrics, fires no alerts and takes no space. It only shows up on the invoice, as a small line that repeats every month.</p>
<h2>Why orphaned IPs are left behind</h2>
<p>Almost always because of an incomplete deletion. Deleting a VM by hand from the console removes the VM, but not necessarily what surrounded it:</p>
<ul>
<li>On AWS, an Elastic IP outlives the instance: when you terminate it, the address stays allocated to your account, unassociated, billing.</li>
<li>On Azure, deleting the VM can leave the NIC and the public IP behind, unless the option to delete them with the VM was selected. The IP stays associated with a NIC that serves nobody, and it cannot be released until it is dissociated.</li>
<li>On GCP, an address reserved as static goes back to the RESERVED status when the VM that used it is deleted.</li>
</ul>
<p>The other common source is testing: somebody reserves an IP for a temporary environment, the environment gets deleted and the IP does not.</p>
<h2>How to find them, cloud by cloud</h2>
<p>Read-only permissions are enough: that is least privilege in practice, because finding does not require the right to delete. On AWS, the Elastic IPs with no association (the command is per region):</p>
<pre><code class="language-plaintext">aws ec2 describe-addresses \
  --query "Addresses[?AssociationId==null].[PublicIp,AllocationId]" \
  --output table
</code></pre>
<p>There is one case this filter does not show: an Elastic IP associated with a network interface (ENI) that was left loose, with no instance. It reads as associated and serves nobody. To see it, list the ENIs in the available status with <code>aws ec2 describe-network-interfaces --filters Name=status,Values=available</code>.</p>
<p>On GCP, the reserved external addresses nobody uses:</p>
<pre><code class="language-plaintext">gcloud compute addresses list \
  --filter="status=RESERVED AND addressType=EXTERNAL" \
  --format="table(name,address,region,status)"
</code></pre>
<p>On Azure, the public IPs with no IP configuration attached, and the NICs with no VM:</p>
<pre><code class="language-plaintext">az network public-ip list \
  --query "[?ipConfiguration==null].{name:name, rg:resourceGroup, ip:ipAddress}" -o table

az network nic list \
  --query "[?virtualMachine==null &amp;&amp; privateEndpoint==null].{name:name, rg:resourceGroup}" -o table
</code></pre>
<p>The <code>privateEndpoint==null</code> filter is not decoration: the NIC of a Private Endpoint has no VM either, and without that filter an interface in use shows up as orphaned. Deleting it cuts private access to a service.</p>
<h2>Before deleting: proven orphan or unknown</h2>
<p>A list of candidates is not a deletion list. If a command could not read a field, because of permissions or a region that did not answer, the honest answer is "I do not know", not "it is free". Releasing an IP in use is worse than paying for it one more month: the address does not come back, and if it was in a DNS record or on a customer allowlist, the outage is immediate.</p>
<blockquote>
<p>An unused IP costs USD 3.65 per month on AWS. An IP released by mistake can cost an incident.</p>
</blockquote>
<h2>How Control Cloud handles it</h2>
<p>Control Cloud reads the inventory of your clouds with read-only credentials and gives every IP one of three verdicts: orphaned, in use or unknown. The cost advisor lists only the proven orphans, each with its monthly cost: an Elastic IP that is unassociated or associated with an ENI whose instance no longer exists, a GCP static IP nobody uses, an Azure public IP held by a NIC with no VM. Unknown never counts as a saving, and on GCP an IP with no verdict is priced at the in-use rate, the lower one, so the number is not inflated.</p>
<p>The advisor total is the exact sum of the listed rows, at the same rates as the rest of the cost page, and those rates can be edited if your contract has different ones. To estimate what shutting VMs down can cut in non-prod, the <a href="https://controlcloud.dev/en/calculadora">savings calculator</a> does the math, and <a href="https://controlcloud.dev/en/blog/apagar-dev-fuera-de-horario-cuanto-se-ahorra">shutting down dev after hours</a> details what drops and what does not.</p>
]]></content:encoded></item><item><title><![CDATA[Shutting down dev and QA after hours: how much you actually save]]></title><description><![CDATA[The question always arrives the same way: "if we shut dev down at night, how much does the invoice drop?". The short answer is less than the compute math…]]></description><link>https://controlcloud.hashnode.dev/shutting-down-dev-and-qa-after-hours-how-much-you-actually-save</link><guid isPermaLink="true">https://controlcloud.hashnode.dev/shutting-down-dev-and-qa-after-hours-how-much-you-actually-save</guid><category><![CDATA[Devops]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[finops]]></category><category><![CDATA[AWS]]></category><dc:creator><![CDATA[Gonzalo Gjuka]]></dc:creator><pubDate>Fri, 23 Oct 2026 17:23:34 GMT</pubDate><content:encoded><![CDATA[<p>The question always arrives the same way: "if we shut dev down at night, how much does the invoice drop?". The short answer is less than the compute math says and more than most people expect. The long answer has a formula and a list of things that keep billing with everything turned off.</p>
<h2>The math: 108 of 168 hours</h2>
<p>A week has 168 hours. If the team uses the environment from 8am to 8pm, Monday to Friday, the VM is in use for 60 hours (12 × 5). The other 108 are nights and weekend: five 12-hour nights plus the 48 hours of Saturday and Sunday. 108 / 168 = 64%.</p>
<p>For any window, with N hours off every weekday night:</p>
<pre><code class="language-plaintext">share off = (48 + 5 × N) / 168
</code></pre>
<ul>
<li>N = 8 (on 16 hours a day): 88 / 168 = 52%</li>
<li>N = 10: 98 / 168 = 58%</li>
<li>N = 12: 108 / 168 = 64%</li>
<li>N = 14: 118 / 168 = 70%</li>
<li>N = 16 (on 8 hours a day): 128 / 168 = 76%</li>
</ul>
<p>As an example: a VM billed at USD 0.10 per hour costs USD 73 per month running all the time (730 hours). With N = 12 it stops billing 64% of those hours: USD 46.93 less per month. The <a href="https://controlcloud.dev/en/calculadora">savings calculator</a> does this same math with your number of VMs, their cost and your schedule.</p>
<h2>What does not drop when you turn the VMs off</h2>
<p>The 64% applies to the compute of the VM, not to the invoice of the environment. A dev environment has more pieces, and several keep billing with the VMs off:</p>
<ul>
<li>Disks. They are billed per provisioned GB, with the VM on or off. The disk of a stopped VM keeps billing in full.</li>
<li>Managed databases. RDS, Azure SQL or Cloud SQL have their own lifecycle: turning off the VMs that use them does not stop them. On AWS, a stopped RDS instance starts again on its own after seven days.</li>
<li>Load balancers and NAT gateways. They are billed per hour of existence, plus processed traffic. With no VMs behind them they still bill the hour.</li>
<li>Public IPs. On AWS every public IPv4 address costs USD 0.005 per hour, attached or not, so the Elastic IP of a stopped instance keeps billing. The details are in <a href="https://controlcloud.dev/en/blog/ips-publicas-que-cobran-aws-gcp">unused public IPs</a>.</li>
<li>Committed capacity. A VM covered by a reservation or a usage commitment is paid for regardless: turning it off returns nothing.</li>
</ul>
<p>One Azure detail: a VM shut down from inside the operating system still holds its hardware and bills compute. It stops billing only once it is deallocated, which is what the Stop button in the portal or <code>az vm deallocate</code> does.</p>
<h2>Three lists, not a percentage</h2>
<p>That is why the Control Cloud cost advisor does not apply a percentage to the invoice total. It keeps three lists apart, because each one carries a different certainty:</p>
<ul>
<li>Unused: paying for nothing. Proven orphaned resources that bill, such as disks with no VM, unassociated IPs or interfaces with no owner. The saving is their full cost. Anything that could not be verified stays out.</li>
<li>Disks of stopped VMs. The VM bills no compute, but its disk does. If the VM is not coming back, a snapshot and deleting the disk end that charge.</li>
<li>Off-hours shutdown. Running VMs billed by the hour. The saving of each one is its monthly cost times the share off, (48 + 5 × N) / 168, with N chosen on the page.</li>
</ul>
<p>Each resource appears in one list only, so no saving is counted twice. And the number at the top, the possible saving, is the exact sum of the rows below it: every amount is an inventory row, at the same rates as the rest of the page. If a figure cannot be traced back to a resource, it is not there.</p>
<blockquote>
<p>A saving you cannot point to in an inventory row is a marketing estimate, not an operations number.</p>
</blockquote>
<h2>Before the first shutdown</h2>
<p>The math says how much can be cut; what is left is deciding who turns off what, and keeping a record. With cron scripts spread around, that turns opaque quickly, and it is the subject of <a href="https://controlcloud.dev/en/blog/ventanas-operativas-non-prod-audit-log-vs-cron">operating windows in non-prod</a>.</p>
<p>The order that works: first the paying-for-nothing list, then the disks of stopped VMs, and only then the schedule. The first two cut the full cost of each resource without changing anybody's working hours; the third needs the window agreed with the team that uses the environment.</p>
]]></content:encoded></item></channel></rss>