<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0"><channel><title>Pulumi Blog: Adam Gordon Bell</title><link>https://www.pulumi.com/blog/author/adam-gordon-bell/</link><description>Pulumi blog posts: Adam Gordon Bell.</description><language>en-us</language><pubDate>Tue, 21 Jul 2026 00:00:00 +0000</pubDate><item><title>Kubernetes Agent Sandbox: What It Is and How to Deploy It with Pulumi</title><link>https://www.pulumi.com/blog/kubernetes-agent-sandbox/</link><pubDate>Tue, 21 Jul 2026 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/kubernetes-agent-sandbox/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/kubernetes-agent-sandbox/index.png" /&gt;
&lt;p&gt;When you use a coding agent, it can seem like there&amp;rsquo;s a trade-off between autonomy and permissions. If you approve every command, it&amp;rsquo;s safe but slow. Let it do whatever it likes and it works more autonomously, but as the &lt;a href="https://www.stepsecurity.io/blog/supply-chain-security-alert-popular-nx-build-system-package-compromised-with-data-stealing-malware"&gt;nx supply-chain attack&lt;/a&gt; showed, that can go badly.&lt;/p&gt;
&lt;p&gt;The fix is to give the agent a sandbox: a box it&amp;rsquo;s &lt;em&gt;allowed&lt;/em&gt; to wreck, with limited permissions and scoped network access. The only files are the checkout you handed it, the only credentials are the task&amp;rsquo;s own, and trashing the machine just means a disposable pod gets garbage-collected early. &lt;a href="https://www.pulumi.com/product/neo/"&gt;Pulumi Neo&lt;/a&gt; works this way, and if you want to scale that pattern up inside your own organization, the Kubernetes project &lt;a href="https://agent-sandbox.sigs.k8s.io/"&gt;Agent Sandbox&lt;/a&gt; is a great path to building your own. This post is what it is and how to deploy it on GKE with Pulumi.&lt;/p&gt;
&lt;h2 id="what-is-agent-sandbox"&gt;What is Agent Sandbox?&lt;/h2&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;strong&gt;&lt;a href="https://github.com/kubernetes-sigs/agent-sandbox"&gt;Agent Sandbox&lt;/a&gt;&lt;/strong&gt; is a Kubernetes SIGs project that gives AI agents isolated, disposable environments as Kubernetes resources: a &lt;code&gt;Sandbox&lt;/code&gt; custom resource, backed by gVisor or Kata Containers for kernel-level isolation.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You could build Agent Sandbox yourself. You&amp;rsquo;d need gVisor support, a userspace kernel that sits between the agent&amp;rsquo;s code and your host. With that in place, you could approximate a sandbox for every agent by stringing together a StatefulSet of size one, a headless Service, and a PersistentVolumeClaim, plus some lifecycle machinery to keep warm pools of nodes around. Agent Sandbox wraps all of that up as a CRD, so you can run a Kubernetes cluster where each sandbox is a disposable, kernel-isolated environment a coding agent runs in.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;agents.x-k8s.io/v1beta1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Sandbox&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;demo-sandbox&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;podTemplate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runtimeClassName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;gvisor&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;agent&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu:24.04 &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# swap in your coding-agent image&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;sleep&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;infinity&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;figcaption&gt;
&lt;center&gt;
&lt;i&gt;
The &lt;code&gt;Sandbox&lt;/code&gt; CRD, the whole idea in one manifest.
&lt;/i&gt;
&lt;/center&gt;
&lt;/figcaption&gt;
&lt;p&gt;There are two common patterns for using it. In the first, every coding agent session in the organization maps to its own pod with a persistent volume, basically Claude Code running across a whole fleet of pods, each one an individual user&amp;rsquo;s session.&lt;/p&gt;
&lt;p&gt;&lt;img src="scenario-1.png" alt="Scenario 1: a dedicated agent cluster giving each developer their own kernel-isolated sandbox with a coding agent and IDE inside"&gt;&lt;/p&gt;
&lt;p&gt;In the second, you&amp;rsquo;re building your own Neo or Devin: a productized agent harness that needs to spin up execution environments. The harness lives outside the sandboxes and hands each task a disposable box, and Agent Sandbox is what spins those boxes up and down, suspending and resuming them.&lt;/p&gt;
&lt;h2 id="why-not-just-run-agents-in-docker"&gt;Why not just run agents in Docker?&lt;/h2&gt;
&lt;p&gt;So why do we need gVisor or Kata Containers at all? The agent already runs inside a container. Isn&amp;rsquo;t that the box?&lt;/p&gt;
&lt;p&gt;Not really. A container isn&amp;rsquo;t much of a security barrier. Every container on a host shares one kernel, and the Linux kernel exposes a huge surface area, 450+ syscalls, to every one of them. &lt;a href="https://unit42.paloaltonetworks.com/breaking-docker-via-runc-explaining-cve-2019-5736/"&gt;CVE-2019-5736&lt;/a&gt; is the canonical example: a malicious container tricks runc into overwriting the &lt;em&gt;host&amp;rsquo;s own runc binary&lt;/em&gt;, and after that every &lt;code&gt;docker run&lt;/code&gt; on the host runs attacker code as root. Put a prompt-injectable AI agent in that container, and the risk is obvious.&lt;/p&gt;
&lt;p&gt;gVisor, the runtime underneath Agent Sandbox&amp;rsquo;s default path, puts a userspace kernel written in Go between your agent and the host, which limits the possible security surface area. A kernel bug the agent can reach becomes, mostly, a crash in a userspace process rather than a root shell on your node. That is what makes an Agent Sandbox secure.&lt;/p&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;p&gt;&lt;strong&gt;What is GKE Sandbox?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GKE Sandbox&lt;/strong&gt; is GKE&amp;rsquo;s gVisor feature: a &lt;code&gt;RuntimeClass&lt;/code&gt; named &lt;code&gt;gvisor&lt;/code&gt; that runs a pod under the userspace kernel described above. It predates Agent Sandbox by years and is a per-pod isolation primitive, not an agent tool. &lt;strong&gt;Agent Sandbox&lt;/strong&gt; is the lifecycle layer that sits on top of it: the CRD&amp;rsquo;s runtime selection points down at gVisor (or Kata) for the actual kernel isolation. They compose. On GKE, Agent Sandbox with &lt;code&gt;runtimeClassName: gvisor&lt;/code&gt; is literally using GKE Sandbox underneath.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Google engineers in Kubernetes SIG Apps maintain Agent Sandbox, Janet Kuo and Justin Santa Barbara (of kOps) among them, and it launched at KubeCon NA in November 2025. It&amp;rsquo;s also one layer of a broader cloud-native agent stack taking shape: agent-sandbox for isolation, kagenti (IBM) for identity, kagent (Solo.io, a CNCF Sandbox project) for agent logic, agent-substrate for density. As Solo.io&amp;rsquo;s Lin Sun puts it, &lt;a href="https://www.cncf.io/blog/2026/07/07/why-sandboxing-your-agent-is-not-enough/"&gt;&amp;ldquo;Sandboxing your agents is necessary, but not sufficient.&amp;rdquo;&lt;/a&gt;&lt;/p&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;p&gt;&lt;strong&gt;The rung above gVisor: hardware microVMs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;gVisor filters syscalls in a userspace kernel. The heavier option gives the workload its own guest kernel behind hardware virtualization (KVM), so an escape has to cross a CPU-enforced boundary. &lt;strong&gt;Firecracker&lt;/strong&gt;, the VMM AWS built for Lambda (and the isolation layer under &lt;a href="https://www.pulumi.com/blog/from-works-on-my-machine-to-production-ready-ai-agents-with-amazon-bedrock-agentcore/"&gt;Bedrock AgentCore&lt;/a&gt;), keeps that boundary cheap: roughly 50,000 lines of Rust against QEMU&amp;rsquo;s ~1.4 million lines of C. Google&amp;rsquo;s &lt;a href="https://google.github.io/security-research/kvmctf/rules.html"&gt;kvmCTF&lt;/a&gt; pays $250,000 for a KVM escape, which is how the market rates that boundary. Agent Sandbox reaches this rung through &lt;strong&gt;Kata Containers&lt;/strong&gt; via the same &lt;code&gt;runtime&lt;/code&gt; selection, and the tradeoff isn&amp;rsquo;t linear: Kata can beat gVisor on I/O-heavy work, because its guest runs a real kernel servicing syscalls natively.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="the-one-second-problem"&gt;The one-second problem&lt;/h2&gt;
&lt;p&gt;Isolation is the hard part, but it isn&amp;rsquo;t the only one. If you&amp;rsquo;re in scenario 2, using Agent Sandbox pods as the backing instances for your own agent harness, startup time is a challenge too. People abandon chat sessions, and when they come back they expect the agent to pick up quickly, not wait on a machine to boot.&lt;/p&gt;
&lt;p&gt;&lt;img src="scenario-2.png" alt="Scenario 2: a harness outside the sandboxes, routing each session to a box where tools run, suspending and resuming them from snapshots"&gt;&lt;/p&gt;
&lt;p&gt;Booting a fresh Kubernetes pod costs about a second of overhead. That&amp;rsquo;s nothing for a rolling deployment, but enough that the maintainers say it &lt;a href="https://kubernetes.io/blog/2026/03/20/running-agents-on-kubernetes-with-agent-sandbox/"&gt;&amp;ldquo;breaks the continuity&amp;rdquo;&lt;/a&gt; of an interaction. So Agent Sandbox avoids paying that cost twice: suspend a sandbox and its pod goes away while its workspace and identity persist, and warm pools keep pre-booted pods ready for new sandboxes to adopt. The managed GKE version goes further and restores from memory snapshots, the same snapshot-restore move E2B (~150ms) and Fly&amp;rsquo;s Sprites (~300ms) use to pull the effective cold start well under a second&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h2 id="deploying-it-on-gke-with-pulumi"&gt;Deploying it on GKE with Pulumi&lt;/h2&gt;
&lt;p&gt;If you already have a cluster and want to try Agent Sandbox, the &lt;code&gt;kubectl apply&lt;/code&gt; from the project&amp;rsquo;s quickstart will probably get you going. But productionized use takes a bit more. Let&amp;rsquo;s walk through setting up a cluster with a sane access policy, named individuals, and a dedicated environment per user. The full program is at &lt;a href="https://github.com/pulumi/examples/tree/master/gcp-ts-agent-sandbox"&gt;pulumi/examples/gcp-ts-agent-sandbox&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Move 1: the cluster.&lt;/strong&gt; First we stand up a GKE cluster with a gVisor node pool. The one line that matters is the &lt;code&gt;sandboxConfig&lt;/code&gt;, with &lt;code&gt;sandboxType: &amp;quot;gvisor&amp;quot;&lt;/code&gt;. As far as I&amp;rsquo;m aware, this can only be done on GKE:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gvisorPool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;gcp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NodePool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;gvisor-pool&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;cluster&lt;/span&gt;: &lt;span class="kt"&gt;cluster.name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;nodeConfig&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;machineType&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;n2-standard-4&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;imageType&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;COS_CONTAINERD&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// gVisor requires COS + containerd
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;sandboxConfig&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sandboxType&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;gvisor&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;GKE installs the &lt;code&gt;gvisor&lt;/code&gt; RuntimeClass, labels the nodes, and taints them so only pods that opt in land there.&lt;/p&gt;
&lt;p&gt;That auto-install is the GKE convenience, not a requirement. Agent Sandbox itself is just CRDs and a controller, so on any other cluster you can &lt;a href="https://gvisor.dev/docs/user_guide/install/"&gt;install gVisor yourself&lt;/a&gt; (or Kata), register a &lt;code&gt;RuntimeClass&lt;/code&gt;, and everything below works the same.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Move 2: the install, pinned.&lt;/strong&gt; Next we install the controller and the CRDs, using the all-in-one &lt;code&gt;sandbox-with-extensions.yaml&lt;/code&gt; manifest that shipped in last week&amp;rsquo;s v0.5.2&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sb"&gt;`https://github.com/kubernetes-sigs/agent-sandbox/releases/download/v0.5.2`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agentSandbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;k8s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ConfigGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;agent-sandbox&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;/sandbox-with-extensions.yaml`&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Pinning matters here: releases land every week or two, and the API group already graduated once (&lt;code&gt;v1alpha&lt;/code&gt; to &lt;code&gt;v1beta1&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Move 3: sandboxes are a loop, not a manifest.&lt;/strong&gt; This is where Pulumi really shows its advantages. In practice you don&amp;rsquo;t create sandboxes by hand, because they&amp;rsquo;re per-user or per-task. So the demo reads a list and maps over it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;developers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requireObject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;Developer&lt;/span&gt;&lt;span class="err"&gt;[]&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;developers&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Who may open which box, derived from that same list.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;acl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sandboxAcl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;developers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sandboxes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;developers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AgentSandbox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`sbx-&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;owner&lt;/span&gt;: &lt;span class="kt"&gt;dev.name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// this owner&amp;#39;s Claude Code creds, per box, not per cluster
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt;: &lt;span class="kt"&gt;BOOT_PROMPT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;dependsOn&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;agentSandbox&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;operator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;acl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That &lt;code&gt;developers&lt;/code&gt; array is read at runtime, so it could just as easily be a GitHub team or whoever currently has a session open, none of which &lt;code&gt;kubectl apply -k&lt;/code&gt; can loop over. (The &lt;code&gt;developers&lt;/code&gt; list and the per-box credentials are stack config; the &lt;a href="https://www.pulumi.com/registry/packages/gcp/how-to-guides/gcp-ts-agent-sandbox/#running-the-example"&gt;example&amp;rsquo;s README&lt;/a&gt; has the exact &lt;code&gt;pulumi config set&lt;/code&gt; commands.)&lt;/p&gt;
&lt;p&gt;Nothing in the sandbox is Claude-specific, either. The agent is just what the image installs: swap in Codex CLI, or point one of the &lt;a href="https://www.pulumi.com/blog/claude-code-orchestration-frameworks/"&gt;Claude Code orchestration frameworks&lt;/a&gt; at it, and the isolation story doesn&amp;rsquo;t change.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Move 4: the egress policy.&lt;/strong&gt; Create a &lt;code&gt;Sandbox&lt;/code&gt; directly, as we do here, and &lt;strong&gt;no NetworkPolicy applies&lt;/strong&gt;&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;: kernel isolation, but wide-open egress. (The &lt;code&gt;SandboxTemplate&lt;/code&gt; extension manages a default policy for template-created sandboxes; a raw &lt;code&gt;Sandbox&lt;/code&gt; gets nothing.) We set a policy that lets the agent reach &lt;code&gt;api.anthropic.com&lt;/code&gt; and npm but not any private IPs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;egress&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="nx"&gt;ipBlock&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;cidr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;0.0.0.0/0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;except&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;10.0.0.0/8&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;172.16.0.0/12&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;192.168.0.0/16&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;169.254.169.254/32&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}}],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="cm"&gt;/* + a DNS rule to kube-system:53 */&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We also exclude &lt;code&gt;169.254.169.254&lt;/code&gt; to keep the agent off the node metadata server.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Move 5: private access with Tailscale.&lt;/strong&gt; The sandbox runs an IDE (code-server) on port 13337. Instead of a public load balancer with a password, we put each sandbox on a tailnet with the Tailscale Kubernetes operator. Three annotations on the Service do it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;tailscale.com/expose&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;true&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;tailscale.com/hostname&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// sbx-adam
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;tailscale.com/tags&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`tag:&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// tag:sbx-adam
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Move 6: lock down each sandbox.&lt;/strong&gt; Finally, we generate the tailnet access policy from the same developer list that creates the boxes, so each person can reach only their own box:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;acls&lt;/span&gt;: &lt;span class="kt"&gt;developers.map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;accept&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// this person
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sb"&gt;`tag:sbx-&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;:13337`&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// this box, this port. Nothing else.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;})),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Because the boxes and the grants come from one array, a developer&amp;rsquo;s sandbox and their permission to open it can&amp;rsquo;t drift apart.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Move 7: run it.&lt;/strong&gt; With all of that declared, &lt;code&gt;pulumi up&lt;/code&gt; builds the whole environment in one command: cluster, gVisor pool, controller, per-developer sandboxes, egress policy, and private access. Get the stack&amp;rsquo;s URL and open it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ pulumi stack output sandboxUrls
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;http://sbx-adam:13337&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Open it on any device signed into your tailnet and you&amp;rsquo;re in a full VS Code (code-server), running inside the sandbox pod, on the gVisor node pool, behind the egress policy set above.&lt;/p&gt;
&lt;p&gt;Each box also boots with a task. The prompt in the demo asks the agent to figure out where it is: read &lt;code&gt;/proc/version&lt;/code&gt;, decide whether it&amp;rsquo;s in a container or a VM, and write up the evidence in a &lt;code&gt;REPORT.md&lt;/code&gt;. An agent doing forensics on its own jail is a decent smoke test that the isolation is real.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the finished result:&lt;/p&gt;
&lt;blockquote class="twitter-tweet" data-dnt="true"&gt;&lt;p lang="en" dir="ltr"&gt;Recorded a little demo: Here&amp;#39;s how every developer gets their own kernel-isolated dev box on Kubernetes: VS Code + Claude Code in a pod behind Tailscale with gVisor underneath for security isolation. write up coming&lt;/p&gt;&amp;mdash; Adam Gordon Bell (@adamgordonbell) &lt;a href="https://twitter.com/adamgordonbell/status/2079203736803549566"&gt;July 20, 2026&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;h2 id="wrapping-up"&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Agent Sandbox makes a kernel-isolated, disposable agent environment a first-class Kubernetes object, and Pulumi is how you stand one up as one program instead of a runbook.&lt;/p&gt;
&lt;p&gt;The full program, everything in this post, deploy to teardown, is at &lt;a href="https://github.com/pulumi/examples/tree/master/gcp-ts-agent-sandbox"&gt;pulumi/examples/gcp-ts-agent-sandbox&lt;/a&gt;. Clone it, point it at a GCP project, and you have your own dedicated agent cluster in about twelve minutes. If you&amp;rsquo;re new to Pulumi, &lt;a href="https://www.pulumi.com/docs/iac/get-started/"&gt;get started here&lt;/a&gt;.&lt;/p&gt;
&lt;a href="https://github.com/pulumi/examples" target="_blank" rel="noopener noreferrer" class="github-card"&gt;
&lt;img
src="https://opengraph.githubassets.com/1/pulumi/examples"
alt="GitHub repository: pulumi/examples"
class="github-card-image"
loading="lazy"
/&gt;
&lt;div class="github-card-content"&gt;
&lt;div class="github-card-domain"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon github-card-icon" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#b-github"/&gt;&lt;/svg&gt;
github.com/pulumi/examples
&lt;/div&gt;
&lt;/div&gt;
&lt;/a&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Warm pools plus snapshot restore are how the managed version keeps this fast at scale: Google&amp;rsquo;s GKE Agent Sandbox launch cites 300 sandboxes per second at sub-second latency. See &lt;a href="https://cloud.google.com/blog/products/containers-kubernetes/bringing-you-agent-sandbox-on-gke-and-agent-substrate"&gt;Bringing you Agent Sandbox on GKE and Agent Substrate&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;The all-in-one manifest is new in &lt;a href="https://github.com/kubernetes-sigs/agent-sandbox/releases/tag/v0.5.2"&gt;v0.5.2&lt;/a&gt; (July 17): earlier releases split the install into &lt;code&gt;manifest.yaml&lt;/code&gt; plus &lt;code&gt;extensions.yaml&lt;/code&gt;, and v0.5.2 renamed the core file — one more reason to pin the version you deploy.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;Verified against the v0.5.2 release manifests: &lt;code&gt;sandbox-with-extensions.yaml&lt;/code&gt; installs no &lt;code&gt;NetworkPolicy&lt;/code&gt; objects, and &lt;code&gt;networkPolicyManagement&lt;/code&gt; defaults to &lt;code&gt;Managed&lt;/code&gt; only for &lt;code&gt;SandboxTemplate&lt;/code&gt;-created sandboxes. A directly-created &lt;code&gt;Sandbox&lt;/code&gt; gets neither, so restricting egress is left to you. See the &lt;a href="https://github.com/kubernetes-sigs/agent-sandbox/releases/tag/v0.5.2"&gt;v0.5.2 release&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>Adam Gordon Bell</author><category>kubernetes</category><category>ai</category><category>gcp</category></item><item><title>Six Live Kubernetes Recommendations: AKS, Cilium, Rate Limiting, and More</title><link>https://www.pulumi.com/blog/getting-started-aks-pulumi-csharp/</link><pubDate>Mon, 15 Jun 2026 12:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/getting-started-aks-pulumi-csharp/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/getting-started-aks-pulumi-csharp/index.png" /&gt;
&lt;p&gt;&lt;em&gt;On June 10th, Engin and I ran a live workshop building an AKS cluster, an Azure Container Registry, and a random-cat web app from scratch in C#. This is the writeup, including the parts we didn&amp;rsquo;t get to live.&lt;/em&gt;&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/-K_lI65Djzs?rel=0?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Live demos keep you honest. On June 10th my AKS workshop went a little sideways. Partway through, Docker Hub rate-limited my image pull and we had to adapt the content on the fly. The original plan was to stand up an AKS cluster with Cilium, an Azure Container Registry with the cluster&amp;rsquo;s pull permission wired in code, and a random-cat web app, then split the infrastructure from the workload into separate Pulumi stacks. Live, we didn&amp;rsquo;t make it through all of that, but we had some fun tangents and it turned out to be a great session. So here are my six recommendations for working with Kubernetes on Azure from this recent workshop.&lt;/p&gt;
&lt;p&gt;&lt;img src="00-overview-what-were-building.png" alt="Overview of what we&amp;rsquo;re building: a developer using Pulumi IaC to provision an AKS cluster that pulls images from Azure Container Registry, exposed through an Azure load balancer"&gt;&lt;/p&gt;
&lt;h2 id="1-pick-the-language-your-team-already-uses"&gt;1. Pick the language your team already uses&lt;/h2&gt;
&lt;p&gt;I opened the session by asking the room what language they prefer to work in, with one caveat from me: &amp;ldquo;Hopefully, it&amp;rsquo;s not PowerShell because this is not in PowerShell.&amp;rdquo; We got some C# answers and zero requests for PowerShell, which works out, because the whole workshop is in &lt;del&gt;PowerShell&lt;/del&gt; C#.&lt;/p&gt;
&lt;p&gt;I chose C# because, first, I genuinely love the language. I spent the early part of my career as a C# developer, and on Azure it just makes sense. Second, an Azure workshop tends to draw a big turnout of C# devs, and if they aren&amp;rsquo;t C# devs themselves, they often work somewhere the backend team uses C#. Pulumi lets you bring that language to your infrastructure rather than learn a new DSL.&lt;/p&gt;
&lt;p&gt;AI coding assistants also came up. LLMs have substantially more C# and Go in their training data than HCL or Bicep. As I put it live: &amp;ldquo;If you&amp;rsquo;re working in HCL, well, there&amp;rsquo;s not that much out there in the training set, but there&amp;rsquo;s lots of C#. There&amp;rsquo;s lots of Go.&amp;rdquo; I co-authored parts of the workshop code with Claude.&lt;/p&gt;
&lt;div class="note note-tip note-large"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-lightbulb-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;p&gt;&lt;strong&gt;Pick the language your team already writes.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Pulumi runs on the language your backend engineers know, and that language has more AI assistant training data behind it too.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="aside-everyones-path-ran-through-docker-swarm"&gt;Aside: everyone&amp;rsquo;s path ran through Docker Swarm&lt;/h2&gt;
&lt;p&gt;We also had some fun historical conversations about orchestrators. I asked Engin: &amp;ldquo;Did you use anything before Kubernetes became popular? Like Mesosphere or any of those other interesting solutions of the heyday?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Engin had walked the same road most of us did.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Engin:&lt;/strong&gt; &amp;ldquo;We used the classical Ansible powered provisioning of VMs&amp;hellip; And then we switched to Consul to have a little bit more control&amp;hellip; And then we had a short intermezzo with Docker Swarm&amp;hellip; So Docker Swarm with Portainer was really goated. And Kubernetes then took over.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;My own history rhymed with his.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Adam:&lt;/strong&gt; &amp;ldquo;I worked at a place where we used a bunch of shell scripts to have like JVM services, like a red green deploy&amp;hellip; And then it was Docker Swarm for a little bit. And then it was Kubernetes.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;No single tool won. Teams picked what fit their existing skills and shifted gradually as the ecosystem matured.&lt;/p&gt;
&lt;h2 id="2-use-cilium-on-aks-if-you-can"&gt;2. Use Cilium on AKS if you can&lt;/h2&gt;
&lt;p&gt;By default, Kubernetes on AKS doesn&amp;rsquo;t turn on Cilium. My strong recommendation is to turn it on. The option is called Azure CNI Powered by Cilium, and it takes one config block:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;NetworkProfile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ACI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContainerServiceNetworkProfileArgs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;NetworkDataplane&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;cilium&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;NetworkPlugin&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;azure&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;NetworkPluginMode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;overlay&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;NetworkPolicy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;cilium&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;PodCidr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;192.168.0.0/16&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This buys you eBPF networking instead of kube-proxy and iptables. &lt;a href="https://ebpf.io/what-is-ebpf/"&gt;eBPF&lt;/a&gt; lets you run sandboxed programs inside the Linux kernel itself, so packet handling and &lt;code&gt;NetworkPolicy&lt;/code&gt; enforcement happen down in the kernel instead of up in userspace. You also get Hubble flow visibility built in, the same technology as GKE&amp;rsquo;s Dataplane V2. As I said live, &amp;ldquo;this is very easy to set up on Azure. You just network data plane, set it to Cilium. Network policy, set it to Cilium.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Self-hosted, Cilium is &lt;code&gt;helm install cilium&lt;/code&gt; with a dozen flags and a lifecycle you own forever. On AKS, it&amp;rsquo;s these five lines and Azure runs it. On a getting-started cluster, I&amp;rsquo;ll take the managed version every time. Owning that lifecycle yourself buys you nothing here.&lt;/p&gt;
&lt;p&gt;Full code: &lt;a href="https://github.com/pulumi/workshops/blob/main/az-getting-started-aks/01-cluster/Program.cs"&gt;&lt;code&gt;01-cluster/Program.cs&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="01-cluster.png" alt="Stage 1: cluster and ACR in place"&gt;&lt;/p&gt;
&lt;div class="note note-tip note-large"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-lightbulb-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;p&gt;&lt;strong&gt;On managed Kubernetes, complexity you&amp;rsquo;d self-host becomes a field you set.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;AKS gives you the managed version of eBPF networking. Pulumi makes the configuration code instead of portal clicks.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="3-debugging-kubernetes-is-easier-when-your-tools-have-the-full-context"&gt;3. Debugging Kubernetes is easier when your tools have the full context&lt;/h2&gt;
&lt;p&gt;The rate limit wasn&amp;rsquo;t even the first failure of the hour. That honor went to Azure, which refused to provision the cluster on the first &lt;code&gt;pulumi up&lt;/code&gt;. I was running in Canada Central, and Pulumi Neo read the error and diagnosed it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Neo:&lt;/strong&gt; &amp;ldquo;Capacity constraints in the selected region. We might need to change our location.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Adam:&lt;/strong&gt; &amp;ldquo;Maybe this Canadian Azure instance is not all it&amp;rsquo;s cracked up to be.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Engin:&lt;/strong&gt; &amp;ldquo;East US is always a good bet.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We switched the region to East US, and the cluster came up. That is the kind of diagnosis a stack-aware assistant is good at, reading the actual provisioning error instead of leaving me to guess.&lt;/p&gt;
&lt;p&gt;Then the image step hit. The workshop&amp;rsquo;s original flow pulled the cat app image into ACR with &lt;code&gt;az acr import --source docker.io/agbell/my-random-cat&lt;/code&gt;, and Docker Hub rate-limited me, even though I was the only person in the room running the code. So how do you blow through an anonymous pull limit all by yourself? The trick is that &lt;code&gt;az acr import&lt;/code&gt; runs server-side. Azure does the pull from Docker Hub, not my laptop, and it goes out over an egress IP shared by who-knows-how-many other Azure tenants. Docker Hub counts anonymous pulls per IP address, so all of that shared traffic lands in one bucket. It flagged me at the worst possible moment: &amp;ldquo;I just got flagged by Docker Hub. That is awesome. That will be a challenge for this demo.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Nothing focuses a workshop like watching your own demo get rate-limited in front of a live audience.&lt;/p&gt;
&lt;p&gt;Live, I asked Neo to route around it: &amp;ldquo;we&amp;rsquo;re using Neo. And Neo is going to switch us out to a nice Hello World project.&amp;rdquo; Neo could see the whole stack, down to the failing image reference, and it swapped the workload over to a standard hello-world image hosted on Azure, so the deploy no longer depended on Docker Hub. A few minutes later: &amp;ldquo;we should have a Hello World container running here. It&amp;rsquo;s way less fun than my image&amp;hellip; just a bunch of random cats. But here we can see that this is working.&amp;rdquo; I won&amp;rsquo;t oversell it. The cat was gone, and we didn&amp;rsquo;t get through every planned stage. But the cluster was up with a workload deployed, and the session kept moving.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s the case for a stack-aware assistant over a chatbot. A general chatbot can tell you that Docker Hub has rate limits and that you should host images closer to home. Neo was working with the actual program state, so the workaround it proposed was already wired to my resources. Engin put it this way: &amp;ldquo;for me, Pulumi Neo, the subcommand is the new Pulumi app.&amp;rdquo; When something breaks mid-demo, the tool that can read your stack is the one that helps.&lt;/p&gt;
&lt;p&gt;The long-term fix was switching to &lt;code&gt;az acr build&lt;/code&gt;, building the image in ACR from source, so the app image never has to come from Docker Hub at all. Every stage in the repo now builds the cat image this way, from source shipped in each stage&amp;rsquo;s &lt;code&gt;app/&lt;/code&gt; folder:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;az acr build --registry &amp;lt;your-acr&amp;gt; --image my-random-cat:latest app
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;No local Docker, and the app image never touches Docker Hub. The only Docker Hub dependency left is the base image (&lt;code&gt;python:3.9-slim&lt;/code&gt;), pulled server-side by ACR&amp;rsquo;s build instead of by an import you re-run every time.&lt;/p&gt;
&lt;p&gt;Full code: &lt;a href="https://github.com/pulumi/workshops/blob/main/az-getting-started-aks/02-app/Program.cs"&gt;&lt;code&gt;02-app/Program.cs&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="02-app.png" alt="Stage 2: cluster, ACR, and cat app running"&gt;&lt;/p&gt;
&lt;div class="note note-tip note-large"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-lightbulb-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;p&gt;&lt;strong&gt;An assistant that can read your stack beats one that can&amp;rsquo;t.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A general chatbot knows Docker Hub has rate limits. Neo could see the deployment and the failing image reference, and that context is what turned advice into a working fix mid-session.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="4-you-dont-have-to-abandon-terraform-to-start"&gt;4. You don&amp;rsquo;t have to abandon Terraform to start&lt;/h2&gt;
&lt;p&gt;One attendee, Martin, had been using Terraform for eight-plus years. His question, put directly: &amp;ldquo;I&amp;rsquo;m using TF for eight plus years. Why should I consider Pulumi? Where is the strength?&amp;hellip; It seems unnecessary to TF for me. At least from what I&amp;rsquo;m seeing now.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Fair question, and the honest answer is you don&amp;rsquo;t have to abandon your modules. Pulumi has &lt;a href="https://www.pulumi.com/docs/iac/guides/building-extending/using-existing-tools/use-terraform-module/"&gt;first-class Terraform module support&lt;/a&gt;, so you can reference existing modules from a Pulumi program. Python ML teams can import infrastructure modules, and platform teams can wrap them in typed abstractions. The portfolio doesn&amp;rsquo;t change. The programs that use it just get more capable.&lt;/p&gt;
&lt;p&gt;The sharper reason is about edges. HCL and Bicep are purpose-built DSLs, and when you hit what they can&amp;rsquo;t express, there&amp;rsquo;s no escape hatch inside the language. With a general-purpose language you can always drop to the SDK or shell out. The edges still exist. They&amp;rsquo;re just further out, and that&amp;rsquo;s the whole difference once a project gets past the basics.&lt;/p&gt;
&lt;div class="note note-tip note-large"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-lightbulb-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;p&gt;&lt;strong&gt;You don&amp;rsquo;t have to rip and replace.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Pulumi can reference existing Terraform modules. Move what makes sense now. The DSL/language tradeoff shows up at the edges, not the basics.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="5-split-slow-infrastructure-from-fast-workloads"&gt;5. Split slow infrastructure from fast workloads&lt;/h2&gt;
&lt;p&gt;Once we had the cluster up and the app running, the single program had grown: cluster, ACR, &lt;code&gt;AcrPull&lt;/code&gt;, image build, deployment, service. Every new workload meant editing the file that also declares the cluster. We had planned this turn in the session in advance. I posed the problem and Engin had the recommendation ready:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Adam:&lt;/strong&gt; &amp;ldquo;What do you think the problem is with my code here, Engin? &amp;hellip; The problem is like every time I want to add a new service to my Kubernetes cluster, I have to go into this file that declares the cluster and all its infrastructure. I think I would prefer to split it.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Engin:&lt;/strong&gt; &amp;ldquo;Create other stacks which are then referencing each other to get the values out. Actually, I highly recommend.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Adam:&lt;/strong&gt; &amp;ldquo;Yes, I totally agree with you, Engin. We should split these out into two separate stacks.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So that&amp;rsquo;s what we did. The infrastructure stack exports the kubeconfig and registry URL. The workload stack references those outputs via &lt;a href="https://www.pulumi.com/docs/iac/concepts/stacks/#stackreferences"&gt;&lt;code&gt;StackReference&lt;/code&gt;&lt;/a&gt;. The slow-moving infra and the fast-moving app become independent deployment units. The infra team and the app team can ship on their own cadences, without fighting over one file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// workload/Program.cs — pulls cluster outputs, never touches cluster resources&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;clusterStack&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;StackReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;clusterStack&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;kubeconfig&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clusterStack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;kubeconfig&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;acrLoginServer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clusterStack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;acrLoginServer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The tricky part is one we didn&amp;rsquo;t cover live, which is how you split a running workload without taking down the running services. The real answer is migrating the stack state, moving resources between stacks instead of destroying and recreating them. As I noted in the session: &amp;ldquo;now we are just removing the service from our project. If this was actual production stuff, I don&amp;rsquo;t think I&amp;rsquo;d be just shutting down the service&amp;hellip; You can actually just make changes to the state rather than tearing them down.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;For the workshop, a simpler move kept the Kubernetes cluster up and the demo moving: keep the project name. Pulumi identifies stacks as &lt;code&gt;org/project/stack&lt;/code&gt;, so if you move the cluster code into &lt;code&gt;aks-cluster/&lt;/code&gt; while keeping the project name &lt;code&gt;kube-kitties&lt;/code&gt;, Pulumi still sees &lt;code&gt;org/kube-kitties/dev&lt;/code&gt;. Same stack, nothing recreated. Rename the project and you get a second cluster.&lt;/p&gt;
&lt;p&gt;In stage four of the demo, I showed how to extend this pattern to work with existing YAML manifests: &lt;a href="https://www.pulumi.com/registry/packages/kubernetes/api-docs/yaml/configgroup/"&gt;&lt;code&gt;ConfigGroup&lt;/code&gt;&lt;/a&gt; drives raw Kubernetes YAML through the Pulumi provider, giving you previews and dependency ordering without rewriting files. One transformation swaps the Docker Hub image reference for the ACR copy, so manifests stay portable and pods pull from your private registry.&lt;/p&gt;
&lt;p&gt;Code: &lt;a href="https://github.com/pulumi/workshops/tree/main/az-getting-started-aks/03-split"&gt;&lt;code&gt;03-split/&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/pulumi/workshops/tree/main/az-getting-started-aks/04-split-yaml"&gt;&lt;code&gt;04-split-yaml/&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="03-split.png" alt="Stage 3: infra and workload as separate stacks, joined by a stack reference"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="04-split-yaml.png" alt="Stage 4: existing YAML manifests driven through Pulumi ConfigGroup"&gt;&lt;/p&gt;
&lt;div class="note note-tip note-large"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-lightbulb-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;p&gt;&lt;strong&gt;Keep the project name when you split, and the cluster doesn&amp;rsquo;t move.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Pulumi identifies stacks as &lt;code&gt;org/project/stack&lt;/code&gt;. Same project name means the same stack. Rename it and you spin up a new cluster.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="rounded-lg bg-violet-50 p-6 my-8"&gt;
&lt;p class="heading-4 m-0 mb-3 flex items-center gap-1.5"&gt;Run Kubernetes on AKS with code&lt;/p&gt;
&lt;div class="body-base m-0 text-gray-950"&gt;Provision your AKS cluster, container registry, and workloads in C#, then split infrastructure from apps into separate stacks so each ships on its own cadence.&lt;/div&gt;
&lt;a href="https://app.pulumi.com/signup" data-track="blog-body-cta" class="btn btn-primary mt-4"&gt;
Get started
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--regular size-4" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-arrow-right-regular"/&gt;&lt;/svg&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id="6-gitops-is-the-way"&gt;6. GitOps is the way&lt;/h2&gt;
&lt;p&gt;The final stage of the workshop is left as homework, and it&amp;rsquo;s a great one to pick up. GitOps is the standard way to run Kubernetes at this point, the One True Way™ depending on who you ask. My position from the call: &amp;ldquo;I think I&amp;rsquo;m on team GitOps.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;The take-home is the &lt;a href="https://github.com/pulumi/workshops/tree/main/az-getting-started-aks/05-gitops"&gt;&lt;code&gt;05-gitops&lt;/code&gt;&lt;/a&gt; folder. We didn&amp;rsquo;t demo it live, but the code is real and verified: Pulumi stands up the cluster, installs Argo CD, and registers the cat as an Argo &lt;code&gt;Application&lt;/code&gt;. From there, changes are &lt;code&gt;git push&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To see Kubernetes and GitOps worked through end to end, watch the video above. Engin also recently ran a &lt;a href="https://github.com/pulumi/workshops/tree/main/getting-started-with-kubernetes-google-cloud"&gt;GCP workshop&lt;/a&gt; with great examples of GitOps with Pulumi, using Flux.&lt;/p&gt;
&lt;div class="note note-tip note-large"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-lightbulb-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;p&gt;&lt;strong&gt;Let Pulumi stand up the cluster, then let GitOps run the apps.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Pulumi installs Argo CD or Flux as part of the infrastructure. From there, workload changes are a &lt;code&gt;git push&lt;/code&gt;. Stage 5 of the workshop repo is a complete Argo CD example.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="try-it-yourself"&gt;Try it yourself&lt;/h2&gt;
&lt;p&gt;That&amp;rsquo;s the whole workshop, six recommendations and two live failures that ended with a cat app running on AKS. To run it yourself, clone the repo. There&amp;rsquo;s a &lt;a href="https://github.com/pulumi/workshops/blob/main/az-getting-started-aks/DEMO.md"&gt;DEMO.md&lt;/a&gt; with every step of this session, and the stage folders mirror the arc above. &lt;code&gt;01-cluster&lt;/code&gt; is the infrastructure checkpoint, &lt;code&gt;02-app&lt;/code&gt; adds the cat app built into your own registry, &lt;code&gt;03-split&lt;/code&gt; and &lt;code&gt;04-split-yaml&lt;/code&gt; separate infrastructure from workload, and &lt;code&gt;05-gitops&lt;/code&gt; is the homework.&lt;/p&gt;
&lt;p&gt;Pick a stage and bring it up:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git clone https://github.com/pulumi/workshops
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; workshops/az-getting-started-aks/02-app
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pulumi up
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl http://&lt;span class="k"&gt;$(&lt;/span&gt;pulumi stack output catServiceIp&lt;span class="k"&gt;)&lt;/span&gt;/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Expect 5 to 8 minutes waiting on cluster create. That&amp;rsquo;s just how long AKS takes. And since each folder is a complete, runnable checkpoint with its own stack, run &lt;code&gt;pulumi destroy&lt;/code&gt; before moving to the next stage, or you&amp;rsquo;ll have several clusters billing at once.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll need an Azure subscription, the Pulumi CLI, and the .NET SDK. Basic Kubernetes familiarity helps. For more on the building blocks, see the &lt;a href="https://www.pulumi.com/registry/packages/azure-native/"&gt;Azure Native provider&lt;/a&gt; and the &lt;a href="https://www.pulumi.com/registry/packages/kubernetes/"&gt;Kubernetes provider&lt;/a&gt; in the Pulumi Registry.&lt;/p&gt;
&lt;p&gt;The full workshop code lives at &lt;a href="https://github.com/pulumi/workshops"&gt;github.com/pulumi/workshops&lt;/a&gt; under &lt;code&gt;az-getting-started-aks&lt;/code&gt;. Check out our upcoming sessions on the &lt;a href="https://www.pulumi.com/events/"&gt;workshop page&lt;/a&gt;, or subscribe to the newsletter to hear what&amp;rsquo;s coming up.&lt;/p&gt;
&lt;p&gt;If there&amp;rsquo;s a seventh recommendation hiding in here, it&amp;rsquo;s the one I learned the hard way: never pull from Docker Hub live again. I&amp;rsquo;ll be keeping my images on a paid cloud registry from now on.&lt;/p&gt;
&lt;p&gt;Engin and I will be doing more of these, and we&amp;rsquo;d love to see you at the next one.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Parts of the workshop code were authored by Claude and Pulumi Neo, and parts of this writeup got help from both as well.&lt;/em&gt;&lt;/p&gt;</description><author>Adam Gordon Bell</author><category>kubernetes</category><category>azure</category><category>dotnet</category></item><item><title>Seven Rules for Building an AI-Native Software Factory</title><link>https://www.pulumi.com/blog/seven-rules-ai-native-software-factory/</link><pubDate>Thu, 21 May 2026 06:00:00 -0700</pubDate><guid>https://www.pulumi.com/blog/seven-rules-ai-native-software-factory/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/seven-rules-ai-native-software-factory/index.png" /&gt;
&lt;p&gt;&lt;em&gt;Ewan Dawson is CTO of &lt;a href="https://compostable.ai/"&gt;Compostable AI&lt;/a&gt;, where five engineers run an AI-native software factory: nineteen clients, custom AWS deployments, most of them shipped within a day of contract signing. This article is adapted from his recent Pulumi webinar, and covers rules in more depth than we had time for on stage.&lt;/em&gt;&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/oHNdlWlsR-w?rel=0?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;For the past twenty years, I&amp;rsquo;ve viewed software development as a craft. The best engineers drew on decades of experience to get every function right.&lt;/p&gt;
&lt;p&gt;But two years into the agentic AI revolution, I realised software is going to look more like a factory than a craft. The economics have changed. We can&amp;rsquo;t treat code as bespoke anymore. To scale, we have to think industrial — use the tools to ship more value with fewer engineers.&lt;/p&gt;
&lt;p&gt;I joined Compostable AI soon after it was founded 2.5 years ago, and I built the engineering org AI-native from day one. The technology has come a long way since then, and so has my understanding of what AI-native actually means. Here are seven rules I keep coming back to.&lt;/p&gt;
&lt;div class="note note-info note-large"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;strong&gt;An AI software factory&lt;/strong&gt; is a software operation where autonomous agents write and ship most of the code. The engineers stop writing it by hand and spend their time deciding what gets built and talking to customers. The rules below are our rules for building and running an AI-native software factory.&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="1-transform-dont-enhance"&gt;1. Transform, don&amp;rsquo;t enhance&lt;/h2&gt;
&lt;img src="rule-1-chrysalis.png" alt="" class="float-right w-32 ml-4 mb-2 mt-1 rounded-lg sm:w-40 sm:ml-6 lg:w-44"&gt;
&lt;p&gt;Going AI-native isn&amp;rsquo;t an upgrade to your existing process. If you treat AI as a way to hand your developers smarter tools, you leave most of the value on the table. You get the leverage by rebuilding how you write software — and the culture and processes around it.&lt;/p&gt;
&lt;p&gt;I know that&amp;rsquo;s a tall order for a large, mature engineering org. My advice: start small. Pick one team or one business area and run it as a fully AI-native function. Take what you learn and roll it out from there. And do the political work early, especially with your Governance, Risk, and Compliance function. Get GRC on your side early. Otherwise AI becomes a compliance fight instead of a structural advantage.&lt;/p&gt;
&lt;div class="note note-tip note-large"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-lightbulb-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;p&gt;&lt;strong&gt;Don&amp;rsquo;t bolt AI onto your existing workflow. Redesign the workflow around what agents can do.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Most of the leverage in this technology comes from rebuilding around it. The tool change is the small part.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="2-remove-the-problem-dont-solve-it"&gt;2. Remove the problem, don&amp;rsquo;t solve it&lt;/h2&gt;
&lt;img src="rule-2-knot.png" alt="" class="float-left w-32 mr-4 mb-2 mt-1 rounded-lg sm:w-40 sm:mr-6 lg:w-44"&gt;
&lt;p&gt;Going AI-native flips which problems are hard and which are easy. The right move often isn&amp;rsquo;t to engineer a solution. It&amp;rsquo;s to reframe the problem so it goes away.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s an example. Serving multiple clients with agents writing the code, blast radius wasn&amp;rsquo;t a hypothetical. One bad agent run could trash a customer&amp;rsquo;s database, or leak one client&amp;rsquo;s data into another&amp;rsquo;s. Our instinct was to build a secure multi-tenant sandbox with guardrails, approvals, rollback. But every version we tried still had agents loose in a shared environment, one bug away from making one customer&amp;rsquo;s data visible to another&amp;rsquo;s. So we removed the problem: every client gets two dedicated AWS accounts, one for production and one &amp;ldquo;digital twin&amp;rdquo; staging account. Agents iterate on staging until the work checks out. Only then does it ship to production. We have nineteen accounts now, one per client.&lt;/p&gt;
&lt;p&gt;Managing nineteen AWS accounts with five engineers used to be an administrative nightmare. When code is cheap, infrastructure-as-code tools like AWS Control Tower and Pulumi make it the easier path.&lt;/p&gt;
&lt;div class="note note-tip note-large"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-lightbulb-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;p&gt;&lt;strong&gt;Remove the problem before you try to solve it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s cheaper to reframe the problem than to engineer your way through it.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="3-pick-tools-your-agents-can-drive"&gt;3. Pick tools your agents can drive&lt;/h2&gt;
&lt;img src="rule-3-wrench.png" alt="" class="float-right w-32 ml-4 mb-2 mt-1 rounded-lg sm:w-40 sm:ml-6 lg:w-44"&gt;
&lt;p&gt;Removing problems is the process side. The other side is tooling. If you want an automated factory, your tech stack has to be something agents can drive. This overlaps a lot with tools that have great developer experience. If a tool has a robust API plus a clean CLI, agents can drive it. If it&amp;rsquo;s heavy click-ops around a web UI, agents stop there.&lt;/p&gt;
&lt;p&gt;We didn&amp;rsquo;t get there first try. Our first IaC tool worked fine when we had a couple of clients. As we added more, accounts drifted, deployments slowed, retries got complicated. We needed something built for where we were heading.&lt;/p&gt;
&lt;p&gt;I went looking, and &lt;a href="https://www.pulumi.com/"&gt;Pulumi&lt;/a&gt; fit. We express infrastructure as type-safe code — TypeScript, in our case, rather than HCL — and agents are good at writing it. Pair that with &lt;a href="https://www.pulumi.com/product/neo/"&gt;Pulumi Neo&lt;/a&gt; — pre-loaded with domain-specific Pulumi skills — and we ship infrastructure that follows best practices. One of my colleagues put it: &amp;ldquo;The scary thing about Neo is it just seems to know everything about what we do.&amp;rdquo; Pulumi IaC plus &lt;a href="https://www.pulumi.com/docs/pulumi-cloud/esc/"&gt;Pulumi ESC&lt;/a&gt; for configuration beats stitching tools together. And TypeScript lets us build higher-level abstractions that keep the AWS account fleet tractable.&lt;/p&gt;
&lt;blockquote class="pullquote"&gt;
&lt;p&gt;&amp;ldquo;I don&amp;rsquo;t actually care if it&amp;rsquo;s HCL or TypeScript, as long as my software development agents can write it. And they do a better job with TypeScript than HCL.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="note note-tip note-large"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-lightbulb-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;p&gt;&lt;strong&gt;Tools have to share your AI-native mindset. If they don&amp;rsquo;t integrate deeply, the human becomes the glue.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If part of your stack still requires a human to click through a web UI to provision an account, your agents stop there.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="4-dont-let-one-agent-do-everything"&gt;4. Don&amp;rsquo;t let one agent do everything&lt;/h2&gt;
&lt;img src="rule-4-constellation.png" alt="" class="float-left w-32 mr-4 mb-2 mt-1 rounded-lg sm:w-40 sm:mr-6 lg:w-44"&gt;
&lt;p&gt;When I first started with agents, I reached for a god prompt: one massive system prompt meant to guide a single agent through the whole software lifecycle. It didn&amp;rsquo;t work. Agents struggle when you give them multiple goals. The writer is lenient on its own work — it won&amp;rsquo;t catch what it just shipped. You don&amp;rsquo;t want it reviewing the code, checking for security flaws, or hunting bugs.&lt;/p&gt;
&lt;p&gt;We get better results from a constellation of specialized agents, each handling one part of the line. Pulumi Neo handles infrastructure. Alongside it sit agents specialized in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Code implementation&lt;/li&gt;
&lt;li&gt;Code review and testing&lt;/li&gt;
&lt;li&gt;Security auditing&lt;/li&gt;
&lt;li&gt;Internal standards compliance&lt;/li&gt;
&lt;li&gt;Documentation updates&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tasks pass down the line. Clean code comes out the other end, with almost no human involved.&lt;/p&gt;
&lt;div class="note note-tip note-large"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-lightbulb-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;p&gt;&lt;strong&gt;Don&amp;rsquo;t let any agent mark its own homework. Specialize by job.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Treat agents the way you&amp;rsquo;d treat a team. The one who writes the code shouldn&amp;rsquo;t be the one signing it off.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="5-measure-human-hours-per-unit-of-value"&gt;5. Measure human hours per unit of value&lt;/h2&gt;
&lt;img src="rule-5-hourglass.png" alt="" class="float-right w-32 ml-4 mb-2 mt-1 rounded-lg sm:w-40 sm:ml-6 lg:w-44"&gt;
&lt;p&gt;Once we had agents writing and agents reviewing, throughput went up — but the bottleneck moved past the PR. Engineering hours were still the most expensive thing in the building, so my core metric is human hours per unit of value produced. Minimize that.&lt;/p&gt;
&lt;p&gt;That means hunting for every step that still goes through a person — especially the mid-pipeline steps between ideation and production. Automate the human touchpoints along that line, and the factory runs 24/7.&lt;/p&gt;
&lt;p&gt;Pushing automation this hard also forces good engineering. A chaotic, undocumented process is impossible to automate. Good engineering is still good engineering, AI or not. Agents won&amp;rsquo;t fix a weak process.&lt;/p&gt;
&lt;div class="note note-tip note-large"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-lightbulb-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;p&gt;&lt;strong&gt;Measure human hours per unit of value. Treat every one as a bottleneck to remove.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You can&amp;rsquo;t automate what you can&amp;rsquo;t describe. Every human in the pipeline marks a piece that hasn&amp;rsquo;t been described yet.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="6-design-for-convergence-not-one-shot-correctness"&gt;6. Design for convergence, not one-shot correctness&lt;/h2&gt;
&lt;img src="rule-6-spiral.png" alt="" class="float-left w-32 mr-4 mb-2 mt-1 rounded-lg sm:w-40 sm:mr-6 lg:w-44"&gt;
&lt;p&gt;Even with the human touchpoints removed, the agents don&amp;rsquo;t ship right the first try. Once you embrace the factory pipeline, you stop needing them to. We design for convergence instead — a system that lands on the right answer through automated iteration.&lt;/p&gt;
&lt;p&gt;The loop we run looks like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Refinement:&lt;/strong&gt; agents iterate on the Product Requirements Document until the problem is clear.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Planning:&lt;/strong&gt; agents draft multiple technical approaches, and evaluation agents pick the best one.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Implementation:&lt;/strong&gt; coding agents write the software.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Review:&lt;/strong&gt; specialized checking agents look for bugs, API misuse, and security flaws.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If the checkers find a problem, they hand it back to the implementation agent. The loop repeats until the tests pass and the agents agree on a clean PR. Once it converges, we merge and deploy to staging.&lt;/p&gt;
&lt;p&gt;Two things have to be true. You need a way to evaluate the output. Without that, you don&amp;rsquo;t know when to stop. And the loop has to converge — each pass has to get closer. A checker that fails every PR for a different reason isn&amp;rsquo;t helping — it just keeps the work going in circles. The feedback has to narrow the search, not widen it.&lt;/p&gt;
&lt;p&gt;Once it converges, the question moves on. How cheap can we make it? Lower the time to PR, reduce token count, drop the overall cost. The optimization never really ends.&lt;/p&gt;
&lt;div class="note note-tip note-large"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-lightbulb-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;p&gt;&lt;strong&gt;Don&amp;rsquo;t aim for one-shot correctness. Design for convergence.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It doesn&amp;rsquo;t matter how many tries it takes, as long as the loop closes without a human in it. Get convergence first. The optimization comes after.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="7-run-the-factory-in-the-cloud-not-on-a-laptop"&gt;7. Run the factory in the cloud, not on a laptop&lt;/h2&gt;
&lt;img src="rule-7-cloud.png" alt="" class="float-right w-32 ml-4 mb-2 mt-1 rounded-lg sm:w-40 sm:ml-6 lg:w-44"&gt;
&lt;p&gt;Even a converged factory has to live somewhere. Try running a fully automated factory on individual developers&amp;rsquo; laptops, and it falls apart. Laptops are highly trusted machines. Put autonomous agents on them and your security posture drops, fast. And the factory has to run 24/7. Events come from elsewhere — PR comments, Slack threads, errors in test environments.&lt;/p&gt;
&lt;p&gt;Cloud also kills configuration drift across a dozen developer machines. The same prompts run against different model versions, and env vars sit half-set on half the laptops. The thing you&amp;rsquo;re trying to optimize lives in different states across the team. Cloud isn&amp;rsquo;t just where the factory runs; it&amp;rsquo;s the only place a team can iterate on it together. Keep everything in one place — AWS, Pulumi Cloud, GitHub. The specific stack matters less than the principle of one place.&lt;/p&gt;
&lt;p&gt;And the part that matters most: the factory keeps running, testing, and deploying long after we&amp;rsquo;ve closed our laptops and gone to sleep — the &lt;a href="https://www.pulumi.com/blog/dark-factory-pattern-pulumi-autonomous-iac/"&gt;dark factory&lt;/a&gt; pattern, where the line keeps producing with the lights off.&lt;/p&gt;
&lt;div class="note note-tip note-large"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-lightbulb-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;p&gt;&lt;strong&gt;Build the factory somewhere you can work on it — not just somewhere it can run.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A factory scattered across laptops can&amp;rsquo;t be improved as a system. Cloud keeps it in one shape, 24/7, and lets the team iterate together.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="how-to-build-an-ai-native-software-factory"&gt;How to build an AI-native software factory&lt;/h2&gt;
&lt;p&gt;There&amp;rsquo;s no single blueprint for how to build a software factory, but these seven rules are the software factory model we run at Compostable AI: transform the workflow rather than bolt AI onto it, remove problems instead of engineering around them, pick tools your agents can drive, split work across specialized agents, measure human hours per unit of value, design for convergence over one-shot correctness, and run the factory in the cloud so it operates 24/7.&lt;/p&gt;
&lt;h2 id="closing-thought"&gt;Closing thought&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve shipped more code in the last two years than I did in the fifteen before that. Most of it in languages I couldn&amp;rsquo;t write by hand. And that&amp;rsquo;s after a stretch in leadership where I wrote almost none.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re where I was two years ago: don&amp;rsquo;t ask how AI fits into what you already do. The factory is built one rule at a time, and it&amp;rsquo;s not a template — it&amp;rsquo;s the practice of finding where you&amp;rsquo;re taking advantage of the new economics and where you&amp;rsquo;re not, where your practices still need an update. The leverage is in finding these places and improving them.&lt;/p&gt;
&lt;h2 id="frequently-asked-questions"&gt;Frequently asked questions&lt;/h2&gt;
&lt;p&gt;&lt;details&gt;
&lt;summary&gt;What is a software factory?&lt;/summary&gt;
A software factory runs software delivery as a repeatable industrial process instead of hand-crafting each feature, the way a physical factory turns out products on a line. The idea predates AI. An AI-native software factory is the version where agents do most of the building and the engineers steer.
&lt;/details&gt;
&lt;details&gt;
&lt;summary&gt;How is a software factory different from traditional software development?&lt;/summary&gt;
Traditional development treats code as a craft, each function written and reviewed by hand. A software factory treats software delivery as an industrial process: agents handle the writing and checking through an automated, converging loop, and the expensive human hours move to defining the work and deciding what to build. The goal is to minimize human hours per unit of value shipped.
&lt;/details&gt;&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;How do you keep AI agents from breaking things across customers?&lt;/summary&gt;
Rather than build a shared multi-tenant sandbox, Compostable AI removes the problem: every client gets two dedicated AWS accounts, one for production and a &amp;ldquo;digital twin&amp;rdquo; staging account. Agents iterate on staging until the work checks out, and only then does it ship to production. Infrastructure-as-code tools like AWS Control Tower and Pulumi make running that account fleet tractable for a small team.
&lt;/details&gt;
&lt;details&gt;
&lt;summary&gt;What tools does an AI-native software factory need?&lt;/summary&gt;
Tools your agents can actually drive, anything with a solid API and a clean CLI, rather than click-ops around a web UI. Compostable AI expresses infrastructure as type-safe TypeScript with &lt;a href="https://www.pulumi.com/"&gt;Pulumi&lt;/a&gt;, pairs it with &lt;a href="https://www.pulumi.com/product/neo/"&gt;Pulumi Neo&lt;/a&gt; for domain-specific infrastructure skills, and uses &lt;a href="https://www.pulumi.com/docs/pulumi-cloud/esc/"&gt;Pulumi ESC&lt;/a&gt; for configuration. If part of your stack still requires a human to click through a UI, your agents stop there.
&lt;/details&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Watch the &lt;a href="https://www.youtube.com/watch?v=oHNdlWlsR-w"&gt;original Pulumi webinar&lt;/a&gt;. Read the &lt;a href="https://www.pulumi.com/case-studies/compostable-ai/"&gt;Compostable AI case study&lt;/a&gt;, and learn more about &lt;a href="https://compostable.ai/"&gt;Compostable AI&lt;/a&gt; and &lt;a href="https://www.pulumi.com/product/neo/"&gt;Pulumi Neo&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</description><author>Ewan Dawson</author><author>Adam Gordon Bell</author><category>ai</category><category>ai-agents</category><category>infrastructure-as-code</category><category>pulumi-neo</category><category>esc</category></item><item><title>Ten More Things You Can Do With Pulumi Neo</title><link>https://www.pulumi.com/blog/10-more-things-you-can-do-with-neo/</link><pubDate>Tue, 19 May 2026 05:00:00 -0700</pubDate><guid>https://www.pulumi.com/blog/10-more-things-you-can-do-with-neo/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/10-more-things-you-can-do-with-neo/index.png" /&gt;
&lt;p&gt;Last fall, after launching &lt;a href="https://www.pulumi.com/neo/"&gt;Pulumi Neo&lt;/a&gt;, we wrote up &lt;a href="https://www.pulumi.com/blog/10-things-you-can-do-with-neo/"&gt;10 things you could do with it&lt;/a&gt;. In the months that followed, as platform teams handed Neo more real work, we watched and listened, shipping a steady stream of features like &lt;a href="https://www.pulumi.com/blog/neo-plan-mode/"&gt;plan mode&lt;/a&gt;, &lt;a href="https://www.pulumi.com/blog/neo-read-only-mode/"&gt;read-only mode&lt;/a&gt;, &lt;a href="https://www.pulumi.com/blog/pulumi-neo-now-supports-agentsmd/"&gt;AGENTS.md&lt;/a&gt;, an &lt;a href="https://www.pulumi.com/blog/neo-integration-catalog/"&gt;integration catalog&lt;/a&gt;, &lt;a href="https://www.pulumi.com/blog/neo-migration/"&gt;cross-cloud migration&lt;/a&gt;, and &lt;a href="https://www.pulumi.com/blog/neo-task-sharing/"&gt;task sharing&lt;/a&gt;. With &lt;a href="https://www.pulumi.com/releases/agentic-infrastructure-era/"&gt;today&amp;rsquo;s release&lt;/a&gt;, Neo extends beyond the Pulumi Cloud console into the Pulumi CLI, GitHub, and Slack.&lt;/p&gt;
&lt;p&gt;So here are &lt;strong&gt;10 more&lt;/strong&gt; things you can do with Neo.&lt;/p&gt;
&lt;h2 id="1-deploy-your-app-to-aws-without-writing-iac"&gt;1. Deploy your app to AWS without writing IaC&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Hand Neo a repo. Neo picks the right services — ECS, AWS Fargate, ALB — writes the Pulumi, and opens a PR.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The cloud infrastructure part of getting a new service running, especially one in a new language, is always a few hours of boilerplate: a VPC and subnets, an IAM role, security groups, a load balancer, DNS, and a TLS cert.&lt;/p&gt;
&lt;p&gt;With Neo, that work collapses into a prompt. Point Neo at a repo and ask:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Deploy this app to AWS as a publicly accessible service.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://www.pulumi.com/blog/neo-plan-mode/"&gt;Plan mode&lt;/a&gt; comes back with the resources Neo will create, named and sized: ECS running on AWS Fargate, an ALB, and the VPC wiring. Approve, and Neo writes the Pulumi program, runs a preview, and opens a PR. You, the human in the loop, merge it after review.&lt;/p&gt;
&lt;p&gt;
&lt;div class="my-4"&gt;
&lt;video class="flex outline-none rounded-lg w-full" title="Neo deploying an app to AWS: prompt, plan mode, PR, public URL"
autoplay muted playsinline
loop &gt;
&lt;source src="deploy-to-aws2.mp4" /&gt;
&lt;/video&gt;
&lt;/div&gt;
&lt;figcaption&gt;
&lt;center&gt;
&lt;i&gt;
Neo planning a PR and deploying an app to AWS.
&lt;/i&gt;
&lt;/center&gt;
&lt;/figcaption&gt;
&lt;/p&gt;
&lt;a href="https://app.pulumi.com/neo?prompt=I%27d&amp;#43;like&amp;#43;to&amp;#43;deploy&amp;#43;this&amp;#43;app&amp;#43;to&amp;#43;AWS.&amp;#43;Confirm&amp;#43;what&amp;#43;you%27ll&amp;#43;create." class="neo-card"&gt;
&lt;div class="neo-card-content"&gt;
&lt;div class="neo-card-icon"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--regular" fill="currentColor" role="img" aria-label="Pulumi Neo AI icon"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#c-pulumi-neo-regular"/&gt;&lt;/svg&gt;
&lt;/div&gt;
&lt;div class="neo-card-text"&gt;
&lt;span class="neo-card-subtitle"&gt;Start a Neo task&lt;/span&gt;
&lt;span class="neo-card-title"&gt;Ask Neo to deploy your app to AWS and make a PR&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="neo-card-arrow"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--bold" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-caret-right-bold"/&gt;&lt;/svg&gt;
&lt;/div&gt;
&lt;/a&gt;
&lt;h2 id="2-diagnose-a-slow-api-from-metrics-logs-and-code"&gt;2. Diagnose a slow API from metrics, logs, and code&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Slow endpoints live at the seam between runtime metrics and the stack that runs them. Neo reads both and proposes a fix with the metric evidence as the rationale.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Production incidents often involve multiple tools. When the &lt;code&gt;checkout&lt;/code&gt; endpoint&amp;rsquo;s p95 latency climbs from 200ms to 1.2s, the metric is in Datadog, but the cause might be somewhere in your AWS account: maybe RDS is out of IOPS, maybe the connection pool is too small, maybe the autoscaler isn&amp;rsquo;t keeping up. Connecting &amp;ldquo;this metric looks bad&amp;rdquo; to a recent backend change and then to a one-line fix in your Pulumi program is an exercise in detective work.&lt;/p&gt;
&lt;p&gt;Neo&amp;rsquo;s &lt;a href="https://www.pulumi.com/blog/neo-integration-catalog/"&gt;integration catalog&lt;/a&gt; bridges this gap. With built-in Datadog APM, PagerDuty, and Honeycomb integrations sitting alongside your Pulumi state, Neo can read traces and metrics from the tools your team already uses and take action.&lt;/p&gt;
&lt;p&gt;Ask Neo:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Find the scaling bottleneck on &lt;code&gt;/checkout&lt;/code&gt; from the last 7 days of metrics and propose a fix.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Neo pulls the metric history, matches the Datadog tag &lt;code&gt;db.cluster=checkout-rds&lt;/code&gt; to the RDS instance in your &lt;code&gt;prod-checkout&lt;/code&gt; Pulumi stack, and opens a PR with a Pulumi diff that bumps the storage IOPS and raises the connection-pool ceiling. You review and roll out the fix.&lt;/p&gt;
&lt;p&gt;
&lt;div class="my-4"&gt;
&lt;video class="flex outline-none rounded-lg w-full" title="Enabling the Honeycomb integration in Neo"
autoplay muted playsinline
loop &gt;
&lt;source src="honey-comb.mp4" /&gt;
&lt;/video&gt;
&lt;/div&gt;
&lt;figcaption&gt;
&lt;center&gt;
&lt;i&gt;
Toggle on the Honeycomb integration so Neo can read traces and metrics alongside your Pulumi stacks.
&lt;/i&gt;
&lt;/center&gt;
&lt;/figcaption&gt;
&lt;/p&gt;
&lt;h2 id="3-automate-pagerduty-incident-response-from-slack"&gt;3. Automate PagerDuty incident response from Slack&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;A page comes in. You paste it into your on-call channel and tag Neo, and Neo replies with the cross-system view you&amp;rsquo;d otherwise spend the first 20 minutes assembling.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;On-call triage is often about getting up to speed quickly. You get paged because something is in the red, and you don&amp;rsquo;t know why.&lt;/p&gt;
&lt;p&gt;You mention Neo in the on-call Slack channel:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;@neo, what&amp;rsquo;s going on with this alert?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Neo starts querying metrics and traces. With PagerDuty and Datadog in the integration catalog, it correlates the alert with every deploy and stack change tagged with the alert&amp;rsquo;s service in the last hour, and finds the change that lines up:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Two deploys in the last hour touched services tagged &lt;code&gt;service:checkout&lt;/code&gt;: &lt;code&gt;checkout-api@a3f9c2&lt;/code&gt; (12 min ago, app-layer deploy) and Pulumi stack &lt;code&gt;prod-checkout-rds&lt;/code&gt; (45 min ago, decreased &lt;code&gt;max_connections&lt;/code&gt; from 200 → 100). p99 latency inflection at 14:03 lines up with the stack change. Likely cause: the connection-pool reduction is starving the API under current load.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You ask a couple of clarifying questions in-thread, then ask Neo to open a rollback PR against the Pulumi stack.&lt;/p&gt;
&lt;p&gt;&lt;img src="neo-integration-catalog.png" alt="Neo Settings → Integrations page: six integrations available with Authorize buttons for Atlassian, Datadog, Honeycomb, Linear, PagerDuty, and Supabase"&gt;
&lt;figcaption&gt;
&lt;center&gt;
&lt;i&gt;
Authorize PagerDuty and Datadog in Neo's settings. Neo can then read alerts in your on-call Slack channel, find the change that correlates, and open a PR when you ask.
&lt;/i&gt;
&lt;/center&gt;
&lt;/figcaption&gt;
&lt;/p&gt;
&lt;h2 id="4-implement-a-jira-or-linear-ticket-end-to-end"&gt;4. Implement a Jira or Linear ticket end-to-end&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Hand Neo a ticket number from Linear, Jira, or GitHub Issues. Neo reads the description and acceptance criteria, plans against your stack, and opens a PR.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Tickets often pile up not because they&amp;rsquo;re unimportant, but because they&amp;rsquo;re not urgent. Ongoing maintenance quietly accumulates. Bumping a provider version, centralizing secret management, working through small policy violations: each one matters, but none of them ever moves to the top of the queue. Explaining each one to an agent is its own overhead.&lt;/p&gt;
&lt;p&gt;The fix is letting Neo read the ticket itself. Connect Linear integration or Jira automation through the integration catalog (GitHub Issues works too), and Neo pulls the ticket the same way an engineer would: title, description, acceptance criteria.&lt;/p&gt;
&lt;p&gt;Ask Neo:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Implement CAD-1234 in our payments stack.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Neo reads the ticket, plans against your existing stack, opens a PR, and drops a comment back on the ticket. The ticket and the PR end up linked, and your backlog shrinks.&lt;/p&gt;
&lt;p&gt;
&lt;div class="my-4"&gt;
&lt;video class="flex outline-none rounded-lg w-full" title="Neo running locally in the Pulumi CLI: fielding a Linear issue, analyzing the codebase, and producing a PR that upgrades multiple projects to the latest Pulumi and AWS provider versions"
autoplay muted playsinline
loop &gt;
&lt;source src="neo-linear.mp4" /&gt;
&lt;/video&gt;
&lt;/div&gt;
&lt;figcaption&gt;
&lt;center&gt;
&lt;i&gt;
Neo running locally in the Pulumi CLI: fielding a Linear issue, analyzing the codebase, and producing a PR that upgrades multiple projects to the latest Pulumi and AWS provider versions.
&lt;/i&gt;
&lt;/center&gt;
&lt;/figcaption&gt;
&lt;/p&gt;
&lt;a href="https://app.pulumi.com/neo?prompt=I%27d&amp;#43;like&amp;#43;to&amp;#43;implement&amp;#43;a&amp;#43;ticket&amp;#43;from&amp;#43;Linear&amp;#43;%28or&amp;#43;Jira%2C&amp;#43;or&amp;#43;GitHub&amp;#43;Issues%29.&amp;#43;Ask&amp;#43;me&amp;#43;for&amp;#43;the&amp;#43;ticket&amp;#43;number." class="neo-card"&gt;
&lt;div class="neo-card-content"&gt;
&lt;div class="neo-card-icon"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--regular" fill="currentColor" role="img" aria-label="Pulumi Neo AI icon"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#c-pulumi-neo-regular"/&gt;&lt;/svg&gt;
&lt;/div&gt;
&lt;div class="neo-card-text"&gt;
&lt;span class="neo-card-subtitle"&gt;Start a Neo task&lt;/span&gt;
&lt;span class="neo-card-title"&gt;Implement a Linear ticket end-to-end&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="neo-card-arrow"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--bold" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-caret-right-bold"/&gt;&lt;/svg&gt;
&lt;/div&gt;
&lt;/a&gt;
&lt;h2 id="5-audit-and-tighten-over-privileged-iam-roles"&gt;5. Audit and tighten over-privileged IAM roles&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Neo audits each role against what your stack code actually does, and proposes scoped policies that improve your security posture.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;IAM cleanup is the kind of work nobody has the time to prioritize. Production has 40 roles. Half of them started with &lt;code&gt;s3:*&lt;/code&gt; because nobody had time to scope them, and the cleanup slips quarter to quarter.&lt;/p&gt;
&lt;p&gt;Ask Neo:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Audit IAM permissions across my accounts and propose narrower policies for over-privileged stack-managed roles.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Neo cross-references each role&amp;rsquo;s policy against what the stack code actually calls, and opens a PR per role. The PR body lists the API calls Neo found in the stack code, like &lt;code&gt;s3:GetObject&lt;/code&gt; on &lt;code&gt;audit-logs-*&lt;/code&gt; and &lt;code&gt;s3:PutObject&lt;/code&gt; on &lt;code&gt;audit-logs-staging&lt;/code&gt;, as the justification for the scoped policy. The evidence sits next to the diff.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re unclear about which roles count as in-scope or what your team considers over-privileged, start in &lt;a href="https://www.pulumi.com/blog/neo-plan-mode/"&gt;plan mode&lt;/a&gt; and agree on that with Neo first.&lt;/p&gt;
&lt;p&gt;
&lt;div class="my-4"&gt;
&lt;video class="flex outline-none rounded-lg w-full" title="Neo auditing IAM and proposing narrower policies"
autoplay muted playsinline
loop &gt;
&lt;source src="iam-narrow.mp4" /&gt;
&lt;/video&gt;
&lt;/div&gt;
&lt;figcaption&gt;
&lt;center&gt;
&lt;i&gt;
Neo auditing an over-privileged IAM role and proposing a narrower policy, with the actually-used permissions as evidence.
&lt;/i&gt;
&lt;/center&gt;
&lt;/figcaption&gt;
&lt;/p&gt;
&lt;a href="https://app.pulumi.com/neo?prompt=Audit&amp;#43;IAM&amp;#43;permissions&amp;#43;across&amp;#43;my&amp;#43;accounts&amp;#43;and&amp;#43;propose&amp;#43;narrower&amp;#43;policies&amp;#43;for&amp;#43;over-privileged&amp;#43;stack-managed&amp;#43;roles." class="neo-card"&gt;
&lt;div class="neo-card-content"&gt;
&lt;div class="neo-card-icon"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--regular" fill="currentColor" role="img" aria-label="Pulumi Neo AI icon"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#c-pulumi-neo-regular"/&gt;&lt;/svg&gt;
&lt;/div&gt;
&lt;div class="neo-card-text"&gt;
&lt;span class="neo-card-subtitle"&gt;Start a Neo task&lt;/span&gt;
&lt;span class="neo-card-title"&gt;Audit IAM and tighten over-privileged roles&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="neo-card-arrow"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--bold" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-caret-right-bold"/&gt;&lt;/svg&gt;
&lt;/div&gt;
&lt;/a&gt;
&lt;h2 id="6-migrate-from-aws-cdk-onto-your-platforms-golden-paths"&gt;6. Migrate from AWS CDK onto your platform&amp;rsquo;s golden paths&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Neo reads your existing AWS CDK app and lands a PR that swaps AWS&amp;rsquo;s defaults for your team&amp;rsquo;s published components.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;CDK&amp;rsquo;s L2 constructs encode AWS&amp;rsquo;s defaults. &lt;code&gt;s3.Bucket&lt;/code&gt; with &lt;code&gt;encryption: BucketEncryption.S3_MANAGED&lt;/code&gt; is a sane choice, but it&amp;rsquo;s AWS&amp;rsquo;s idea of sane, not yours. A platform team that&amp;rsquo;s published its own components to the &lt;a href="https://www.pulumi.com/docs/idp/concepts/private-registry/"&gt;Pulumi Private Registry&lt;/a&gt; has already decided what &lt;em&gt;your&lt;/em&gt; bucket defaults look like: encryption with the right KMS key, tagging by cost center.&lt;/p&gt;
&lt;p&gt;Ask Neo:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Migrate the &lt;code&gt;payments-vpc&lt;/code&gt; CDK stack to Pulumi using our published components.&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Neo reads the source CDK app and your registry side by side. It maps each CDK construct to its closest team-published equivalent, clarifying with you where the mapping is ambiguous.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Before (AWS CDK, AWS&amp;#39;s defaults)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Assets&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;bucketName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;payments-assets&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;encryption&lt;/span&gt;: &lt;span class="kt"&gt;s3.BucketEncryption.S3_MANAGED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;versioned&lt;/span&gt;: &lt;span class="kt"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// After (Pulumi, your team&amp;#39;s published component)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;platform&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@payments/platform&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;assets&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;bucketName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;payments-assets&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;classification&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;internal&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;a href="https://app.pulumi.com/neo?prompt=I%27d&amp;#43;like&amp;#43;to&amp;#43;migrate&amp;#43;this&amp;#43;CDK&amp;#43;stack&amp;#43;to&amp;#43;Pulumi.&amp;#43;Use&amp;#43;our&amp;#43;published&amp;#43;components&amp;#43;where&amp;#43;you&amp;#43;can." class="neo-card"&gt;
&lt;div class="neo-card-content"&gt;
&lt;div class="neo-card-icon"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--regular" fill="currentColor" role="img" aria-label="Pulumi Neo AI icon"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#c-pulumi-neo-regular"/&gt;&lt;/svg&gt;
&lt;/div&gt;
&lt;div class="neo-card-text"&gt;
&lt;span class="neo-card-subtitle"&gt;Start a Neo task&lt;/span&gt;
&lt;span class="neo-card-title"&gt;Migrate CDK onto your golden paths&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="neo-card-arrow"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--bold" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-caret-right-bold"/&gt;&lt;/svg&gt;
&lt;/div&gt;
&lt;/a&gt;
&lt;p&gt;Neo handles &lt;a href="https://www.pulumi.com/blog/neo-migration/"&gt;Terraform and Azure ARM migrations&lt;/a&gt; the same way.&lt;/p&gt;
&lt;h2 id="7-containerize-a-service-and-migrate-it-to-kubernetes-from-a-runbook"&gt;7. Containerize a service and migrate it to Kubernetes from a runbook&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Write the containerization pattern down once. Every service after that is a prompt away.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Containerizing an application and moving it to Kubernetes involves several small decisions: which Docker image, what labels go on deployments, how ingress is wired, and how secrets reach the pod. But after a team has moved two or three services, the pattern is set. The decisions get written down in a runbook, and every subsequent migration is mostly the same shape.&lt;/p&gt;
&lt;p&gt;Ask Neo:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Containerize the &lt;code&gt;billing-api&lt;/code&gt; service and write its Kubernetes manifests, following our K8s migration runbook in Confluence.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Neo reads the source repo and the runbook in Confluence via the &lt;a href="https://www.pulumi.com/blog/neo-integration-catalog/"&gt;integration catalog&lt;/a&gt; and starts working on your request.&lt;/p&gt;
&lt;p&gt;You can save this as a Neo skill that splits the work into multiple PRs — Dockerfile first, ECR config next, Deployment/Service/Ingress manifests after — and link back to each runbook convention for ease of review. The output reflects your conventions: the labels you actually use, the ingress class you&amp;rsquo;ve standardized on, and the External Secrets Operator config your team prefers.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;re still the one reviewing the PRs and deciding what the cutover looks like in production. Neo follows your internal standards, so the new service ends up shaped like the last one you migrated.&lt;/p&gt;
&lt;p&gt;&lt;img src="neo-migration-prs.png" alt="Five GitHub PRs in a row, one per migration step (Dockerfile, Deployment, Service+Ingress, ExternalSecret, HPA+PDB), each citing the runbook section it implements"&gt;
&lt;figcaption&gt;
&lt;center&gt;
&lt;i&gt;
Neo migrating a VM-based service to Kubernetes step by step, following the team's Confluence runbook.
&lt;/i&gt;
&lt;/center&gt;
&lt;/figcaption&gt;
&lt;/p&gt;
&lt;p&gt;Once you&amp;rsquo;ve delegated something a few times, the next move is to automate it. The remaining three tasks are the kind Neo doesn&amp;rsquo;t need to be asked for. Drift, deps, compliance: they&amp;rsquo;re the operations you put on a schedule via &lt;a href="https://www.pulumi.com/blog/neo-automations/"&gt;Neo Automations&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="8-schedule-daily-configuration-drift-detection-across-your-cloud-infrastructure"&gt;8. Schedule daily configuration drift detection across your cloud infrastructure&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Schedule a daily drift check across your cloud. Wake up to PRs that fix what changed overnight.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Configuration drift is an ongoing challenge. The security team rotated an IAM role at 04:47 UTC. Someone changed a security group in the AWS console three weeks ago. Left alone, drift turns into security gaps, into compliance issues, and into the kind of &amp;ldquo;wait, who changed that?&amp;rdquo; confusion nobody wants to chase down.&lt;/p&gt;
&lt;p&gt;Pulumi Cloud is already good at configuration drift detection. Neo takes it a step further.&lt;/p&gt;
&lt;p&gt;Ask Neo:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Every morning at 6 AM, check all production infrastructure for drift and create PRs to fix any issues you find.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;From then on, the task runs on its own, and you wake up to a PR per drifted resource. The description spells out what happened (&lt;code&gt;iam_role.audit-reader&lt;/code&gt; had inline policy &lt;code&gt;AllowReadAuditLogs&lt;/code&gt; added at 04:47 UTC) and cites the section of &lt;code&gt;infra/runbooks/drift.md&lt;/code&gt; Neo followed.&lt;/p&gt;
&lt;p&gt;Some drift gets encoded into the Pulumi program, like the IAM rotation above. Some gets reverted, like the security group rule added from the console. Some gets ignored entirely, like autoscaler-managed Lambda concurrency reservations the runbook tells Neo to skip. You write the runbook once; Neo follows it every morning to decide what to do.&lt;/p&gt;
&lt;p&gt;&lt;img src="neo-drift-pr.png" alt="Neo&amp;rsquo;s morning drift PR: encodes a security-team IAM rotation into the prod-audit stack, citing the runbook section that says to accept and encode the change"&gt;
&lt;figcaption&gt;
&lt;center&gt;
&lt;i&gt;
Neo's morning drift PR. The body names the resource, the change, when it happened, and the section of the runbook Neo followed to decide what to do.
&lt;/i&gt;
&lt;/center&gt;
&lt;/figcaption&gt;
&lt;/p&gt;
&lt;a href="https://app.pulumi.com/neo?prompt=Every&amp;#43;morning&amp;#43;at&amp;#43;6&amp;#43;AM%2C&amp;#43;check&amp;#43;all&amp;#43;production&amp;#43;infrastructure&amp;#43;for&amp;#43;drift&amp;#43;and&amp;#43;create&amp;#43;PRs&amp;#43;to&amp;#43;fix&amp;#43;any&amp;#43;issues&amp;#43;you&amp;#43;find." class="neo-card"&gt;
&lt;div class="neo-card-content"&gt;
&lt;div class="neo-card-icon"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--regular" fill="currentColor" role="img" aria-label="Pulumi Neo AI icon"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#c-pulumi-neo-regular"/&gt;&lt;/svg&gt;
&lt;/div&gt;
&lt;div class="neo-card-text"&gt;
&lt;span class="neo-card-subtitle"&gt;Start a Neo task&lt;/span&gt;
&lt;span class="neo-card-title"&gt;Schedule a daily drift check&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="neo-card-arrow"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--bold" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-caret-right-bold"/&gt;&lt;/svg&gt;
&lt;/div&gt;
&lt;/a&gt;
&lt;h2 id="9-schedule-weekly-upgrades-for-outdated-lambda-runtimes-and-providers"&gt;9. Schedule weekly upgrades for outdated Lambda runtimes and providers&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Lambda runtimes and container base images age out. Schedule the upgrade pass; review the PRs Neo opens.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;AWS Lambda end-of-life notices come out months ahead. Node 20 stopped receiving updates as an AWS Lambda runtime at the end of April. Python 3.9 reached end-of-support last December. After the deadline, AWS blocks new deploys and eventually stops invoking the function. Each one needs to move to a supported runtime before the cutoff.&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Schedule it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Every Sunday night at 10 PM, check our Lambda functions for runtimes nearing end-of-support and open PRs to upgrade them.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Neo reads the AWS Lambda runtime deprecation page, matches the end-of-support runtimes against every Lambda function in your stacks, and opens one PR per stack.&lt;/p&gt;
&lt;p&gt;If Python 3.9 is reaching end-of-support, the upgrade is to Python 3.12, and &lt;code&gt;datetime.utcnow()&lt;/code&gt; calls need to move to &lt;code&gt;datetime.now(datetime.UTC)&lt;/code&gt;. Neo can make all of those replacements in the same PR.&lt;/p&gt;
&lt;p&gt;The same task can catch container base images with critical CVEs and bump them too.&lt;/p&gt;
&lt;p&gt;
&lt;div class="my-4"&gt;
&lt;video class="flex outline-none rounded-lg w-full" title="Setting up a weekly scheduled task in Neo"
autoplay muted playsinline
loop &gt;
&lt;source src="neo-schedule-setup.mp4" /&gt;
&lt;/video&gt;
&lt;/div&gt;
&lt;figcaption&gt;
&lt;center&gt;
&lt;i&gt;
Setting up a weekly task in the Scheduled Tasks UI. Once saved, Neo runs the prompt every Sunday night and opens PRs you review on Monday.
&lt;/i&gt;
&lt;/center&gt;
&lt;/figcaption&gt;
&lt;/p&gt;
&lt;a href="https://app.pulumi.com/neo?prompt=Every&amp;#43;Sunday&amp;#43;night&amp;#43;at&amp;#43;10&amp;#43;PM%2C&amp;#43;check&amp;#43;our&amp;#43;Lambda&amp;#43;functions&amp;#43;for&amp;#43;runtimes&amp;#43;nearing&amp;#43;end-of-support&amp;#43;and&amp;#43;open&amp;#43;PRs&amp;#43;to&amp;#43;upgrade&amp;#43;them." class="neo-card"&gt;
&lt;div class="neo-card-content"&gt;
&lt;div class="neo-card-icon"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--regular" fill="currentColor" role="img" aria-label="Pulumi Neo AI icon"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#c-pulumi-neo-regular"/&gt;&lt;/svg&gt;
&lt;/div&gt;
&lt;div class="neo-card-text"&gt;
&lt;span class="neo-card-subtitle"&gt;Start a Neo task&lt;/span&gt;
&lt;span class="neo-card-title"&gt;Schedule a weekly runtime upgrade check&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="neo-card-arrow"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--bold" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-caret-right-bold"/&gt;&lt;/svg&gt;
&lt;/div&gt;
&lt;/a&gt;
&lt;h2 id="10-fix-aws-cis-benchmark-failures-with-daily-prs"&gt;10. Fix AWS CIS Benchmark failures with daily PRs&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Run the AWS CIS Benchmark on a schedule. Wake up to PRs that fix every failure.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://docs.aws.amazon.com/securityhub/latest/userguide/cis-aws-foundations-benchmark.html"&gt;CIS AWS Foundations Benchmark&lt;/a&gt;, available through AWS Security Hub, is something every team should be keeping an eye on. The benchmark finds issues like S3 buckets that allow public read access (&lt;code&gt;S3.1&lt;/code&gt;), root user access keys that shouldn&amp;rsquo;t exist (&lt;code&gt;IAM.4&lt;/code&gt;), or CloudTrail not being enabled (&lt;code&gt;CloudTrail.1&lt;/code&gt;). Scanning for these issues is a solved problem, but closing and addressing them is not. They pile up between audits because each one is a code change in a different stack, and nobody owns the cross-stack cleanup.&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Schedule the cleanup:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Every morning, read CIS Benchmark failures from Security Hub. For every failure on an IaC-managed resource, open a PR with the fix.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Neo opens one PR per failure. A bucket failing &lt;code&gt;S3.1&lt;/code&gt; arrives as a Pulumi diff that adds &lt;code&gt;blockPublicAccess&lt;/code&gt; to the bucket in your &lt;code&gt;prod-checkout&lt;/code&gt; stack. The PR body lists the CIS rule number, the resource ID, the diff, and a clean &lt;code&gt;pulumi preview&lt;/code&gt; against the live infrastructure.&lt;/p&gt;
&lt;p&gt;The runbook is where your security team writes down what each control means for your stacks. Block public S3 buckets, except the ones tagged &lt;code&gt;public-content=true&lt;/code&gt; for CloudFront origins. Don&amp;rsquo;t auto-touch the break-glass IAM roles; page a human instead. Multi-region CloudTrail stays on, no exceptions. Neo reads that file, checks each Security Hub finding against it, and only opens a PR for the ones you&amp;rsquo;ve said are safe to fix. The rest get routed or ignored, the way your team already handles them.&lt;/p&gt;
&lt;p&gt;
&lt;div class="my-4"&gt;
&lt;video class="flex outline-none rounded-lg w-full" title="Scrolling through Neo&amp;#39;s morning CIS Benchmark PR"
autoplay muted playsinline
loop &gt;
&lt;source src="neo-cis-pr.mp4" /&gt;
&lt;/video&gt;
&lt;/div&gt;
&lt;figcaption&gt;
&lt;center&gt;
&lt;i&gt;
A PR raised by Neo to fix a CIS Benchmark failure, with the failing rule, the resource, and the runbook decision laid out in the body.
&lt;/i&gt;
&lt;/center&gt;
&lt;/figcaption&gt;
&lt;/p&gt;
&lt;a href="https://app.pulumi.com/neo?prompt=Every&amp;#43;morning%2C&amp;#43;verify&amp;#43;all&amp;#43;resources&amp;#43;meet&amp;#43;our&amp;#43;compliance&amp;#43;policies&amp;#43;and&amp;#43;create&amp;#43;PRs&amp;#43;to&amp;#43;fix&amp;#43;violations." class="neo-card"&gt;
&lt;div class="neo-card-content"&gt;
&lt;div class="neo-card-icon"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--regular" fill="currentColor" role="img" aria-label="Pulumi Neo AI icon"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#c-pulumi-neo-regular"/&gt;&lt;/svg&gt;
&lt;/div&gt;
&lt;div class="neo-card-text"&gt;
&lt;span class="neo-card-subtitle"&gt;Start a Neo task&lt;/span&gt;
&lt;span class="neo-card-title"&gt;Schedule daily AWS CIS Benchmark scans&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="neo-card-arrow"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--bold" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-caret-right-bold"/&gt;&lt;/svg&gt;
&lt;/div&gt;
&lt;/a&gt;
&lt;h2 id="neo-your-newest-platform-engineer"&gt;Neo: your newest platform engineer&lt;/h2&gt;
&lt;p&gt;Over the past year, many product teams have stopped treating AI as a request-by-request assistant and started delegating to it outright.&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt; Agents open pull requests, investigate issues, and iterate on review feedback.&lt;/p&gt;
&lt;p&gt;But platform engineers have held back because a bad infrastructure change doesn&amp;rsquo;t just fail, it can take production down. Coding agents benefit from fast, forgiving feedback loops, but infrastructure recovery is rarely as simple as reverting a commit.&lt;/p&gt;
&lt;p&gt;What was missing wasn&amp;rsquo;t the appetite. It was an agent with enough organizational context and &lt;a href="https://www.pulumi.com/blog/grounded-ai-why-neo-knows-your-infrastructure/"&gt;grounding&lt;/a&gt; to plan reliably, enough guardrails to feel safe and contain mistakes, and enough discipline to keep working without being asked.&lt;/p&gt;
&lt;p&gt;The theme across these tasks is clear. A thing platform engineers used to keep in their heads becomes a task you delegate, then becomes work that runs without you. Neo isn&amp;rsquo;t generating infrastructure from a template. It&amp;rsquo;s a teammate who knows your code, your providers, your conventions, your production metrics, and can raise PRs for you to review.&lt;/p&gt;
&lt;p&gt;Neo now lives in your terminal, in your pull requests, in your Slack workspace, and in Pulumi Cloud. Pick one of these workflows and &lt;a href="https://www.pulumi.com/docs/pulumi-cloud/neo/"&gt;give it a try&lt;/a&gt;.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;The observant reader will notice Terraform-to-Pulumi was covered &lt;a href="https://www.pulumi.com/blog/10-things-you-can-do-with-neo/"&gt;in the original post&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Also covered in the &lt;a href="https://www.pulumi.com/blog/10-things-you-can-do-with-neo/"&gt;original post&lt;/a&gt;. Last year you could ask Neo to do it once. This year you can put it on a schedule.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;Also covered in the &lt;a href="https://www.pulumi.com/blog/10-things-you-can-do-with-neo/"&gt;original post&lt;/a&gt;. Last year Neo could remediate violations on demand. This year Security Hub feeds findings to a scheduled task that knows your runbook&amp;rsquo;s interpretation of each control.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;For a concrete example, see &lt;a href="https://www.pulumi.com/blog/seven-rules-ai-native-software-factory/"&gt;Seven Rules for Building an AI-Native Software Factory&lt;/a&gt;: Ewan Dawson, CTO of Compostable AI, runs nineteen client deployments with five engineers, using Pulumi Neo to handle most of the infrastructure work.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>Adam Gordon Bell</author><category>ai</category><category>platform-engineering</category><category>pulumi-neo</category><category>aws</category></item><item><title>CDKTF is deprecated: What's next for your team?</title><link>https://www.pulumi.com/blog/cdktf-is-deprecated-whats-next-for-your-team/</link><pubDate>Thu, 18 Dec 2025 10:00:00 -0800</pubDate><guid>https://www.pulumi.com/blog/cdktf-is-deprecated-whats-next-for-your-team/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/cdktf-is-deprecated-whats-next-for-your-team/index.png" /&gt;
&lt;p&gt;In July, 2020, CDK for Terraform (CDKTF) was introduced, and last week, on December 10, it was officially deprecated. Support for CDKTF has stopped, the &lt;a href="https://github.com/cdktf"&gt;organization&lt;/a&gt; and &lt;a href="https://github.com/hashicorp/terraform-cdk"&gt;repository&lt;/a&gt; have been archived, and HashiCorp/IBM will no longer be updating or maintaining it, leaving a lot of teams out there without a clear path forward.&lt;/p&gt;
&lt;p&gt;For most teams, that means it&amp;rsquo;s time to start looking for a replacement.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s an unfortunate situation to suddenly find yourself in as a user of CDKTF, but you do have options, and Pulumi is one of them. In this post, we&amp;rsquo;ll help you understand what those options are, how Pulumi fits into them, and what it&amp;rsquo;d look like to migrate your CDKTF projects to Pulumi.&lt;/p&gt;
&lt;h2 id="what-are-the-alternatives-to-cdktf"&gt;What are the alternatives to CDKTF?&lt;/h2&gt;
&lt;p&gt;Teams migrating away from CDKTF generally have three options:&lt;/p&gt;
&lt;h3 id="option-1-fall-back-to-hcl"&gt;Option 1: Fall back to HCL&lt;/h3&gt;
&lt;p&gt;HashiCorp&amp;rsquo;s official recommendation is to export your projects to HashiCorp Configuration Language (HCL) and manage them with Terraform. CDKTF even has a command that makes this fairly simple:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cdktf synth --hcl
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Of course, if you&amp;rsquo;re using CDKTF, you probably chose it specifically to avoid HCL. So while possible, this probably isn&amp;rsquo;t the choice most teams would make unless they had to.&lt;/p&gt;
&lt;h3 id="option-2-migrate-to-aws-cdk"&gt;Option 2: Migrate to AWS CDK&lt;/h3&gt;
&lt;p&gt;If your team is all-in on AWS, another option would be to migrate to AWS CDK. It&amp;rsquo;s widely used, officially supported, the programming model is similar to CDKTF&amp;rsquo;s, and both CDK and CDKTF transpile to an intermediate format (CloudFormation YAML and Terraform JSON, respectively) that gets passed on to their underlying tools for deployment.&lt;/p&gt;
&lt;p&gt;But while their programming and deployment models are conceptually similar, their resource models and APIs are entirely different. Here&amp;rsquo;s the code for an S3 bucket written in AWS CDK, for example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;aws-cdk-lib/aws-s3&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;my-bucket&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;bucketName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;my-example-bucket&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;versioned&lt;/span&gt;: &lt;span class="kt"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;publicReadAccess&lt;/span&gt;: &lt;span class="kt"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And here&amp;rsquo;s the code for a similarly configured bucket in CDKTF:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;S3Bucket&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;@cdktf/provider-aws/lib/s3-bucket&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;S3Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;my-bucket&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;my-example-bucket&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;versioning&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;enabled&lt;/span&gt;: &lt;span class="kt"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;acl&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;private&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Notice how different these APIs are — and this is just one simple resource with only a few properties; imagine having to rewrite dozens or hundreds of them. Beyond that, there&amp;rsquo;s also the problem of state: How would you go about translating the contents of a Terraform state file containing hundreds of resources into the equivalent CloudFormation YAML or JSON?&lt;/p&gt;
&lt;p&gt;Despite their surface similarities, CDKTF and AWS CDK have little in common. Migration would essentially mean a ground-up rewrite that&amp;rsquo;d also leave you without the multi-cloud support you already have with CDKTF. For most teams, that makes this option a practical non-starter.&lt;/p&gt;
&lt;h3 id="option-3-migrate-to-pulumi"&gt;Option 3: Migrate to Pulumi&lt;/h3&gt;
&lt;p&gt;This is where we should acknowledge our obvious bias — but we genuinely believe that for most users of CDKTF, Pulumi really is the simplest and most broadly compatible alternative.&lt;/p&gt;
&lt;p&gt;Like CDKTF, Pulumi lets you build and manage your infrastructure with general-purpose languages like TypeScript, Python, Go, C#, and Java, and it supports organizing your code into higher-level abstractions called &lt;a href="https://www.pulumi.com/docs/iac/concepts/components/"&gt;&lt;em&gt;components&lt;/em&gt;&lt;/a&gt;, which you can think of like CDKTF constructs. Both organize cloud resources into &lt;a href="https://www.pulumi.com/docs/iac/concepts/stacks/"&gt;&lt;em&gt;stacks&lt;/em&gt;&lt;/a&gt; (think &lt;code&gt;dev&lt;/code&gt;, &lt;code&gt;prod&lt;/code&gt;), and both track &lt;a href="https://www.pulumi.com/docs/iac/concepts/state-and-backends/"&gt;deployment state&lt;/a&gt; similarly, with local, remote, and cloud-hosted options available.&lt;/p&gt;
&lt;p&gt;Many of Pulumi&amp;rsquo;s most popular &lt;a href="https://www.pulumi.com/docs/iac/concepts/resources/providers/"&gt;providers&lt;/a&gt; (e.g., the AWS provider) are also built from open-source Terraform schemas, which means their resource models will be nearly identical to what you&amp;rsquo;re used to with CDKTF. Here&amp;rsquo;s what an S3 bucket looks like in Pulumi, for example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;@pulumi/aws&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;my-bucket&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;my-example-bucket&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;versioning&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;enabled&lt;/span&gt;: &lt;span class="kt"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;acl&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;private&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can also use &lt;a href="https://www.pulumi.com/docs/iac/get-started/terraform/terraform-providers/"&gt;any Terraform provider&lt;/a&gt; with Pulumi, and you can even &lt;a href="https://www.pulumi.com/docs/iac/guides/migration/migrating-to-pulumi/from-terraform/#using-terraform-modules-directly"&gt;reference Terraform modules directly&lt;/a&gt; from within your Pulumi code.&lt;/p&gt;
&lt;p&gt;Pulumi is also different from CDKTF in several ways. One is that rather than transpile your source code to a format like JSON as CDKTF does (and then deploying it separately later), Pulumi uses its own declarative deployment engine that resolves the resource graph at runtime and provisions cloud resources directly, which is much faster and more flexible. You can learn more about the deployment model in &lt;a href="https://www.pulumi.com/docs/iac/concepts/how-pulumi-works/"&gt;How Pulumi Works&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Given the API similarities, the support for all Terraform providers and modules, the ability to &lt;a href="https://www.pulumi.com/docs/iac/guides/migration/#coexistence"&gt;coexist&lt;/a&gt; alongside Terraform-managed projects, and the built-in support for conversion (which we&amp;rsquo;ll cover next), we think Pulumi is the best alternative for most teams looking to migrate.&lt;/p&gt;
&lt;div class="rounded-lg bg-violet-50 p-6 my-8"&gt;
&lt;p class="heading-4 m-0 mb-3 flex items-center gap-1.5"&gt;Migrate from CDKTF to Pulumi&lt;/p&gt;
&lt;div class="body-base m-0 text-gray-950"&gt;Keep writing infrastructure in TypeScript, Python, Go, C#, or Java, and use Pulumi&amp;rsquo;s built-in convert and import commands to bring your CDKTF projects and state across.&lt;/div&gt;
&lt;a href="https://www.pulumi.com/docs/iac/guides/migration/migrating-to-pulumi/from-terraform/" data-track="blog-body-cta" class="btn btn-primary mt-4"&gt;
Get started
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--regular size-4" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-arrow-right-regular"/&gt;&lt;/svg&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id="what-migrating-to-pulumi-looks-like"&gt;What migrating to Pulumi looks like&lt;/h2&gt;
&lt;p&gt;Migrating a CDKTF project to Pulumi generally happens in three steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Conversion&lt;/strong&gt;, which translates your CDKTF code into a new Pulumi program&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Import&lt;/strong&gt;, which reads the contents of your CDKTF state into a new Pulumi stack&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Refactoring&lt;/strong&gt;, which brings the code in the new program into alignment with the stack&amp;rsquo;s currently deployed resources&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="conversion-and-import"&gt;Conversion and import&lt;/h3&gt;
&lt;p&gt;Migration starts with exporting your CDKTF project to HCL with &lt;code&gt;cdktf synth&lt;/code&gt;. From there, Pulumi&amp;rsquo;s built-in &lt;a href="https://www.pulumi.com/docs/iac/cli/commands/pulumi_convert/"&gt;&lt;code&gt;convert&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://www.pulumi.com/docs/iac/cli/commands/pulumi_import/"&gt;&lt;code&gt;import&lt;/code&gt;&lt;/a&gt; commands handle creating the new program and importing your state:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Export your project to HCL.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cdktf synth --hcl
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Convert the HCL into a new Pulumi project.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pulumi convert --from terraform --language typescript
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Create a new Pulumi stack.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pulumi stack init dev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Import your CDKTF stack&amp;#39;s resources into your new Pulumi stack.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pulumi import --from terraform ./terraform.dev.tfstate
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The converter automatically translates Terraform input variables, data sources, resources, and outputs into their Pulumi equivalents. You can read more about how this works in &lt;a href="https://www.pulumi.com/docs/iac/guides/migration/migrating-to-pulumi/from-terraform/#converting-terraform-hcl-to-pulumi"&gt;Converting Terraform HCL to Pulumi&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="refactoring"&gt;Refactoring&lt;/h3&gt;
&lt;p&gt;Once you&amp;rsquo;ve imported your state, you&amp;rsquo;ll often have to make some adjustments to the code to bring it in line with the new Pulumi stack. For instance, &lt;code&gt;pulumi import&lt;/code&gt; marks new resources &lt;a href="https://www.pulumi.com/docs/iac/concepts/resources/options/protect/"&gt;protected&lt;/a&gt; by default, to prevent them from being accidentally deleted — but since the code produced by &lt;code&gt;pulumi convert&lt;/code&gt; doesn&amp;rsquo;t include the &lt;code&gt;protect&lt;/code&gt; resource option, you&amp;rsquo;ll need to add it yourself. Fortunately the import step also emits code that you can copy into your program to make this process a little easier.&lt;/p&gt;
&lt;p&gt;Refactoring can get a bit more complicated when custom logic and higher-level abstractions are involved, as fidelity to the original CDKTF code is often lost in the translation to HCL. In these situations, having the help of an LLM to recapture that original logic or translate your CDKTF constructs into Pulumi components can be a big time-saver.&lt;/p&gt;
&lt;h2 id="an-end-to-end-example"&gt;An end-to-end example&lt;/h2&gt;
&lt;p&gt;The best way to get a feel for how this works, though, is to try it yourself.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://github.com/pulumi/cdktf-to-pulumi-example"&gt;pulumi/cdktf-to-pulumi-example&lt;/a&gt; repository on GitHub contains a CDKTF project with multiple stacks written in TypeScript, along with a guide that walks you through the process of migrating that project to Pulumi. The guide covers everything we&amp;rsquo;ve discussed here so far, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Converting the CDKTF project into a new Pulumi project&lt;/li&gt;
&lt;li&gt;Importing its actively running resources into Pulumi stacks&lt;/li&gt;
&lt;li&gt;Modifying the generated code to align with imported state&lt;/li&gt;
&lt;li&gt;Performing an initial deployment with Pulumi to complete the migration process&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The walkthrough takes only a few minutes to complete, and it&amp;rsquo;s a great way to stand up an example of your own to get more familiar with Pulumi.&lt;/p&gt;
&lt;a href="https://github.com/pulumi/cdktf-to-pulumi-example" target="_blank" rel="noopener noreferrer" class="github-card"&gt;
&lt;img
src="https://opengraph.githubassets.com/1/pulumi/cdktf-to-pulumi-example"
alt="GitHub repository: pulumi/cdktf-to-pulumi-example"
class="github-card-image"
loading="lazy"
/&gt;
&lt;div class="github-card-content"&gt;
&lt;div class="github-card-domain"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon github-card-icon" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#b-github"/&gt;&lt;/svg&gt;
github.com/pulumi/cdktf-to-pulumi-example
&lt;/div&gt;
&lt;/div&gt;
&lt;/a&gt;
&lt;h2 id="whats-next"&gt;What&amp;rsquo;s next?&lt;/h2&gt;
&lt;p&gt;If you’re moving on from CDKTF and looking for an alternative, there are a few possible paths forward. For teams that want to keep using real languages and avoid a ground-up rewrite, Pulumi offers the clearest way forward.&lt;/p&gt;
&lt;p&gt;To learn more about how Pulumi works, how it differs from CDKTF and from Terraform, how to handle additional conversion scenarios, and more, we recommend:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Diving into &lt;a href="https://www.pulumi.com/docs/iac/concepts/"&gt;the Pulumi docs&lt;/a&gt; to get familiar with core concepts and features of the platform&lt;/li&gt;
&lt;li&gt;Reading &lt;a href="https://www.pulumi.com/docs/iac/guides/migration/migrating-to-pulumi/from-terraform/"&gt;Migrating from Terraform or CDKTF to Pulumi&lt;/a&gt; for more detailed, Terraform-specific migration guidance&lt;/li&gt;
&lt;li&gt;Joining us in the &lt;a href="https://slack.pulumi.com/"&gt;Pulumi Community Slack&lt;/a&gt; to ask questions and learn from others who&amp;rsquo;ve successfully made the leap from Terraform and CDKTF to Pulumi&lt;/li&gt;
&lt;li&gt;Checking out &lt;a href="https://www.pulumi.com/blog/all-iac-including-terraform-and-hcl/"&gt;Pulumi for All Your IaC — Including Terraform and HCL&lt;/a&gt; to learn more about Pulumi&amp;rsquo;s native support for Terraform and HCL&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And of course, &lt;a href="https://www.pulumi.com/contact/"&gt;feel free to reach out&lt;/a&gt;! We&amp;rsquo;d love to help in any way we can.&lt;/p&gt;</description><author>Adam Gordon Bell</author><author>Christian Nunciato</author><category>migration</category><category>terraform</category><category>cdktf</category></item><item><title>AWS built an integrated AI Agent training pipeline and they want you to rent it</title><link>https://www.pulumi.com/blog/aws-reinvent-2025-roundup/</link><pubDate>Wed, 10 Dec 2025 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/aws-reinvent-2025-roundup/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/aws-reinvent-2025-roundup/index.png" /&gt;
&lt;p&gt;AWS re:Invent 2025 delivered a myriad of announcements across AI, silicon, and cloud infrastructure. AWS unveiled the expanded Nova model family, introduced Nova Forge for custom model training, launched Trainium3 UltraServers, and added major production features to AgentCore. It was a lot, and taken at face value, it looks like another scattershot year of big releases.&lt;/p&gt;
&lt;p&gt;But if you look past the firehose, a pattern emerges. These announcements fit together into a single bet about how enterprise AI will be built.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AWS built a vertically integrated agent-training pipeline; it’s expensive, ambitious, and not for everyone.&lt;/strong&gt;&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/M1OEOmqLz9s?rel=0?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;The clearest place to see that pattern is in how AWS talked about the Nova models. AWS dropped four new foundation models (Lite, Pro, Sonic, Omni) spanning text, multimodal, and speech. And AWS downplayed benchmarks entirely. A hint that the models aren’t the real story.&lt;/p&gt;
&lt;p&gt;What AWS was really foregrounding wasn’t the models themselves, but the system used to shape them: Nova Forge.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Try it with Neo:&lt;/strong&gt; &lt;a href="https://app.pulumi.com/neo?prefer_signup=true&amp;amp;prompt=Show%20me%20how%20to%20use%20Pulumi%20in%20Python%20to%20set%20up%20AWS%20Bedrock%20permissions%20and%20call%20the%20Nova%202%20Pro%20model"&gt;Set up Nova 2 with Pulumi&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="rent-the-lab-nova-forge"&gt;Rent the lab: Nova Forge&lt;/h2&gt;
&lt;p&gt;&lt;img src="training-stages.jpeg" alt="The LLM training pipeline: pre-training, SFT, RLHF, fine-tuning, narrowing down to prompt/context"&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://aws.amazon.com/nova/forge/"&gt;Nova Forge&lt;/a&gt; is a managed way to run continued pretraining, fine-tuning, and reward-based alignment on Amazon&amp;rsquo;s &lt;a href="https://aws.amazon.com/nova/"&gt;Nova&lt;/a&gt; models using your data and your reinforcement loops. Instead of a finished, frozen model plus a thin fine-tuning API, you feed your data into earlier training stages while AWS handles the ugly parts: large-scale training runs, cluster management, and hosting. Access is $100,000 per year&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;, plus compute costs!&lt;/p&gt;
&lt;p&gt;If you can afford that, you bring big proprietary datasets (code, tickets, logs, documents) and they keep doing next-token pretraining on a mix of their data and yours, then instruction tuning (SFT), then &amp;ldquo;RL-style&amp;rdquo; preference optimization, but with your data and your reward signals mixed in.&lt;/p&gt;
&lt;p&gt;Why does this exist? Because the kind of training this enables is usually out of reach. Training a GPT-4-class frontier model from scratch runs tens of millions to $100M+ in compute alone&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;. You don&amp;rsquo;t own the weights and you&amp;rsquo;re locked into their stack, but you get frontier-level capabilities with your data baked in, without building datacenters or staffing ML teams.&lt;/p&gt;
&lt;p&gt;Think of it as frontier-lab-as-a-service. No one else offers anything this close to a public, end-to-end training pipeline. And the only reason AWS can offer it is the next announcement.&lt;/p&gt;
&lt;h2 id="the-margin-weapon-trainium"&gt;The margin weapon: Trainium&lt;/h2&gt;
&lt;figure style="width: 40%; float: right; margin-left: 20px; margin-bottom: 10px;"&gt;
&lt;img src="trainium-flywheel.jpeg" alt="The Trainium flywheel: cheaper training leads to more custom models, more inference revenue, funding the next chip"&gt;
&lt;figcaption&gt;&lt;i&gt;The idealized Trainium flywheel: each generation should decrease training costs.&lt;/i&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;AWS built its own AI accelerator so it doesn&amp;rsquo;t have to live entirely on NVIDIA. Trainium is that chip. You don&amp;rsquo;t buy it; you rent it as a cloud box. This year: their third-gen chip (Trainium3) and new rack-scale &lt;code&gt;Trn3 UltraServers&lt;/code&gt; are out, with 4× the performance and big energy/cost gains over the previous gen, positioned as a serious alternative to high-end GPUs for training and serving big models.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m sure one reason for Trainium&amp;rsquo;s is that AWS wants to stop handing NVIDIA half its AI training revenue. But the real story is bigger than cost-cutting. Trainium is the quiet machinery that makes AWS&amp;rsquo;s model-factory ambitions economically viable. You can only rent a frontier training pipeline if you can afford it, and Trainium makes it cheaper (if that word applies to six-figure entry costs).&lt;/p&gt;
&lt;p&gt;Trainium is what turns Forge from a one-off experiment into an actual development pipeline. By compressing the marginal cost of each training cycle, AWS is trying to make iterative specialization economically viable. You can tune, test, and retrain until you converge on something useful.&lt;/p&gt;
&lt;p&gt;AWS is clearly positioning Trainium3 to anchor a fully vertical stack&lt;/p&gt;
&lt;div class="note note-info" style="display: block;"&gt;
&lt;p&gt;&lt;b&gt;Spinning up a Trainium instance with Pulumi (where available):&lt;/b&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pulumi_aws&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;aws&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Assumes: ami, subnet, security_group already configured&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;trn1_instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Instance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;trn1-instance&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;instance_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;trn1.2xlarge&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ami&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ami&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;subnet_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subnet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;vpc_security_group_ids&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;security_group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;associate_public_ip_address&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;trn1-training-instance&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;a href="https://app.pulumi.com/neo?prefer_signup=true&amp;prompt=Show%20me%20how%20to%20use%20Pulumi%20in%20Python%20to%20provision%20AWS%20Trn1%20EC2%20instances%20for%20ML%20training"&gt;Try it with Neo: Provision Trainium instances&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="the-data-moat-play"&gt;The data moat play&lt;/h2&gt;
&lt;p&gt;For most companies, this whole stack is overkill. If your AI roadmap is “add a chatbot and maybe summarize some tickets,” you don’t need Nova Forge, and you definitely don’t need Trainium. Hosted models plus RAG and an Agentic loop will get you 90% of the way there.&lt;/p&gt;
&lt;p&gt;But this type of training is powerful, and it&amp;rsquo;s never been in reach to so many. If LLMs behave like the distributions they&amp;rsquo;re trained on, then getting your proprietary mess (logs, incident reports, claims histories, deal flows, call transcripts) into the core training loop means the model doesn&amp;rsquo;t just know your docs; it behaves like someone who&amp;rsquo;s lived inside your systems. That’s qualitatively different from “we stuffed a PDF into the context window.”&lt;/p&gt;
&lt;p&gt;Latency and cost at scale matter too. For high-volume workflows like support triage, routing, code review, and fraud checks, &amp;ldquo;generic frontier model + giant prompt + RAG + tools&amp;rdquo; is slow and expensive. A smaller model that has your world baked into the weights can run with smaller contexts, simpler prompts, and fewer tool calls. And then there is reinforcement learning, which I&amp;rsquo;ll get to shortly.&lt;/p&gt;
&lt;p&gt;But even if you get that far, a custom Nova model sitting in Bedrock is only half the story. You still need somewhere for it to act: a runtime, tools, policies, and an audit trail. That’s the gap AgentCore is meant to fill.&lt;/p&gt;
&lt;h2 id="where-the-models-work-agentcore"&gt;Where the models work: AgentCore&lt;/h2&gt;
&lt;figure style="width: 40%; float: right; margin-left: 20px; margin-bottom: 10px;"&gt;
&lt;img src="agentcore-blocks.jpeg" alt="AgentCore components as Lego blocks: Runtime, Memory, Policy, Evals"&gt;
&lt;figcaption&gt;&lt;i&gt;AgentCore: building blocks so you don't have to wire agents from scratch.&lt;/i&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If Nova is the brain and Trainium is the muscle to build it, AgentCore is the nervous system.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://aws.amazon.com/bedrock/agentcore/"&gt;AgentCore&lt;/a&gt; is a managed runtime for AI agents: instead of you wiring LLMs, tools, memory, auth, and logging together on Lambda or Fargate, AWS gives you a sticky per-session microVM, a standard way to call tools (Gateway), built-in long- and short-term memory, identity/permissions, and observability/evals. You package your agent, deploy it as an AgentCore runtime, and AWS handles the ugly parts: session isolation, scaling, policy guardrails, and tracing. You pay Fargate-ish per-vCPU/GB-hour pricing for the runtime plus normal Bedrock token and tool-call costs.&lt;/p&gt;
&lt;p&gt;At re:Invent 2025, &lt;a href="https://aws.amazon.com/bedrock/agentcore/"&gt;AgentCore&lt;/a&gt; picked up the missing &amp;ldquo;production&amp;rdquo; pieces: &lt;strong&gt;Policy&lt;/strong&gt;, &lt;strong&gt;Evaluations&lt;/strong&gt;, and &lt;strong&gt;episodic Memory&lt;/strong&gt;. These handle guardrails, quality checks, and per-session state, so you don&amp;rsquo;t have to build them yourself.&lt;/p&gt;
&lt;div class="note note-info" style="display: block;"&gt;
&lt;p&gt;&lt;b&gt;Deploying an AgentCore runtime with Pulumi:&lt;/b&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pulumi_aws&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;aws&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Assumes: role, ecr_repo already configured&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;agent_runtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bedrock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AgentcoreAgentRuntime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;my-agent&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;agent_runtime_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;my-agent-runtime&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;role_arn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;agent_runtime_artifact&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;container_configuration&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;container_uri&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ecr_repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repository_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:latest&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;network_configuration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;network_mode&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;PUBLIC&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;a href="https://app.pulumi.com/neo?prefer_signup=true&amp;prompt=Show%20me%20how%20to%20use%20Pulumi%20in%20Python%20to%20deploy%20an%20AWS%20Bedrock%20AgentCore%20runtime%20with%20IAM%20roles%20and%20ECR"&gt;Try it with Neo: Deploy an AgentCore runtime&lt;/a&gt; — &lt;i&gt;Requires a container image in ECR.&lt;/i&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;How does this come together? AWS shipped a use case.&lt;/p&gt;
&lt;h2 id="the-proof-of-concept-nova-act"&gt;The proof of concept: Nova Act&lt;/h2&gt;
&lt;figure style="width: 40%; float: left; margin-right: 20px; margin-bottom: 10px;"&gt;
&lt;img src="aws-ai-stack.jpeg" alt="The AWS AI stack as a layer cake: Trainium at the bottom, Nova Forge, Bedrock, AgentCore on top"&gt;
&lt;figcaption&gt;&lt;i&gt;The AWS AI stack: vertically integrated from silicon to agent runtime. Nova Act uses the full stack.&lt;/i&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Nova Act is the concrete example of this whole thing coming together. It handles browser-based UI automation: form filling, search-and-extract, QA testing. Amazon claims ~90% reliability. It deploys directly to AgentCore Runtime.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s not &amp;ldquo;an LLM plus Playwright.&amp;rdquo; Nova Act uses a specialized Nova 2 Lite variant trained on synthetic &amp;ldquo;web gym&amp;rdquo; environments: browser simulations that mirror enterprise UIs and provide an automatic reward signal when tasks are completed correctly. Instead of judging output quality, this model was trained on an RL loop that asks: Did the workflow succeed?&lt;/p&gt;
&lt;p&gt;That specialized model is wrapped in AgentCore. The platform handles isolation, scaling, logging, and guardrails, so Nova Act behaves like a production automation system rather than a brittle demo.&lt;/p&gt;
&lt;p&gt;Seen this way, Nova Act is Amazon’s reference implementation for a certain class of enterprise agents: start with a strong general model, specialize it through domain-specific RL in a controlled environment, and run it on AgentCore with tools and policies around it. It’s the pattern AWS expects customers to adopt.&lt;/p&gt;
&lt;h2 id="one-stack-to-rule-them-all"&gt;One stack to rule them all&lt;/h2&gt;
&lt;p&gt;So Nova Forge, Trainium, AgentCore, and Nova Act connect. Trainium lowers the cost of big training runs. Nova Forge lets enterprises plug their own data and rewards into those runs. AgentCore is where the resulting models act, with tools, memory, and policy guardrails. Nova Act shows the pattern in action: a domain-specialized Nova model, trained in a controlled loop, running as a production agent.&lt;/p&gt;
&lt;p&gt;Most enterprises still won’t choose this path. They don’t have the data, the reward loops, or the operational maturity to make early-stage training worthwhile.&lt;/p&gt;
&lt;p&gt;But AWS’s bet is that &lt;a href="https://www.pulumi.com/aws/#video"&gt;enterprise AI is moving past stock foundation models and generic chatbots&lt;/a&gt;. AWS is expecting a world of &lt;strong&gt;agents shaped by proprietary data and domain feedback&lt;/strong&gt;. Most companies won&amp;rsquo;t build the infrastructure to train and operate those agents, and so AWS is offering to rent them the whole pipeline.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Try it with Neo:&lt;/strong&gt; &lt;a href="https://app.pulumi.com/neo?prefer_signup=true&amp;amp;prompt=Create%20a%20Python%20Pulumi%20program%20that%20deploys%20an%20AWS%20Lambda%20function%20calling%20Bedrock%20Nova%20Pro%20and%20exposes%20it%20via%20API%20Gateway"&gt;Deploy a Bedrock-powered API with Pulumi&lt;/a&gt;&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;CNBC reporting on Nova Forge pricing. &lt;a href="https://www.cnbc.com/2025/12/02/amazon-nova-forge-lets-clients-customize-ai-models-for-100000-a-year.html"&gt;Source&lt;/a&gt;&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Sam Altman stated GPT-4 cost &amp;ldquo;more than $100 million&amp;rdquo; to train. &lt;a href="https://news.ycombinator.com/item?id=35971363"&gt;Source&lt;/a&gt;&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>Adam Gordon Bell</author><category>aws</category><category>nova-forge</category><category>bedrock-agentcore</category><category>reinvent</category></item><item><title>How to Implement Robust Security Guardrails Using Policy as Code</title><link>https://www.pulumi.com/blog/deployment-guardrails-with-policy-as-code/</link><pubDate>Tue, 30 Sep 2025 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/deployment-guardrails-with-policy-as-code/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/deployment-guardrails-with-policy-as-code/index.png" /&gt;
&lt;p&gt;Welcome to the third post in our &lt;strong&gt;IDP Best Practices&lt;/strong&gt; series, where we explore how to implement &lt;strong&gt;policy as code&lt;/strong&gt; with &lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/crossguard"&gt;Pulumi CrossGuard&lt;/a&gt; to create deployment guardrails that make self-service infrastructure both powerful and safe.&lt;/p&gt;
&lt;p&gt;Platform engineering presents a fundamental tension: we want to enable developer velocity while maintaining security and compliance. Every platform team faces the same question: how do you give teams the freedom to deploy infrastructure quickly without compromising on safety, security, or organizational standards? The answer isn&amp;rsquo;t to choose between speed and safety, but rather to embrace &lt;strong&gt;automated guardrails&lt;/strong&gt; powered by policy as code that make both possible simultaneously.&lt;/p&gt;
&lt;div class="note note-tip"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-lightbulb-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;strong&gt;Want hands-on experience?&lt;/strong&gt; Access the &lt;a href="https://github.com/pulumi/workshops/tree/main/idp-component-policies"&gt;complete demo code&lt;/a&gt; and &lt;a href="https://github.com/pulumi/workshops/tree/main/idp-component-policies/demo-policies"&gt;policy examples&lt;/a&gt; from this workshop.&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="the-platform-engineering-challenge-speed-vs-safety"&gt;The Platform Engineering Challenge: Speed vs. Safety&lt;/h2&gt;
&lt;p&gt;Let me tell you a story that perfectly captures the platform engineering dilemma. Statsig, a fast-growing feature flag platform processing an incredible 2 trillion events daily, had one infrastructure engineer named Jason who handled all infrastructure requests. Everything worked smoothly until Jason went on parental leave. Suddenly, the entire engineering organization ground to a halt. Infrastructure requests piled up, deployments slowed to a crawl, and the team faced a stark realization: their entire infrastructure capability depended on a single person.&lt;/p&gt;
&lt;p&gt;This crisis forced them to confront a fundamental question that every growing engineering team eventually faces: How do you enable self-service infrastructure without sacrificing security, compliance, or operational stability? The answer wasn&amp;rsquo;t to hire more Jasons or to lock down infrastructure even tighter. Instead, they discovered that deployment guardrails with policy as code could transform their infrastructure conversation from &amp;ldquo;talk to the infrastructure person&amp;rdquo; to &amp;ldquo;ship with confidence, knowing guardrails will catch any issues.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Think of it like building a system of roads with guardrails. When you create well-designed infrastructure components with proper safety barriers, teams can drive fast and confidently, knowing they&amp;rsquo;re protected from going off the cliff. The guardrails don&amp;rsquo;t slow them down; they enable speed by removing fear.&lt;/p&gt;
&lt;h2 id="understanding-platform-engineering-layers"&gt;Understanding Platform Engineering Layers&lt;/h2&gt;
&lt;p&gt;Before we dive into implementing guardrails, we need to understand the architecture of a modern platform and where policies fit within it. Think of your platform as having three distinct layers, each with different security and access requirements.&lt;/p&gt;
&lt;p&gt;At the foundation, you have your &lt;strong&gt;Layer 1: Foundational Infrastructure&lt;/strong&gt;, which includes security controls, shared networking, and identity management systems like OIDC and IAM. Platform teams typically manage this layer with strict access controls because it forms the security backbone of your entire infrastructure.&lt;/p&gt;
&lt;p&gt;Building on that foundation is &lt;strong&gt;Layer 2: Shared Infrastructure&lt;/strong&gt;, which encompasses VPCs, compute platforms, and load balancers. These are standardized components that teams can use but with some room for customization based on specific needs.&lt;/p&gt;
&lt;p&gt;The most dynamic layer is &lt;strong&gt;Layer 3: The Workloads Layer&lt;/strong&gt;, where deployable artifacts, pipelines, and operability tools live. This is where the rubber meets the road, where most self-service activity happens and, consequently, where guardrails become absolutely critical. Developers need maximum freedom at this layer to innovate and ship quickly, but this freedom also introduces the most risk. It&amp;rsquo;s precisely here that policy-driven guardrails prove their worth, enabling that freedom while automatically preventing dangerous configurations.&lt;/p&gt;
&lt;h2 id="what-are-deployment-guardrails"&gt;What Are Deployment Guardrails?&lt;/h2&gt;
&lt;p&gt;Deployment guardrails are automated policies that act as your infrastructure&amp;rsquo;s safety net. Rather than relying on manual reviews or hoping developers remember all the security requirements, guardrails automatically prevent misconfigurations before they reach production. They enforce security standards without human intervention, guide developers toward best practices through immediate feedback, and enable safe self-service by catching issues at the earliest possible moment.&lt;/p&gt;
&lt;p&gt;A helpful analogy is to think of guardrails like type checking in programming languages. Just as TypeScript doesn&amp;rsquo;t restrict your ability to write JavaScript but rather catches type errors before runtime, deployment guardrails don&amp;rsquo;t limit your infrastructure creativity. They simply ensure you&amp;rsquo;re following secure patterns and catch potentially dangerous configurations before they cause problems in production.&lt;/p&gt;
&lt;h2 id="introducing-pulumi-crossguard-policy-as-code"&gt;Introducing Pulumi CrossGuard: Policy as Code&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/crossguard"&gt;Pulumi CrossGuard&lt;/a&gt; is Pulumi&amp;rsquo;s policy as code framework that brings the same engineering rigor to compliance and security that you apply to your application code. Instead of maintaining policy documents in wikis or relying on manual reviews, you can write policies in familiar programming languages like &lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/crossguard/get-started#writing-policies-in-python"&gt;Python&lt;/a&gt;, &lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/crossguard/get-started#writing-policies-in-typescript"&gt;TypeScript&lt;/a&gt;, or Go. These policies then enforce themselves across all your cloud resources and providers, running at different stages of the deployment lifecycle and integrating seamlessly with your CI/CD pipelines for automated enforcement.&lt;/p&gt;
&lt;h3 id="key-policy-types"&gt;Key Policy Types&lt;/h3&gt;
&lt;p&gt;CrossGuard supports two fundamental types of policies, each serving different validation needs:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/crossguard/core-concepts#resource-validation"&gt;Resource Policies&lt;/a&gt;&lt;/strong&gt;: Validate individual resources&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;restrict_dangerous_ports&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ResourceValidationArgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;report_violation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ReportViolation&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;aws:lb/targetGroup:TargetGroup&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;port&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;dangerous_ports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;22&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;23&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;3389&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dangerous_ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;report_violation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Dangerous port detected. Avoid using SSH, Telnet, or RDP ports.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/crossguard/core-concepts#stack-validation"&gt;Stack Policies&lt;/a&gt;&lt;/strong&gt;: Validate relationships across resources&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_microservice_encryption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;StackValidationArgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;report_violation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ReportViolation&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;microservice_components&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;s3_buckets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;custom:infrastructure:Microservice&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;microservice_components&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;aws:s3/bucket:Bucket&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;s3_buckets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;microservice_components&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;s3_buckets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;s3_buckets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;encryption&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;serverSideEncryptionConfiguration&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;encryption&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;report_violation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;S3 bucket must have encryption enabled when used with microservice components.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="building-practical-guardrails-real-world-examples"&gt;Building Practical Guardrails: Real-World Examples&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s walk through building guardrails for a microservice platform, starting with the component from our &lt;a href="https://www.pulumi.com/blog/golden-paths-infrastructure-components-and-templates/"&gt;previous workshop&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="example-1-port-security-policy"&gt;Example 1: Port Security Policy&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: Developers might accidentally expose services on dangerous ports like SSH (22) or RDP (3389).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: A policy that blocks dangerous port configurations.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pulumi_policy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;policy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;restrict_dangerous_ports_validation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;report_violation&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;Prevent services from running on dangerous ports.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;aws:lb/targetGroup:TargetGroup&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;port&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;dangerous_ports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;22&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;23&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;3389&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;5432&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;3306&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dangerous_ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;report_violation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Port &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is not allowed. This port is commonly used for &amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;administrative services and should not be exposed via load balancer.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;restrict_dangerous_ports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResourceValidationPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;restrict-dangerous-ports&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Prevent services from using dangerous ports&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;validate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;restrict_dangerous_ports_validation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;enforcement_level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EnforcementLevel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MANDATORY&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="example-2-resource-limits-policy"&gt;Example 2: Resource Limits Policy&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: Teams might request oversized resources, leading to cost overruns.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: A policy that enforces reasonable resource limits with advisory warnings.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;limit_memory_validation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;report_violation&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;Limit memory allocation for microservices.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;custom:infrastructure:Microservice&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Extract memory from component tags or properties&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;memory_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;memory&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;512Mi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Parse memory value (assuming format like &amp;#34;2Gi&amp;#34;, &amp;#34;1024Mi&amp;#34;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;memory_str&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Gi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;memory_gb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory_str&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;memory_gb&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;report_violation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Memory allocation of &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;memory_str&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; exceeds recommended limit of 1Gi &amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;for microservices. Consider optimizing your application or &amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;contact the platform team for exceptions.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;limit_memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResourceValidationPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;limit-microservice-memory&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Enforce reasonable memory limits for microservices&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;validate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;limit_memory_validation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;enforcement_level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EnforcementLevel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ADVISORY&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="example-3-cross-resource-security-policy"&gt;Example 3: Cross-Resource Security Policy&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: When microservices use S3 buckets, encryption should be mandatory.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: A stack policy that validates encryption across related resources.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;microservice_s3_encryption_validation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;report_violation&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;Ensure S3 buckets used with microservices have encryption enabled.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;microservice_resources&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;s3_buckets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Collect relevant resources&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;custom:infrastructure:Microservice&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;microservice_resources&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;aws:s3/bucket:Bucket&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;s3_buckets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# If we have both microservices and S3 buckets, check encryption&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;microservice_resources&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;s3_buckets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;s3_buckets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;sse_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;serverSideEncryptionConfiguration&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;sse_config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;report_violation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;S3 bucket &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39; must have server-side encryption &amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;enabled when used with microservice components.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;microservice_s3_encryption&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StackValidationPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;microservice-s3-encryption&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Ensure S3 buckets used with microservices are encrypted&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;validate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;microservice_s3_encryption_validation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;enforcement_level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EnforcementLevel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MANDATORY&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="policy-enforcement-models"&gt;Policy Enforcement Models&lt;/h2&gt;
&lt;p&gt;Pulumi CrossGuard supports multiple &lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/crossguard/core-concepts#enforcement-levels"&gt;enforcement models&lt;/a&gt; to fit different workflows, and understanding when to use each model is crucial for effective policy implementation.&lt;/p&gt;
&lt;h3 id="the-preventative-model"&gt;The Preventative Model&lt;/h3&gt;
&lt;p&gt;The most common approach is the preventative model, where policies run before resources are created. This gives you fast feedback and prevents bad deployments from ever reaching the cloud. When you run &lt;code&gt;pulumi preview&lt;/code&gt;, policies are evaluated immediately, and if violations are found during &lt;code&gt;pulumi up&lt;/code&gt;, the deployment is blocked. This model works particularly well for validating port configurations, resource limits, and other settings that you can check without needing to see the actual deployed resource.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Policies run automatically during preview and deployment&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pulumi preview &lt;span class="c1"&gt;# Policies evaluated here&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pulumi up &lt;span class="c1"&gt;# Policies block deployment if violations found&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="the-detective-model"&gt;The Detective Model&lt;/h3&gt;
&lt;p&gt;Sometimes you need to validate the actual cloud state, including auto-generated values like ARNs or IP addresses. That&amp;rsquo;s where the detective model comes in. These policies run after resources are created, making them perfect for compliance scanning and drift detection. While they can&amp;rsquo;t prevent initial deployment of non-compliant resources, they excel at catching configuration drift and validating properties that only exist after deployment.&lt;/p&gt;
&lt;h3 id="cicd-integration"&gt;CI/CD Integration&lt;/h3&gt;
&lt;p&gt;The third model integrates policies directly into your deployment pipeline. This ensures consistent enforcement across all teams and creates natural deployment gates. For example, you might configure &lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/continuous-delivery/github-actions"&gt;GitHub Actions&lt;/a&gt; to run policy validation on every pull request, blocking merges if violations are found. This approach combines the best of both worlds: early feedback during development and guaranteed enforcement before production.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# GitHub Actions example&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Run Policy Validation&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; pulumi preview --policy-pack ./policies
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; if [ $? -ne 0 ]; then
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;Policy violations found. Deployment blocked.&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; exit 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; fi&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="policy-remediation-beyond-detection"&gt;Policy Remediation: Beyond Detection&lt;/h2&gt;
&lt;p&gt;Modern policy frameworks don&amp;rsquo;t just detect violations; they can &lt;strong&gt;&lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/crossguard/core-concepts#remediation-policies"&gt;automatically fix&lt;/a&gt;&lt;/strong&gt; them:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;auto_tag_resources&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;report_violation&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;Automatically add required tags to resources.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;aws:s3/bucket:Bucket&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;tags&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Department&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Instead of just reporting, fix the issue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;tags&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;tags&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;tags&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Department&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Engineering&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="c1"&gt;# Return modified properties&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt; &lt;span class="c1"&gt;# No changes needed&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;auto_tag_policy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResourceValidationPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;auto-tag-resources&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Automatically add required tags&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;validate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;auto_tag_resources&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;enforcement_level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EnforcementLevel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REMEDIATE&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="server-side-policy-enforcement"&gt;Server-Side Policy Enforcement&lt;/h2&gt;
&lt;p&gt;For enterprise deployments, Pulumi provides &lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/crossguard/get-started#enforcing-a-policy-pack"&gt;server-side policy enforcement&lt;/a&gt; that ensures policies can&amp;rsquo;t be bypassed. The process starts by publishing your policies to your Pulumi organization with &lt;code&gt;pulumi policy publish ./my-policies&lt;/code&gt;. Once published, you can &lt;a href="https://www.pulumi.com/docs/iac/crossguard/configuration/#using-pulumi-cloud"&gt;create policy groups&lt;/a&gt; that combine multiple policies with specific enforcement levels, targeting particular stacks or environments while configuring exceptions for special cases. The beauty of this approach is that policies run automatically without requiring CLI flags, providing consistent governance across your entire organization without relying on developers to remember to include policy packs in their commands.&lt;/p&gt;
&lt;h2 id="compliance-ready-policies"&gt;Compliance-Ready Policies&lt;/h2&gt;
&lt;p&gt;While custom policies address your specific organizational needs, compliance requirements often follow industry standards. Pulumi provides hundreds of &lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/crossguard/compliance-ready-policies"&gt;pre-built policies&lt;/a&gt; for common compliance frameworks:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PolicyManager&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@pulumi/policy&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;policyManager&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@pulumi/compliance-ready-policies/policy-manager&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;PolicyPack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;aws-compliance-ready-policies-typescript&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;policies&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;policyManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selectPolicies&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;vendors&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;aws&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ec2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;s3&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;severities&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;medium&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;high&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;critical&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;topics&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;encryption&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;frameworks&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pcidss&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;mandatory&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This automatically includes policies for major compliance frameworks like &lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/crossguard/compliance-ready-policies#frameworks"&gt;PCI DSS&lt;/a&gt; for payment card industry standards, &lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/crossguard/compliance-ready-policies#frameworks"&gt;SOC 2&lt;/a&gt; for security and compliance controls, &lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/crossguard/compliance-ready-policies#frameworks"&gt;ISO 27001&lt;/a&gt; for information security management, and &lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/crossguard/compliance-ready-policies#frameworks"&gt;CIS Benchmarks&lt;/a&gt; for security configuration standards.&lt;/p&gt;
&lt;div class="rounded-lg bg-violet-50 p-6 my-8"&gt;
&lt;p class="heading-4 m-0 mb-3 flex items-center gap-1.5"&gt;Add guardrails with policy as code&lt;/p&gt;
&lt;div class="body-base m-0 text-gray-950"&gt;Write policies in Python or TypeScript, enforce them across every deployment, and give teams self-service infrastructure that stays secure and compliant.&lt;/div&gt;
&lt;a href="https://www.pulumi.com/docs/insights/policy/" data-track="blog-body-cta" class="btn btn-primary mt-4"&gt;
Get started
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--regular size-4" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-arrow-right-regular"/&gt;&lt;/svg&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id="best-practices-for-policy-implementation"&gt;Best Practices for Policy Implementation&lt;/h2&gt;
&lt;p&gt;After implementing policies at dozens of organizations, we&amp;rsquo;ve learned that successful policy adoption follows predictable patterns. The key is to adopt a product mindset: think of your policies as a product serving your development teams, not as bureaucratic rules imposed from above. Start small and iterate based on real feedback rather than trying to implement a comprehensive policy framework from day one.&lt;/p&gt;
&lt;h3 id="start-small-and-iterate"&gt;Start Small and Iterate&lt;/h3&gt;
&lt;p&gt;Begin with just two or three critical policies that address your most pressing risks. Use &lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/crossguard/core-concepts#enforcement-levels"&gt;advisory enforcement&lt;/a&gt; initially, which warns developers about violations but doesn&amp;rsquo;t block deployments. This gives your team time to understand and adapt to the policies. Only after gathering feedback and refining the policies should you graduate to mandatory enforcement.&lt;/p&gt;
&lt;h3 id="provide-clear-actionable-error-messages"&gt;Provide Clear, Actionable Error Messages&lt;/h3&gt;
&lt;p&gt;Nothing frustrates developers more than cryptic policy violations. Your error messages should explain not just what&amp;rsquo;s wrong, but how to fix it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;good_error_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;report_violation&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;violation_detected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;report_violation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;S3 bucket encryption is required for compliance. &amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Add serverSideEncryptionConfiguration to fix this. &amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;See: https://docs.company.com/s3-encryption&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="embrace-progressive-enforcement"&gt;Embrace Progressive Enforcement&lt;/h3&gt;
&lt;p&gt;Think of enforcement levels as a dial, not a switch. Start with &lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/crossguard/core-concepts#advisory"&gt;advisory&lt;/a&gt; mode to warn about issues, move to &lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/crossguard/core-concepts#mandatory"&gt;mandatory&lt;/a&gt; to block deployments, and eventually implement &lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/crossguard/core-concepts#remediation-policies"&gt;remediation&lt;/a&gt; to automatically fix common issues. This progression gives teams time to adapt while gradually raising the security bar.&lt;/p&gt;
&lt;h3 id="test-your-policies-thoroughly"&gt;Test Your Policies Thoroughly&lt;/h3&gt;
&lt;p&gt;Policies are code, and like any code, they need testing. Write unit tests for your policies to ensure they catch violations correctly and don&amp;rsquo;t produce false positives:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Test policies with unit tests&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_dangerous_port_policy&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Mock resource with port 22&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_mock_args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;aws:lb/targetGroup:TargetGroup&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;port&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;violations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;run_policy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;restrict_dangerous_ports&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;violations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;dangerous port&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;violations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="document-escape-hatches"&gt;Document Escape Hatches&lt;/h3&gt;
&lt;p&gt;No matter how well-designed your policies are, there will always be edge cases. Rather than forcing teams to work around policies in dangerous ways, provide clear, documented escape hatches. Create processes for requesting exceptions, handling emergency exemptions, and appealing for policy changes. As one platform engineer put it, you need &amp;ldquo;guardrails outside the guardrails&amp;rdquo; because platform adoption will never be 100%, and that&amp;rsquo;s okay. This transparency builds trust and ensures policies remain effective enablers rather than bureaucratic obstacles to circumvent.&lt;/p&gt;
&lt;h2 id="real-world-success-statsigs-transformation"&gt;Real-World Success: Statsig&amp;rsquo;s Transformation&lt;/h2&gt;
&lt;p&gt;Remember Statsig&amp;rsquo;s infrastructure crisis when Jason went on parental leave? Let&amp;rsquo;s look at how they transformed their platform using the guardrails approach we&amp;rsquo;ve been discussing. After implementing policy-driven guardrails, their infrastructure story completely changed.&lt;/p&gt;
&lt;p&gt;As Tyrone Wong, Infrastructure Engineer at Statsig, explains: &amp;ldquo;We had a single infra owner. Parental leave forced us to build self-service, and that&amp;rsquo;s when everything clicked. Developers open a PR, Pulumi previews the change right in the PR, and CI blocks risky changes. That&amp;rsquo;s how you move fast safely. The magic was turning &amp;rsquo;talk to the infra person&amp;rsquo; into &amp;lsquo;ship with guardrails.&amp;rsquo;&amp;rdquo;&lt;/p&gt;
&lt;p&gt;The results speak for themselves. Deployment times dropped from 1.5 weeks to mere minutes. The infrastructure ticket queue that once haunted their Slack channels disappeared entirely. Security actually improved through automated policy enforcement, eliminating the human error factor. Most importantly, developer satisfaction soared as teams gained the autonomy to ship infrastructure changes without friction or fear.&lt;/p&gt;
&lt;p&gt;When asked if developers were actually using the self-service platform, Tyrone&amp;rsquo;s response was telling: &amp;ldquo;Yeah, people are actually using it because there&amp;rsquo;s changes they wanted to get done. Previously it became this giant effort with lots of meetings, people being concerned, and Google docs about what the changes would be. Now people can just put together a PR, we have our discussions there, see if anything gets flagged, and move forward.&amp;rdquo;&lt;/p&gt;
&lt;h2 id="building-your-policy-strategy"&gt;Building Your Policy Strategy&lt;/h2&gt;
&lt;p&gt;Implementing deployment guardrails isn&amp;rsquo;t a big-bang transformation; it&amp;rsquo;s a journey that unfolds in phases. Based on patterns we&amp;rsquo;ve seen across successful implementations, the first week or two should focus on assessment. Start by understanding your current state, auditing existing infrastructure patterns to identify what teams are actually deploying. Look for common misconfigurations that have caused incidents or near-misses, and document your security and compliance requirements as specific, enforceable rules. Most importantly, survey your developers to understand their pain points with the current infrastructure process, as this assessment forms the foundation for policies that solve real problems rather than creating new ones.&lt;/p&gt;
&lt;p&gt;In weeks three and four, build your foundation by implementing three to five core policies that address your most critical risks. Set up &lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/continuous-delivery"&gt;CI/CD integration&lt;/a&gt; so policies run automatically on every pull request, starting with advisory enforcement to gather feedback without blocking deployments. Create clear documentation and runbooks that explain not just what the policies do, but why they exist and how to work with them.&lt;/p&gt;
&lt;p&gt;By the second month, you&amp;rsquo;re ready to expand. Add &lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/crossguard/compliance-ready-policies"&gt;compliance-specific policies&lt;/a&gt; for regulatory requirements and implement &lt;a href="https://www.pulumi.com/docs/iac/crossguard/get-started/#enforcing-a-policy-pack"&gt;server-side enforcement&lt;/a&gt; to ensure policies can&amp;rsquo;t be bypassed. Create formal processes for policy exemptions and exceptions, and begin measuring policy effectiveness through metrics like violation rates and remediation times.&lt;/p&gt;
&lt;p&gt;Remember that policy implementation is never &amp;ldquo;done.&amp;rdquo; Continuously monitor violation patterns to identify areas where policies might be too strict or too lenient. Refine policies based on developer feedback and incident data, add &lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/crossguard/core-concepts#remediation-policies"&gt;automated remediation&lt;/a&gt; for common violations to reduce manual fixes, and gradually expand coverage to new services and teams using lessons learned from early adopters.&lt;/p&gt;
&lt;h2 id="measuring-policy-success"&gt;Measuring Policy Success&lt;/h2&gt;
&lt;p&gt;You can&amp;rsquo;t improve what you don&amp;rsquo;t measure, and policy effectiveness is no exception. Successful teams track metrics across three critical dimensions to validate their policy strategy.&lt;/p&gt;
&lt;p&gt;From a security perspective, start by monitoring your policy violation rate: the percentage of deployments that trigger policy warnings or blocks. This tells you whether your policies are catching real issues or creating unnecessary friction. Track time to remediation to understand how quickly teams fix violations when they occur. Most importantly, measure the reduction in security incidents caused by misconfigurations. If your policies aren&amp;rsquo;t reducing incidents, they&amp;rsquo;re not addressing the right risks.&lt;/p&gt;
&lt;p&gt;Security is only half the equation, though. The developer experience dimension is equally important. Monitor deployment velocity to ensure policies aren&amp;rsquo;t slowing teams down. Regular developer satisfaction surveys reveal whether teams see policies as helpful guardrails or annoying obstacles. Track support ticket volume; successful policy implementation should reduce infrastructure-related requests as teams become more self-sufficient.&lt;/p&gt;
&lt;p&gt;Finally, don&amp;rsquo;t forget to measure the operational health of your policy system itself. Policy coverage shows what percentage of your resources are protected by policies, while the exemption rate reveals whether policies are practical or if teams constantly need exceptions. Track remediation automation to see how many violations are fixed automatically versus requiring manual intervention. Together, these metrics paint a complete picture of your policy program&amp;rsquo;s health and help you continuously improve your approach.&lt;/p&gt;
&lt;h2 id="common-pitfalls-and-how-to-avoid-them"&gt;Common Pitfalls and How to Avoid Them&lt;/h2&gt;
&lt;p&gt;Even with the best intentions, policy implementations can go wrong. Through years of helping organizations implement policy as code, we&amp;rsquo;ve identified four critical pitfalls that can derail your efforts.&lt;/p&gt;
&lt;p&gt;The first and most common trap is over-engineering policies. It&amp;rsquo;s tempting to create sophisticated policies that handle every edge case, but overly complex policies are hard to understand and maintain. Teams won&amp;rsquo;t trust what they don&amp;rsquo;t understand. Instead, start with simple, clear policies that address specific risks. Use descriptive naming and provide examples of both compliant and non-compliant configurations. Remember, a simple policy that everyone follows is better than a perfect policy that everyone circumvents.&lt;/p&gt;
&lt;p&gt;Equally dangerous is falling into &amp;ldquo;policy theater&amp;rdquo;: implementing policies because you think you should, not because they address real risks. This creates friction without improving security. Every policy should trace back to an actual incident, security requirement, or compliance need. If you can&amp;rsquo;t explain why a policy exists with a concrete example, it probably shouldn&amp;rsquo;t exist.&lt;/p&gt;
&lt;p&gt;Poor developer experience is another common failure point. Nothing kills policy adoption faster than cryptic error messages and unclear remediation steps. When a developer hits a policy violation at 5 PM on a Friday, they need clear guidance on how to fix it. Invest heavily in documentation, helpful error messages, and examples. Consider your policies&amp;rsquo; error messages as part of your product&amp;rsquo;s user experience.&lt;/p&gt;
&lt;p&gt;Finally, inadequate testing can erode trust quickly. Policies that break legitimate use cases will be worked around or disabled. Before enforcing any policy, test it thoroughly with real scenarios from your production workloads. Run policies in advisory mode first to catch false positives, and have teams attempt real deployments with policies enabled to ensure they don&amp;rsquo;t block valid configurations.&lt;/p&gt;
&lt;h2 id="the-future-of-policy-as-code"&gt;The Future of Policy as Code&lt;/h2&gt;
&lt;p&gt;As infrastructure becomes increasingly complex and distributed, policy as code is evolving beyond simple rule enforcement to become an intelligent layer that understands context and intent. Three major trends are shaping this evolution.&lt;/p&gt;
&lt;p&gt;First, we&amp;rsquo;re seeing the emergence of AI-enhanced policies with smart detection systems that use machine learning to identify anomalies that rule-based policies might miss. These systems learn from your infrastructure patterns and can provide contextual recommendations that adapt based on actual usage. Even more exciting is predictive enforcement: imagine policies that can identify risky patterns before they become violations, guiding developers away from problems they haven&amp;rsquo;t encountered yet.&lt;/p&gt;
&lt;p&gt;Second, the multi-cloud reality is driving the need for universal governance. Organizations need policies that work seamlessly across &lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/crossguard/compliance-ready-policies-aws"&gt;AWS&lt;/a&gt;, &lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/crossguard/compliance-ready-policies-azure"&gt;Azure&lt;/a&gt;, and &lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/crossguard/compliance-ready-policies-gcp"&gt;GCP&lt;/a&gt;, with federated enforcement that maintains consistency across multiple cloud accounts and regions. Compliance automation is also maturing, with systems that automatically collect evidence for audits and generate compliance reports without manual intervention.&lt;/p&gt;
&lt;p&gt;Finally, policy enforcement is moving closer to where developers actually work. IDE integration will soon provide real-time policy feedback as you write infrastructure code, catching issues before you even attempt to deploy. Self-service exemption workflows will let developers request and receive policy exceptions through automated approval processes. Perhaps most intriguingly, we&amp;rsquo;re seeing the development of learning policies: systems that improve and adapt based on developer feedback and usage patterns, becoming more helpful over time rather than more restrictive.&lt;/p&gt;
&lt;h2 id="conclusion-enabling-safe-self-service-at-scale"&gt;Conclusion: Enabling Safe Self-Service at Scale&lt;/h2&gt;
&lt;p&gt;We started this post with a fundamental tension in platform engineering: the need for both speed and safety. Through the lens of Statsig&amp;rsquo;s transformation and the technical deep-dive into Pulumi CrossGuard, we&amp;rsquo;ve seen that this isn&amp;rsquo;t actually a tension that needs resolving. It&amp;rsquo;s a false dichotomy that policy as code eliminates entirely.&lt;/p&gt;
&lt;p&gt;The key insight from successful platform teams like Statsig is that guardrails don&amp;rsquo;t restrict freedom; they enable it. When developers know that automated policies will catch dangerous configurations, they gain the confidence to move fast and experiment. When platform teams know that policies automatically enforce security and compliance standards, they can focus on building better platforms instead of reviewing every change. This is the magic of policy as code: it transforms infrastructure governance from a bottleneck into an accelerator.&lt;/p&gt;
&lt;p&gt;But perhaps the most important lesson is that policy as code isn&amp;rsquo;t about saying &amp;ldquo;no&amp;rdquo; to developers. It&amp;rsquo;s about intelligent automation that makes the secure path the path of least resistance. It&amp;rsquo;s about catching mistakes before they become incidents. It&amp;rsquo;s about encoding your organization&amp;rsquo;s hard-won knowledge into systems that help every developer benefit from that experience.&lt;/p&gt;
&lt;p&gt;As you embark on your own journey to implement deployment guardrails, remember that perfection isn&amp;rsquo;t the goal; progress is. Start small, iterate based on feedback, and gradually expand your coverage. Your developers will thank you for the clarity and confidence that comes with well-designed guardrails, and your security team will sleep better knowing that policies are enforced automatically and consistently.&lt;/p&gt;
&lt;p&gt;The path from manual reviews to automated guardrails is well-traveled and well-documented. Our &lt;a href="https://github.com/pulumi/workshops/tree/main/idp-component-policies/demo-policies"&gt;complete policy examples&lt;/a&gt; provide real-world implementations you can adapt to your needs, while the &lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/crossguard"&gt;CrossGuard documentation&lt;/a&gt; offers deep technical details for advanced use cases. If you&amp;rsquo;re on AWS, &lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/crossguard/awsguard"&gt;AWSGuard&amp;rsquo;s pre-built policies&lt;/a&gt; offer immediate value, and our &lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/crossguard/compliance-ready-policies"&gt;compliance-ready policy catalog&lt;/a&gt; addresses specific regulatory requirements.&lt;/p&gt;
&lt;p&gt;The future of infrastructure management isn&amp;rsquo;t about choosing between developer autonomy and operational control. It&amp;rsquo;s about using policy as code to achieve both, creating platforms that are simultaneously powerful and safe, flexible and compliant, fast and secure.&lt;/p&gt;
&lt;p&gt;In our next post, we&amp;rsquo;ll explore Day 2 Platform Operations, diving into how to maintain infrastructure compliance after deployment and automatically remediate configuration drift. Because getting to production is just the beginning; keeping your infrastructure secure and compliant over time is where the real challenge lies.&lt;/p&gt;
&lt;section class="my-16"&gt;
&lt;div class="container mx-auto max-w-5xl md:flex my-8 align-top justify-center text-center"&gt;
&lt;div class="md:w3-/12 mr-4"&gt;
&lt;h2 class="no-anchor"&gt;Get Started with Pulumi&lt;/h2&gt;
&lt;p class="text-gray-700 dark:text-gray-300 text-sm"&gt;Use Pulumi's open-source SDK to create, deploy, and manage infrastructure on any cloud.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="mx-auto max-w-4xl"&gt;
&lt;div class="tiles flex-wrap mt-4"&gt;
&lt;div class="pb-4 md:pr-4 md:w-1/2"&gt;
&lt;a class="tile p-8" href="https://www.pulumi.com/docs/iac/get-started/aws/"&gt;
&lt;img class="h-10 mx-auto" src="https://www.pulumi.com/logos/tech/aws.svg" alt="AWS" /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;div class="pb-4 md:w-1/2"&gt;
&lt;a class="tile p-8" href="https://www.pulumi.com/docs/iac/get-started/azure/"&gt;
&lt;img class="h-10 mx-auto" src="https://www.pulumi.com/logos/tech/azure.svg" alt="Azure" /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;div class="pb-4 md:pr-4 md:w-1/2"&gt;
&lt;a class="tile p-8" href="https://www.pulumi.com/docs/iac/get-started/gcp/"&gt;
&lt;img class="h-10 mx-auto" src="https://www.pulumi.com/logos/tech/gcp.svg" alt="Google Cloud" /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;div class="pb-4 md:w-1/2"&gt;
&lt;a class="tile p-8" href="https://www.pulumi.com/docs/iac/get-started/kubernetes/"&gt;
&lt;img class="h-10 mx-auto" src="https://www.pulumi.com/logos/tech/k8s.svg" alt="Kubernetes" /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;</description><author>Adam Gordon Bell</author><category>internal-developer-platform</category><category>platform-engineering</category><category>policy-as-code</category><category>crossguard</category><category>compliance</category><category>security</category><category>self-service</category><category>guardrails</category></item><item><title>Governance as an Enabler: Scaling Safely and Confidently</title><link>https://www.pulumi.com/blog/platform-engineering-pillars-7/</link><pubDate>Tue, 17 Jun 2025 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/platform-engineering-pillars-7/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/platform-engineering-pillars-7/index.png" /&gt;
&lt;p&gt;In previous articles in this series, we&amp;rsquo;ve explored how &lt;a href="https://www.pulumi.com/blog/series/platform-engineering-pillars/"&gt;platform engineering&lt;/a&gt; transforms infrastructure chaos into consistent provisioning, empowers engineering teams through self-service infrastructure, optimizes workflows, embeds security directly into your platform, and provides observability as a superpower. Each pillar builds upon the previous ones, creating a cohesive foundation that accelerates innovation and productivity.&lt;/p&gt;
&lt;p&gt;But as your platform scales, new challenges inevitably emerge. You&amp;rsquo;ve empowered engineering teams with self-service infrastructure, streamlined workflows, and embedded security directly into your platform. But as your platform scales, new challenges emerge: How do you ensure consistency, compliance, and cost control without slowing your teams down?&lt;/p&gt;
&lt;p&gt;In this article, we&amp;rsquo;ll explore how Platform Engineering transforms governance from a manual, bureaucratic process into an automated, built-in enabler, helping your organization scale safely and confidently. By embedding governance directly into your platform, you can maintain control, ensure compliance, and manage costs effectively, all while preserving the autonomy and speed your engineering teams have come to expect.&lt;/p&gt;
&lt;h2 id="the-problem-governance-as-a-manual-bottleneck"&gt;The Problem: Governance as a Manual Bottleneck&lt;/h2&gt;
&lt;span style="width: 225px; float: right; margin-left: 20px;"&gt;
&lt;span style="text-align:center"&gt;
&lt;img src="frustrated.png" width="200px" alt="frustrated expression"&gt;
&lt;figcaption&gt;
&lt;i&gt;Dealing with manual compliance checks and red tape&lt;/i&gt;
&lt;/figcaption&gt;
&lt;/span&gt;
&lt;/span&gt;
&lt;p&gt;With increased team autonomy and self-service capabilities, how do you ensure consistency, compliance, and cost control across your entire organization?&lt;/p&gt;
&lt;p&gt;Governance often feels like a necessary evil: manual, bureaucratic, and slow. Application teams see it as red tape, while operations teams struggle to maintain control. Manual compliance checks, lengthy audits, and unclear or inconsistent policies create friction and frustration. Teams may bypass governance processes entirely, leading to shadow IT, inconsistent resource configurations, and hidden risks.&lt;/p&gt;
&lt;p&gt;The consequences of manual, bureaucratic governance are clear:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Increased compliance risks and audit failures:&lt;/strong&gt; Without automated enforcement, compliance becomes reactive and error-prone, increasing the likelihood of regulatory violations and audit findings.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unpredictable cloud costs and budget overruns:&lt;/strong&gt; Without clear guardrails, self-service infrastructure can lead to resource sprawl, wasted resources, and unexpected cloud bills.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reduced team autonomy and slower innovation:&lt;/strong&gt; Manual governance processes reintroduce bottlenecks, slowing down deployments and undermining the agility your platform was designed to achieve.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="the-solution-embedding-governance-into-your-platform"&gt;The Solution: Embedding Governance into Your Platform&lt;/h2&gt;
&lt;span style="width: 225px; float: left; margin-right: 20px;"&gt;
&lt;span style="text-align:center"&gt;
&lt;img src="satisfied.png" width="200px" alt="satisfied expression"&gt;
&lt;figcaption&gt;
&lt;i&gt;Successfully scaling safely with embedded governance&lt;/i&gt;
&lt;/figcaption&gt;
&lt;/span&gt;
&lt;/span&gt;
&lt;p&gt;Governance should live inside your platform, not off to the side as a separate process. To make that happen, build these four capabilities into your IDP:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Policy-as-Code for Automated Compliance:&lt;/strong&gt; Declare rules (like approved regions or required tags) as code. The platform enforces them whenever infrastructure is created or updated, so compliance happens automatically.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Platform-Level RBAC for Permission Boundaries:&lt;/strong&gt; Decide who can act on projects, stacks, and templates before any cloud credentials run. This early check prevents unauthorized requests from ever reaching cloud provider.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Audit Logs and Drift Detection for Real-Time Visibility:&lt;/strong&gt; Record every deployment, who ran it, and what changed. Continuously compare live infrastructure to the desired state in code. If someone bypasses approved processes, the platform flags it and alerts the team.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Resource Lifecycle and Deployment Controls:&lt;/strong&gt; Automatically retire idle environments after a set time (Resource TTLs) so forgotten test clusters don’t rack up bills. Also if needed, gate production changes behind lightweight approval workflows. Routine dev or staging updates roll out instantly, but high impact production changes wait until a reviewer signs off.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s dive into each of these.&lt;/p&gt;
&lt;h3 id="a-policy-as-code-for-compliance-and-operational-standards-automating-trust-and-consistency"&gt;A. Policy-as-Code for Compliance and Operational Standards: Automating Trust and Consistency&lt;/h3&gt;
&lt;p&gt;An engineering team is ready to launch a new service. They’ve tested it and everything looks good, until the deployment fails. Not because of a bug. Because it’s targeting an unapproved cloud region.&lt;/p&gt;
&lt;p&gt;Now they’re stuck. A compliance review kicks off. Slack threads fly. A ticket gets filed. What should’ve been a smooth release turns into a delay, all because of a policy someone missed.&lt;/p&gt;
&lt;p&gt;Policy-as-code prevents this.&lt;/p&gt;
&lt;p&gt;When teams deploy something that breaks the rules (like using an unapproved region), the platform blocks it automatically. The error shows up right away, with a clear message. Nothing gets provisioned, and nobody has to file a ticket.&lt;/p&gt;
&lt;p&gt;If you’re already using intent-based components (“I need a Java service with Kafka and PostgreSQL”), most details are handled for you: tags, regions, naming. But people still override things. That’s why policy-as-code matters.&lt;/p&gt;
&lt;p&gt;Think of it as a safety net. A menu of components handle the defaults. Policies catch anything that slips through. Together, they keep your platform consistent without slowing anyone down.&lt;/p&gt;
&lt;h3 id="b-role-based-access-control-rbac-balancing-autonomy-and-control"&gt;B. Role-Based Access Control (RBAC): Balancing Autonomy and Control&lt;/h3&gt;
&lt;span style="width: 225px; float: right; margin-left: 20px;"&gt;
&lt;span style="text-align:center"&gt;
&lt;img src="blocked.png" width="200px" alt="blocked expression"&gt;
&lt;figcaption&gt;
&lt;i&gt;Stopped by lengthy approval processes&lt;/i&gt;
&lt;/figcaption&gt;
&lt;/span&gt;
&lt;/span&gt;
&lt;p&gt;As your platform grows, managing permissions manually gets messy. If an engineer needs to fix a production issue but doesn’t have access, they file a ticket and wait, sometimes for days. Give developers too many rights, and they might change production by accident. Both options slow teams down and increase risk.&lt;/p&gt;
&lt;p&gt;The fix is an RBAC model built into your platform. First, the platform decides who can deploy, who can publish components, and who can manage templates. This check runs before any cloud credentials are used, so invalid requests get blocked early. Second, the cloud IAM layer controls which API calls are allowed, like creating an EC2 instance or updating a database.&lt;/p&gt;
&lt;p&gt;This pairs well with a two-level intent-based approach. Teams describe what they need (“I want a Python Lambda with an SQS queue”), and the platform enforces access only for users with the right scopes. Everyone gets just enough access to do their job, no more, no less.&lt;/p&gt;
&lt;p&gt;A Platform with RBAC makes permissions clear, reduces mistakes, and keeps everything auditable. Devs move fast, spinning up resources as needed, while strong guardrails stay in place. The result is a scalable, least-privilege model that balances autonomy and control, so your organization can grow safely.&lt;/p&gt;
&lt;h3 id="c-auditability-traceability-and-drift-detection-ensuring-visibility-and-trust"&gt;C. Auditability, Traceability, and Drift Detection: Ensuring Visibility and Trust&lt;/h3&gt;
&lt;span style="width: 225px; float: left; margin-right: 20px;"&gt;
&lt;span style="text-align:center"&gt;
&lt;img src="stressed.png" width="200px" alt="stressed expression"&gt;
&lt;figcaption&gt;
&lt;i&gt;Anxious about audit failures and compliance risks&lt;/i&gt;
&lt;/figcaption&gt;
&lt;/span&gt;
&lt;/span&gt;
&lt;p&gt;An ops engineer spots a production database misbehaving. A quick check shows someone changed its configuration outside the approved workflow. Without an audit trail or drift detection, the team scrambles to figure out who made the change and when. Meanwhile, the incorrect setting stays active, posing a security and compliance risk. No one can fix it without guessing.&lt;/p&gt;
&lt;p&gt;A platform with audit logs records every action: who deployed, when, and what changed. Drift detection watches live infrastructure and compares it to the desired state in code. If someone bypasses the workflow (say, editing a database setting in the console), the platform flags it and alerts:
“User Alice changed max_connections on prod-db-01 at 3:42 PM, which no longer matches the expected state.”
Now the team can pinpoint the change, talk to the right person, and revert or update the code, restoring consistency in minutes, not hours.&lt;/p&gt;
&lt;p&gt;Together, audit logs and drift detection give you real-time visibility into every change. You stop playing detective. You see who did what, when, and how it deviated from code, all in one place. That transparency speeds audits, catches unauthorized changes fast, and builds trust across teams. With automatic traces of every change, your platform scales without surprises.&lt;/p&gt;
&lt;h3 id="d-resource-lifecycle-and-deployment-controls-scaling-responsibly-and-safely"&gt;D. Resource Lifecycle and Deployment Controls: Scaling Responsibly and Safely&lt;/h3&gt;
&lt;p&gt;An engineer spins up a test environment, then walks away. Another pushes a change straight to production without review. The abandoned test cluster runs up cloud costs; the unreviewed prod tweak risks an outage. Without automation, both lead to wasted spend and stressful cleanups.&lt;/p&gt;
&lt;p&gt;A modern platform handles this with &lt;strong&gt;ephemeral environments where possible&lt;/strong&gt; and &lt;strong&gt;approval gates where it matters&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In dev and staging, engineers can move quickly. They can create test or preview environments, often tied to users or pull requests, that shut down automatically after a set time. TTL rules keep things tidy without manual cleanup.&lt;/p&gt;
&lt;p&gt;Production, by contrast, is gated. High-impact changes, like modifying a database schema or adjusting a load balancer, require approval. Before anything is provisioned, sign-off is required. Every approval (or denial) is logged: who, when, and why.&lt;/p&gt;
&lt;p&gt;This setup keeps development fast and flexible, while making production changes deliberate and auditable. Your platform stays clean, cost-effective, and safe without getting in the way.&lt;/p&gt;
&lt;span style="width: 225px; float: right; margin-left: 20px;"&gt;
&lt;span style="text-align:center"&gt;
&lt;img src="overwhelmed.png" width="200px" alt="overwhelmed expression"&gt;
&lt;figcaption&gt;
&lt;i&gt;Drowning in resource sprawl and unexpected costs&lt;/i&gt;
&lt;/figcaption&gt;
&lt;/span&gt;
&lt;/span&gt;
&lt;h2 id="real-world-example-governance-enablement-in-action"&gt;Real-World Example: Governance Enablement in Action&lt;/h2&gt;
&lt;p&gt;An engineering team opens a pull request for a new customer-facing service. The platform spins up a preview environment using a template with secure defaults, pre-approved modules, and policy checks. CI runs tests and policy checks in preview (names, regions, encryption, compliance) so the team catches issues early. If a rule fails (say, an unencrypted database), the PR fails before it reaches main. After the PR merges to main, it deploys to production, confident all policy validations have passed.&lt;/p&gt;
&lt;p&gt;Idle QA environments shut down after 48 hours, so forgotten clusters don’t rack up bills. Sensitive production changes, like updating a load balancer or altering a critical schema, are carefully reviewed via pull request. Once approved, the platform deploys automatically and logs every action. Drift detection flags console edits, letting the team revert or update code in minutes.&lt;/p&gt;
&lt;p&gt;Result: Governance becomes an invisible safety net. Engineers move fast, knowing policy-as-code, RBAC, TTL cleanup, approval gates, and change tracking catch mistakes. Ops stays in control without firefighting or chasing orphan resources. The platform scales safely, balancing freedom with built-in guardrails.&lt;/p&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;h2 id="metrics-measuring-governance-enablement"&gt;Metrics: Measuring Governance Enablement&lt;/h2&gt;
&lt;p&gt;To ensure your governance practices truly empower your organization, it&amp;rsquo;s essential to track clear, actionable metrics. These metrics help you understand the effectiveness of your governance processes, identify areas for improvement, and ensure governance remains frictionless and enabling:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Time Spent on Manual Compliance Checks and Audits&lt;/strong&gt;:
Measure how much time your teams spend manually verifying compliance or performing audits. Effective governance automation should significantly reduce this overhead, freeing teams to focus on higher-value tasks.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Number of Compliance Violations or Audit Findings&lt;/strong&gt;:
Track how frequently compliance violations or audit issues occur. Effective governance should reduce these incidents, demonstrating that automated policies and guardrails are working as intended.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cloud Cost Predictability and Budget Adherence&lt;/strong&gt;:
Monitor how accurately your cloud spending aligns with forecasts and budgets. Good governance practices, such as automated tagging, resource lifecycle management, and cost controls, should improve predictability and reduce unexpected cost overruns.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Engineering Team Satisfaction with Governance Processes&lt;/strong&gt;:
Regularly survey engineering teams to gauge their satisfaction with governance processes. High satisfaction indicates that governance is enabling rather than hindering their workflows.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tracking these metrics helps you continuously improve your platform&amp;rsquo;s governance practices, ensuring they remain effective and frictionless.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;span style="width: 225px; float: left; margin-right: 20px;"&gt;
&lt;span style="text-align:center"&gt;
&lt;img src="confident.png" width="200px" alt="confident expression"&gt;
&lt;figcaption&gt;
&lt;i&gt;Working within clear, automated guardrails&lt;/i&gt;
&lt;/figcaption&gt;
&lt;/span&gt;
&lt;/span&gt;
&lt;h2 id="pulumi-and-governance-enablement"&gt;Pulumi and Governance Enablement&lt;/h2&gt;
&lt;p&gt;Pulumi provides built-in governance features that help you scale safely and confidently, embedding compliance, consistency, and control directly into your platform:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CrossGuard (Policy as Code)&lt;/strong&gt;
Define and enforce compliance and operational policies automatically. CrossGuard checks every resource against your organization’s standards before deployment, preventing non-compliant resources and reducing manual audits.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Role-Based Access Control (RBAC) and Teams&lt;/strong&gt;
Manage permissions with precision. Pulumi’s RBAC ensures teams get exactly the access they need, no more, no less, so developers can move quickly within clear boundaries and ops can reduce risk.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Audit Logs and Drift Detection&lt;/strong&gt;
Capture a full history of every change and compare live infrastructure to the desired state in code. Audit logs simplify compliance reviews, drift detection spots unauthorized edits, and teams can fix issues in minutes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Time-to-Live (TTL) Stacks / Ephemeral Environments&lt;/strong&gt;
Spin up short-lived environments for testing or previews. You can assign a TTL to any stack so it shuts down automatically after a set period. That keeps forgotten test resources from racking up costs and ensures your platform stays clean.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By leveraging Pulumi’s governance features, including CrossGuard, RBAC, audit logs, and TTL stacks, your platform becomes a powerful enabler. You automate compliance, maintain consistency, and empower engineering teams to innovate quickly and safely.&lt;/p&gt;
&lt;h2 id="conclusion-governance-as-a-platform-feature"&gt;Conclusion: Governance as a Platform Feature&lt;/h2&gt;
&lt;span style="width: 225px; float: right; margin-left: 20px;"&gt;
&lt;span style="text-align:center"&gt;
&lt;img src="empowered.png" width="200px" alt="empowered expression"&gt;
&lt;figcaption&gt;
&lt;i&gt;Having autonomy with built-in controls&lt;/i&gt;
&lt;/figcaption&gt;
&lt;/span&gt;
&lt;/span&gt;
&lt;p&gt;Governance doesn&amp;rsquo;t have to slow you down. By embedding governance directly into your platform, you empower engineering teams to innovate quickly while ensuring compliance, consistency, and control. Instead of manual checks, governance becomes automatic, transparent, and frictionless, enabling your organization to scale safely and confidently.&lt;/p&gt;
&lt;p&gt;Your engineering teams gain autonomy and speed, your operations teams gain visibility and control, and your organization gains the confidence to innovate at scale.&lt;/p&gt;
&lt;p&gt;You’ve now seen all six pillars of a modern internal developer platform—&lt;a href="https://www.pulumi.com/blog/platform-engineering-pillars-2/"&gt;provisioning&lt;/a&gt;, &lt;a href="https://www.pulumi.com/blog/platform-engineering-pillars-3/"&gt;self-service&lt;/a&gt;, &lt;a href="https://www.pulumi.com/blog/platform-engineering-pillars-4/"&gt;developer experience&lt;/a&gt;, &lt;a href="https://www.pulumi.com/blog/platform-engineering-pillars-5/"&gt;security&lt;/a&gt;, &lt;a href="https://www.pulumi.com/blog/platform-engineering-pillars-6/"&gt;observability&lt;/a&gt;, and governance. If you’d like to see how Pulumi makes building and running a platform like this simpler, check out &lt;a href="https://www.pulumi.com/product/internal-developer-platforms/"&gt;Pulumi IDP&lt;/a&gt;.&lt;/p&gt;</description><author>Adam Gordon Bell</author><category>platform-engineering</category></item><item><title>Observability as a Developer Superpower</title><link>https://www.pulumi.com/blog/platform-engineering-pillars-6/</link><pubDate>Tue, 10 Jun 2025 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/platform-engineering-pillars-6/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/platform-engineering-pillars-6/index.png" /&gt;
&lt;span style="width: 225px; float: right; margin-left: 20px;"&gt;
&lt;span style="text-align:center"&gt;
&lt;img src="frustrated.png" width="200px" alt="frustrated expression"&gt;
&lt;figcaption&gt;
&lt;i&gt;Frustratedly trying to figure out what's actually happening&lt;/i&gt;
&lt;/figcaption&gt;
&lt;/span&gt;
&lt;/span&gt;
&lt;p&gt;In previous articles in this series, we’ve shown how &lt;a href="https://www.pulumi.com/blog/series/platform-engineering-pillars/"&gt;platform engineering&lt;/a&gt; turns infrastructure chaos into consistency, gives teams self-service tools, smooths developer workflows, and bakes security into the platform. Each pillar builds on the last. Together, they create an internal developer platform that cuts friction and speeds innovation.&lt;/p&gt;
&lt;p&gt;Even so, teams still face a big challenge: seeing what’s really happening. Whether things go wrong or run smoothly, engineering teams need clear, actionable insights into their systems. Without observability, you end up guessing, reacting slowly, and hunting through scattered data.&lt;/p&gt;
&lt;p&gt;This article shows how observability can be a superpower, giving teams the visibility, insights, and confidence to build better software. Embedding observability into your platform lets teams spot, understand, and fix problems fast, turning reactive firefighting into proactive innovation.&lt;/p&gt;
&lt;h2 id="the-problem-data-overload-without-insights"&gt;The Problem: Data Overload Without Insights&lt;/h2&gt;
&lt;p&gt;Teams drown in metrics, logs, and traces but lack useful insights. In practice, this shows up in three common friction points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Tool sprawl:&lt;/strong&gt; Teams use separate tools for metrics, logs, and traces. They waste time flipping between dashboards and stitching data together.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Alert fatigue:&lt;/strong&gt; Teams get hit with noisy, context-free alerts. With no clear priority or context, key alerts get lost, causing missed issues or slow responses.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reactive debugging:&lt;/strong&gt; Troubleshooting turns into a late-night fire drill. Teams spend hours digging through logs and metrics after users have already noticed the problem.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When observability is limited to post-mortems and fragmented dashboards, your team wastes time reacting instead of preventing problems, and innovation grinds to a halt.&lt;/p&gt;
&lt;h2 id="the-solution-observability-as-an-engineering-superpower"&gt;The Solution: Observability as an Engineering Superpower&lt;/h2&gt;
&lt;span style="width: 225px; float: left; margin-right: 20px;"&gt;
&lt;span style="text-align:center"&gt;
&lt;img src="tired.png" width="200px" alt="tired expression"&gt;
&lt;figcaption&gt;
&lt;i&gt;Exhausted from hours of reactive debugging when the problem could have been caught earlier&lt;/i&gt;
&lt;/figcaption&gt;
&lt;/span&gt;
&lt;/span&gt;
&lt;p&gt;The solution isn’t just about bolting on more monitoring tools. it’s about baking visibility, context, and guidance into your platform. To do this, embrace three key principles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Centralized Service Dashboards &amp;amp; Service List&lt;/strong&gt;
Surface every running service (or database, function, etc.) in one “Services” portal, complete with health badges (CPU, error rate), on-call owner info, and one-click links to that service’s metrics, logs, and traces. By unifying all telemetry behind a single service card, you eliminate context-switching and help engineers find exactly what they need in seconds, not minutes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Actionable Alerts and Insights&lt;/strong&gt;
Replace vague, noisy notifications with context-rich, prioritized alerts that include severity, correlated root-cause data, and recommended next steps (“Database latency jumped 200% since last deploy: rollback or scale up replicas”). Group and surface only the most critical issues first to reduce alert fatigue and speed up resolution.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Embedding Observability into Engineering Workflows&lt;/strong&gt;
Ship every new microservice with built-in logging, metrics, and tracing by including those hooks in your platform’s service templates and CI/CD pipelines. When instrumentation is automatic, “oops, I forgot to add a span” moments disappear, and teams gain immediate visibility into performance and errors from day one.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When observability becomes a superpower, engineering teams gain the visibility and insights they need to confidently build, deploy, and operate software. Instead of drowning in data, they proactively identify and resolve issues, optimize performance, and innovate with confidence.&lt;/p&gt;
&lt;h3 id="a-centralized-service-dashboards--service-list"&gt;A. Centralized Service Dashboards &amp;amp; Service List&lt;/h3&gt;
&lt;span style="width: 225px; float: right; margin-left: 20px;"&gt;
&lt;span style="text-align:center"&gt;
&lt;img src="learning.png" width="200px" alt="learning expression"&gt;
&lt;figcaption&gt;
&lt;i&gt;Reading the Service Catalog&lt;/i&gt;
&lt;/figcaption&gt;
&lt;/span&gt;
&lt;/span&gt;
&lt;p&gt;Imagine you’re paged at 2 AM because “OrderService” is failing, but you don’t know where to look. Metrics live in Grafana, logs are in Elasticsearch, traces in Jaeger, and you still have to hunt down who’s on call. You spend precious minutes clicking through multiple UIs and Slack channels just to figure out who owns the service and where its telemetry lives.&lt;/p&gt;
&lt;p&gt;A centralized service list solves this by surfacing every running microservice (or database, or function) in one place. In your platform’s web portal, you land on a “Services” page that shows OrderService alongside CPU and error‐rate badges, an on-call owner, and links to its real-time dashboard, filtered logs, and trace waterfall. No matter which team spun it up, you know exactly where to click: metrics, logs, traces, deployment history, and contact info all live behind a single service card.&lt;/p&gt;
&lt;p&gt;By embedding a service list into your platform, you eliminate context switching and reduce onboarding friction. If a service isn’t listed, it isn’t properly instrumented, so gaps stand out immediately. In practice, this “one‐pane‐of‐glass” approach means engineers spend seconds finding the right dashboard and the right person, instead of minutes piecing together fragments across disconnected tools.&lt;/p&gt;
&lt;h3 id="b-actionable-alerts-and-insights-reducing-noise-and-accelerating-response"&gt;B. Actionable Alerts and Insights: Reducing Noise and Accelerating Response&lt;/h3&gt;
&lt;span style="width: 225px; float: left; margin-right: 20px;"&gt;
&lt;span style="text-align:center"&gt;
&lt;img src="angry.png" width="200px" alt="locked-in expression"&gt;
&lt;figcaption&gt;
&lt;i&gt;Digging through alert noise&lt;/i&gt;
&lt;/figcaption&gt;
&lt;/span&gt;
&lt;/span&gt;
&lt;p&gt;It’s 4 AM and your phone buzzes with three simultaneous alerts, each with vague descriptions like &amp;ldquo;High CPU usage detected&amp;rdquo; or &amp;ldquo;Error rate increased.&amp;rdquo; Without clear context or recommended actions, you must manually investigate each alert, digging through logs and metrics to determine severity and root cause. This manual triage process is slow, frustrating, and error-prone, increasing the risk of missing critical issues or delaying resolution.&lt;/p&gt;
&lt;p&gt;Engineering teams often face a constant barrage of alerts, many of which lack clear context, severity, or actionable next steps. This flood of noisy, ambiguous notifications creates alert fatigue, causing your team to overlook critical issues or waste valuable time investigating false positives.&lt;/p&gt;
&lt;p&gt;A platform approach should focus on actionable metrics. Instead of vague notifications, clearly state the issue (&amp;ldquo;Database latency increased by 200%&amp;rdquo;), provide relevant context (&amp;ldquo;Latency spike correlated with recent deployment&amp;rdquo;), and suggest immediate actions (&amp;ldquo;Rollback recent deployment or scale database resources&amp;rdquo;). Additionally, alerts are automatically grouped and prioritized based on severity and impact, ensuring you focus on the most critical issues first.&lt;/p&gt;
&lt;p&gt;By holistically approaching alerts and insights, you significantly reduce alert fatigue and noise, accelerate incident response and resolution, and empower engineering teams with greater confidence and autonomy. Your team spends less time manually triaging alerts and more time proactively resolving issues, improving reliability, productivity, and overall team satisfaction.&lt;/p&gt;
&lt;h3 id="c-embedding-observability-into-engineering-workflows-visibility-from-day-one"&gt;C. Embedding Observability into Engineering Workflows: Visibility from Day One&lt;/h3&gt;
&lt;span style="width: 225px; float: right; margin-left: 20px;"&gt;
&lt;span style="text-align:center"&gt;
&lt;img src="sleepy.png" width="200px" alt="sleepy expression"&gt;
&lt;figcaption&gt;
&lt;i&gt;At 2 AM, where are the logs for this service.&lt;/i&gt;
&lt;/figcaption&gt;
&lt;/span&gt;
&lt;/span&gt;
&lt;p&gt;You’ve just deployed a brand-new microservice to production, only to discover performance issues or unexpected behavior. Sure, you should remember to add tracing, logging, and metrics by hand, but in practice, things slip through the cracks. It isn’t until real-world traffic hits that you realize you forgot to instrument X or Y, and now you’re scrambling to retroactively add code, redeploy, and wait for data to appear, delaying resolution and frustrating your team.&lt;/p&gt;
&lt;p&gt;If your platform’s service templates already include all the necessary logging, metrics gathering, and tracing out of the box, it makes life a lot easier. Embedding observability into those templates and engineering workflows ensures every new microservice ships with built-in instrumentation.&lt;/p&gt;
&lt;p&gt;This proactive approach reduces “oops, I forgot” moments, accelerates troubleshooting, and increases team productivity and satisfaction, ultimately improving the reliability and quality of your software.&lt;/p&gt;
&lt;h2 id="real-world-example-observability-superpower-in-action"&gt;Real-World Example: Observability Superpower in Action&lt;/h2&gt;
&lt;span style="width: 225px; float: left; margin-right: 20px;"&gt;
&lt;span style="text-align:center"&gt;
&lt;img src="excited.png" width="200px" alt="excited expression"&gt;
&lt;figcaption&gt;
&lt;i&gt;Applying an Actional Metric&lt;/i&gt;
&lt;/figcaption&gt;
&lt;/span&gt;
&lt;/span&gt;
&lt;p&gt;At 3:15 AM, your pagerduty alert goes off: “CheckoutService latency spiked 150%.” You log into your platform’s Services portal and immediately see CheckoutService highlighted with a red latency badge and Todd Rivera listed as the on-call owner. Rather than scouring multiple dashboards, you click its service card to jump straight to the metrics, logs, and trace views.&lt;/p&gt;
&lt;p&gt;The alert itself is remarkably precise: “CheckoutService latency rose 150% at 3:10 AM following the v2.3.1 deployment. PaymentGatewayService upstream error rate jumped from 0.2% to 2.3%. Recommendation: rollback v2.3.1 or scale PaymentGateway pods. Contact Todd Rivera.” Instantly, you know where the problem lies, which upstream service is impacted, and what the next step should be.
In the trace waterfall, you spot a 200 ms delay on CheckoutService calls to PaymentGateway. The logs, automatically instrumented by your service template, filter to TimeoutException entries all timestamped at 3:10 AM. Opening the “Ask Platform” AI widget, you type, “Why did CheckoutService latency spike at 3:10 AM?” The AI responds: “Likely cause: v2.3.1 added index idx_created_at to PaymentGateway’s transactions table, causing an 80 ms delay per request. Rollback v2.3.1 or patch queries to remove the new index.”&lt;/p&gt;
&lt;p&gt;Armed with this precise diagnosis, you open a rollback pull request and, after Todd&amp;rsquo;s ok, deploy it within minutes.&lt;/p&gt;
&lt;p&gt;CheckoutService latency and PaymentGateway errors immediately return to baseline. By moving from alert to resolution entirely within the platform (thanks to built-in instrumentation, actionable alerts, and AI-driven analysis, you’ve squashed a major incident before most users ever noticed.&lt;/p&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;h2 id="metrics-measuring-observability-enablement"&gt;Metrics: Measuring Observability Enablement&lt;/h2&gt;
&lt;p&gt;To ensure your observability practices truly empower engineering teams, it&amp;rsquo;s essential to track clear, actionable metrics. These metrics help you understand the effectiveness of your observability tools and processes, identify areas for improvement, and demonstrate the tangible impact observability has on your organization.&lt;/p&gt;
&lt;p&gt;Key metrics to measure observability enablement include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Mean Time to Detection (MTTD)&lt;/strong&gt;:&lt;br&gt;
How quickly are issues identified after they occur? Effective observability should significantly reduce the time it takes to detect problems, enabling faster responses and minimizing user impact.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Mean Time to Resolution (MTTR)&lt;/strong&gt;:&lt;br&gt;
How quickly are issues resolved once detected? With clear, actionable insights and unified observability, your teams should resolve issues faster, reducing downtime and improving reliability.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Engineering Team Satisfaction with Observability Tools&lt;/strong&gt;:&lt;br&gt;
Regularly survey your teams to gauge their satisfaction with observability tools and workflows. Higher satisfaction indicates that observability is effectively embedded into engineering workflows, reducing friction and increasing productivity.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Adoption Rate of Observability Tools and Dashboards&lt;/strong&gt;:&lt;br&gt;
Track how widely observability tools and dashboards are adopted across teams. Increased adoption indicates that your teams find these tools valuable, intuitive, and helpful in their daily work.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reduction in Alert Noise and False Positives&lt;/strong&gt;:&lt;br&gt;
Measure the volume and accuracy of alerts over time. Effective observability should reduce noisy, irrelevant alerts, ensuring your teams focus on meaningful, actionable notifications.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tracking these metrics helps you continuously improve your observability practices, ensuring they remain effective and empowering. By regularly reviewing and acting on these insights, you can proactively enhance team productivity, reliability, and overall satisfaction.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="pulumi-and-observability-enablement"&gt;Pulumi and Observability Enablement&lt;/h2&gt;
&lt;span style="width: 225px; float: right; margin-left: 20px;"&gt;
&lt;span style="text-align:center"&gt;
&lt;img src="new-idea.png" width="200px" alt="new-idea expression"&gt;
&lt;figcaption&gt;
&lt;i&gt;Platform insights&lt;/i&gt;
&lt;/figcaption&gt;
&lt;/span&gt;
&lt;/span&gt;
&lt;p&gt;Pulumi’s platform features let you explore observability without bolting on extra tools. Key Pulumi features that enable observability include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pulumi Insights&lt;/strong&gt;:&lt;br&gt;
Provides unified visibility and powerful search across all your cloud resources. Your teams can quickly discover, explore, and understand their infrastructure, eliminating manual searches and reducing cognitive load.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Centralized Service List&lt;/strong&gt;:
Pulumi IDP’s Services portal gives you a single place to register each microservice, database, or cloud resource and link to its dashboards, logs, and traces.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pulumi Co-Pilot&lt;/strong&gt;:&lt;br&gt;
Delivers AI-powered troubleshooting and insights directly within your workflows. Your teams can ask natural-language questions about their infrastructure (such as &amp;ldquo;What infrastructure changed yesterday?&amp;rdquo;) and receive immediate, actionable answers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Built-In Instrumentation via IDP Components&lt;/strong&gt;:&lt;br&gt;
When you author components and templates in Pulumi IDP, you can bake in standard logging, metrics, and tracing hooks. Every service spun up from those templates ships with consistent instrumentation on day one.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With Pulumi, observability can become an integrated part of your platform, accelerating innovation, improving reliability, and empowering engineering teams to confidently build, deploy, and operate software.&lt;/p&gt;
&lt;h2 id="conclusion-observability-as-a-platform-feature"&gt;Conclusion: Observability as a Platform Feature&lt;/h2&gt;
&lt;span style="width: 225px; float: left; margin-right: 20px;"&gt;
&lt;span style="text-align:center"&gt;
&lt;img src="happy.png" width="200px" alt="happy expression"&gt;
&lt;figcaption&gt;
&lt;i&gt;Happy resolving an incident in minutes.&lt;/i&gt;
&lt;/figcaption&gt;
&lt;/span&gt;
&lt;/span&gt;
&lt;p&gt;Observability isn’t just about plugging in more tools. It’s about baking-in consistent instrumentation, measurement, and context so every engineer (platform, DevOps/SRE, or application) knows exactly where to look and how to act.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Service-Templates with Built-In Telemetry&lt;/strong&gt;
By providing service templates that already include logging, metrics gathering, and tracing, you eliminate “Oops, I forgot to instrument X” moments. Every new microservice inherits a standard setup, so you never have to retroactively add code or scramble when traffic first hits production.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Consistent Service Dashboards &amp;amp; Centralized Service List&lt;/strong&gt;
Instead of hunting across eight different dashboards, engineers always start from a single “Service List” page. From there, one click takes them to that service’s metrics overview, log stream, or trace waterfall. This unified entry point reduces cognitive load and cuts straight to “Where’s the problem?”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Measuring Alert Quality and Actionability&lt;/strong&gt;
A truly mature platform doesn’t just send alerts. It tracks whether those alerts are helpful or noise. By measuring “ratio of actionable alerts vs. false positives,” you continuously fine-tune thresholds and eliminate alert fatigue. The result? Engineers trust their notifications and respond faster to real incidents.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AI-Driven Context and Natural-Language Troubleshooting&lt;/strong&gt;
On top of unified telemetry and alert quality metrics, AI can instantly correlate recent deployments, configuration changes, and error spikes. Engineers can ask, “Why did latency jump at 3 AM?” or “What changed in production last night?” in plain English, and the platform provides a clear, context-enriched answer. This additional layer turns reactive firefighting into proactive problem prevention.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When you combine these elements (components and templates, a single service dashboard, alert quality measurement, and AI/natural-language querying), you transform observability into a genuine superpower. Issues are spotted, triaged, and fixed before customers even notice.&lt;/p&gt;
&lt;p&gt;Next time, we’ll dive into the final pillar, Platform Governance, showing how to enforce policy, manage costs, and keep your platform secure and compliant as it scales.&lt;/p&gt;</description><author>Adam Gordon Bell</author><category>platform-engineering</category></item><item><title>From Configuration Chaos to Programming Languages</title><link>https://www.pulumi.com/blog/config-chaos-to-programming-languages/</link><pubDate>Tue, 03 Jun 2025 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/config-chaos-to-programming-languages/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/config-chaos-to-programming-languages/index.png" /&gt;
&lt;p&gt;&lt;em&gt;This post is based on our video interview with Daniel Ward, a Software Developer/Consultant at Lean TECHniques and Microsoft MVP. Daniel shares his strategies for moving teams from configuration chaos to programming languages for infrastructure, including the 10% rule for change, viral adoption, and why Pulumi beats YAML for complex systems. Watch the video below or read on. - Adam Gordon Bell&lt;/em&gt;&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/3VKbaNtbdSs?rel=0?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Imagine your infrastructure codebase has evolved from a few files to thousands of lines across dozens of configuration files. Your team spends hours deciphering CloudFormation templates, your AWS CDK deployments take 30+ minutes, and introducing any change becomes a multi-day effort. This is the reality many organizations face when their infrastructure-as-code approach outgrows their tooling.&lt;/p&gt;
&lt;p&gt;As a consultant and developer who helps organizations improve their software delivery processes, I&amp;rsquo;ve seen firsthand how the right technologies—specifically using actual programming languages like C#—can transform infrastructure management instead of forcing teams to use declarative configuration files.&lt;/p&gt;
&lt;h2 id="my-background-coaching-teams-through-technical-transformation"&gt;My Background: Coaching Teams Through Technical Transformation&lt;/h2&gt;
&lt;p&gt;My background is primarily as a .NET developer—I&amp;rsquo;m now a Microsoft MVP in my first year—though I work with TypeScript and other languages as needed. About six years ago, I moved into consulting, or what we call &amp;ldquo;coaching&amp;rdquo; at Lean Techniques.&lt;/p&gt;
&lt;p&gt;The idea isn&amp;rsquo;t just to write code for clients or tell them what to do and leave. Our goal is that once we leave, they don&amp;rsquo;t need us anymore. We&amp;rsquo;re essentially trying to put ourselves out of a job.&lt;/p&gt;
&lt;p&gt;Organizations bring me in for various reasons—from implementing automated testing after costly failures to migrating from mainframes to the cloud. Sometimes it&amp;rsquo;s about product delivery challenges, like month-long sprints that never complete on time.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s about bringing places up to a modern standard. As we say at Lean Techniques, turning IT from a cost center into a competitive advantage.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you’re ready to move from sprawling configuration files to code-centric infrastructure, here are eight practical steps to guide you:&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="1-follow-the-10-rule-for-sustainable-change"&gt;1. Follow the 10% Rule for Sustainable Change&lt;/h2&gt;
&lt;p&gt;One of my guiding principles comes from Gerald Weinberg&amp;rsquo;s book &amp;ldquo;The Secrets of Consulting&amp;rdquo;: the 10% rule. When entering a new team or organization, don&amp;rsquo;t try to improve more than 10% at a time.&lt;/p&gt;
&lt;img src="secrets.jpg" alt="The Secrets of Consulting book cover" style="width: 40%; float: left; margin-right: 20px; margin-bottom: 10px;"&gt;
&lt;p&gt;There are a few reasons for this. One is that you can actually end up making the client look bad. If you improve everything at once, their boss might ask why this consultant fixed everything in six months when the team couldn&amp;rsquo;t fix it in five years.&lt;/p&gt;
&lt;p&gt;But there&amp;rsquo;s a deeper reason related to social dynamics. Any social group, in programming or outside of work—if you come in new and try to change how the group works before familiarizing yourself with it, people will be resistant. They&amp;rsquo;ll ask &amp;ldquo;Who do you think you are? You just got here!&amp;rdquo; And they&amp;rsquo;re right—you could be wrong about some things. You have to understand how they work to understand what the problems are.&lt;/p&gt;
&lt;p&gt;This applies directly to introducing new technologies like infrastructure as code. Rather than overhauling everything at once, I recommend starting small.&lt;/p&gt;
&lt;p&gt;If a place is completely new to infrastructure as code, I&amp;rsquo;ll tell them to start small. None of their engineers know how to do this yet, and I&amp;rsquo;m not going to do all the work for them and leave, because then none of their engineers will know how to maintain it.&lt;/p&gt;
&lt;h2 id="2-quantify-the-cost-of-inaction-to-overcome-resistance"&gt;2. Quantify the Cost of Inaction to Overcome Resistance&lt;/h2&gt;
&lt;p&gt;Even with a measured approach, resistance is inevitable. I&amp;rsquo;ve experienced teams that are exceptionally open to improvement, often coming from difficult situations and eager for change. I&amp;rsquo;ve also worked with teams that resist most suggestions, where engineers feel the consultant was forced upon them by management.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s always easier if you can do pull-based versus push-based change—where the team has asked for your help instead of the manager telling them &amp;ldquo;I&amp;rsquo;m inflicting this help on you.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;For those looking to influence change within their organization, I&amp;rsquo;ve found that influencing upper-level management comes down to numbers. They deal with too many teams to get into specifics—they want numbers. If you can show how many hours your team spends each week manually pushing code or waiting on another team, and multiply that by engineer salaries, you&amp;rsquo;re showing how much money is being wasted.&lt;/p&gt;
&lt;p&gt;This approach is far more effective than what can come across as &amp;ldquo;Here&amp;rsquo;s a new tool I want to play with&amp;rdquo;—an anti-pattern that reinforces the stereotype of technical people just wanting to use new technologies.&lt;/p&gt;
&lt;h2 id="3-build-consensus-with-nemawashi-conversations"&gt;3. Build Consensus with Nemawashi Conversations&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve found a Japanese concept called &amp;ldquo;nemawashi&amp;rdquo; (literally &amp;ldquo;turning the roots&amp;rdquo;) provides another effective approach to organizational change:&lt;/p&gt;
&lt;p&gt;&lt;img src="nemawashi.jpeg" alt="Nemawashi: Building consensus through individual conversations"&gt;&lt;/p&gt;
&lt;p&gt;Before introducing an idea to a manager or the whole team, individually go to every person on the team first and get their thoughts. If most people respond positively, you can then approach the decision-maker saying, &amp;ldquo;Everyone on the team wants this—what do you think?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;This isn&amp;rsquo;t just about collecting checkmarks but genuinely gathering feedback. If you&amp;rsquo;re getting pushback from every single person, maybe it&amp;rsquo;s not as great an idea as you thought. Either way, you learn something from it.&lt;/p&gt;
&lt;h2 id="4-drive-viral-adoption-through-early-wins"&gt;4. Drive Viral Adoption Through Early Wins&lt;/h2&gt;
&lt;p&gt;Rather than forcing change on resistant teams, I prefer what I call &amp;ldquo;viral adoption&amp;rdquo;:&lt;/p&gt;
&lt;p&gt;We try to work with teams that ask for our help instead of being pushed onto teams. People are way less resistant if you can show them you&amp;rsquo;re making their job easier. We work with one team who wants our help, make them really effective, and then other teams notice: &amp;ldquo;How come they&amp;rsquo;re releasing so many times a week? How do they have so few defects? How is their pipeline so fast?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;This success creates natural curiosity, leading other teams to either request similar help or study and adopt the successful team&amp;rsquo;s practices. Either way, I&amp;rsquo;m happy that things are improving.&lt;/p&gt;
&lt;h2 id="5-align-dev-and-ops-with-infrastructure-as-code"&gt;5. Align Dev and Ops with Infrastructure as Code&lt;/h2&gt;
&lt;p&gt;When it comes to infrastructure as code specifically, I&amp;rsquo;ve noticed it&amp;rsquo;s becoming table stakes for many organizations, though implementation quality varies widely.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s hard to have actual continuous delivery without infrastructure as code. Without it, developers can&amp;rsquo;t define their own infrastructure and have to wait on another team to do it. By continuous, I mean teams should be able to deploy every 30 minutes if they want. If you have to wait one to three business days for the operations team to create your resources, that&amp;rsquo;s not continuous.&lt;/p&gt;
&lt;p&gt;Resistance to infrastructure as code typically comes from two directions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;From developers&lt;/strong&gt;: &amp;ldquo;I already have my plate full—now I have to learn cloud and deployment too?&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;From operations&lt;/strong&gt;: &amp;ldquo;That&amp;rsquo;s my job—I don&amp;rsquo;t want to lose it.&amp;rdquo;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I see this as a win-win opportunity for both sides. For operations teams, the job shifts from mundane tasks like &amp;ldquo;create a lambda for me&amp;rdquo; or &amp;ldquo;replace this code bundle&amp;rdquo; to more creative platform engineering work. For developers, the benefit is clear: no more waiting on operations to proceed with their work.&lt;/p&gt;
&lt;h2 id="6-invest-in-platform-engineering-for-reusable-components"&gt;6. Invest in Platform Engineering for Reusable Components&lt;/h2&gt;
&lt;p&gt;Platform engineering has emerged as a solution to these challenges. Instead of every team reinventing solutions for common needs like logging, monitoring, or deployment pipelines, platform teams create standardized, reusable components.&lt;/p&gt;
&lt;p&gt;It seems wasteful if every single team has to manage their own side of the Splunk instance or Dynatrace. These are solved problems that every team needs. A platform team can look across the organization, see what can be standardized, and remove those problems from individual teams.&lt;/p&gt;
&lt;p&gt;This approach is particularly powerful with tools like Pulumi, which allows creating reusable component resources for common patterns: &amp;ldquo;It&amp;rsquo;s very common for teams to create a Lambda-backed API Gateway or a Lambda hooked up to SQS. These are super common things, so here&amp;rsquo;s a component resource for that so you don&amp;rsquo;t have to write it yourself.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;The key difference in mindset is that platform teams aren&amp;rsquo;t enforcing rules—they&amp;rsquo;re building tools that make developers&amp;rsquo; lives easier. Instead of saying &amp;ldquo;You must do logging this way,&amp;rdquo; it&amp;rsquo;s &amp;ldquo;Here&amp;rsquo;s an endpoint you can use—you&amp;rsquo;re good to go.&amp;rdquo;&lt;/p&gt;
&lt;h2 id="7-avoid-yaml-pitfalls-embrace-programming-languages"&gt;7. Avoid YAML Pitfalls: Embrace Programming Languages&lt;/h2&gt;
&lt;p&gt;One of my pain points is the limitations of configuration languages like YAML for complex infrastructure. I recently gave a presentation about a GitHub Actions workflow that grew to over 1,000 lines, calling shared actions that call other shared actions, making it nearly impossible to understand what&amp;rsquo;s happening.&lt;/p&gt;
&lt;p&gt;I couldn&amp;rsquo;t use breakpoints or the normal libraries I would use. I&amp;rsquo;ve seen a 69,000-line YAML file for Prometheus and Kubernetes—that&amp;rsquo;s an auto-generated example, but even a thousand lines calling several other files is too much.&lt;/p&gt;
&lt;p&gt;The fundamental issue is abstraction. Programming languages allow loops, conditionals, and functions to remove repetition and build larger structures. Configuration languages often lack these capabilities, leading to copy-pasting and unmanageable growth.&lt;/p&gt;
&lt;p&gt;With declarative approaches, you end up using a lot of magic strings. It&amp;rsquo;s easy to forget one when you change values. The tooling is also an issue—I&amp;rsquo;m familiar with IDEs and libraries for programming languages, but the equivalent tools for YAML just aren&amp;rsquo;t as mature.&lt;/p&gt;
&lt;h2 id="8-choose-pulumi-for-flexible-code-first-iac"&gt;8. Choose Pulumi for Flexible, Code-First IaC&lt;/h2&gt;
&lt;p&gt;When comparing infrastructure-as-code options, I see three major players: AWS CDK, Terraform, and Pulumi. While AWS CDK offers similar capabilities to Pulumi for AWS resources, it relies on CloudFormation under the hood:&lt;/p&gt;
&lt;p&gt;CloudFormation is just a black box—it&amp;rsquo;s proprietary. If something goes wrong, you have to hope someone&amp;rsquo;s already asked about it on GitHub. When deployments take 15-30 minutes, there&amp;rsquo;s not much you can do—it&amp;rsquo;s just how CloudFormation works.&lt;/p&gt;
&lt;p&gt;Compared to Terraform, which uses HCL (HashiCorp Configuration Language), Pulumi offers the flexibility of actual programming languages:&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s nothing wrong with declarative approaches in some cases, but at a certain point—when you have big systems and chunks of resources you want to share between teams—it becomes more cumbersome than helpful. That&amp;rsquo;s why Terraform is working on their own CDK now.&lt;/p&gt;
&lt;p&gt;What sets Pulumi apart is its flexibility: Even if you have platform teams who aren&amp;rsquo;t developers and want to set standards, they can use YAML with Pulumi and then transform those component resources into other languages for dev teams to use. You get the best of all worlds.&lt;/p&gt;
&lt;h2 id="start-small-think-big-small-changes-big-impact"&gt;Start Small, Think Big: Small Changes, Big Impact&lt;/h2&gt;
&lt;p&gt;Whether you&amp;rsquo;re introducing infrastructure as code or any significant technical change, my advice remains consistent: start small, focus on solving real problems, and let success spread naturally.&lt;/p&gt;
&lt;p&gt;By understanding the human elements of change—the 10% rule, nemawashi, and viral adoption—you can transform resistance into enthusiasm. And by choosing tools like Pulumi that bridge the gap between developers and operations, you can create infrastructure that&amp;rsquo;s both powerful and maintainable.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m currently writing a blog series on Pulumi at &lt;a href="https://daninacan.com"&gt;daninacan.com&lt;/a&gt;. I also speak at conferences occasionally, so check out my speaking page if you&amp;rsquo;re interested.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Ready to escape configuration chaos? Start small, focus on solving real problems, and &lt;a href="https://www.pulumi.com/docs/get-started/"&gt;get started with Pulumi&lt;/a&gt; to leverage the full power of programming languages for infrastructure. Join &lt;a href="https://slack.pulumi.com/"&gt;our community Slack&lt;/a&gt; to connect with engineers making the same transformation.&lt;/em&gt;&lt;/p&gt;</description><author>Daniel Ward</author><author>Adam Gordon Bell</author><category>infrastructure-as-code</category><category>consulting</category><category>configuration-management</category><category>programming-languages</category></item><item><title>Platform Engineering Buffet at SEITENBAU</title><link>https://www.pulumi.com/blog/platform-engineering-buffet/</link><pubDate>Tue, 27 May 2025 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/platform-engineering-buffet/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/platform-engineering-buffet/index.png" /&gt;
&lt;p&gt;&lt;em&gt;This post is based on my video interview with Nico Thomas, a platform engineer at SEITENBAU. In this article, Nico shares how he and his team built a flexible, reusable platform to support 20+ diverse projects using Pulumi. Watch the video below or read on for Nico’s take on building an infrastructure buffet. - Adam Gordon Bell&lt;/em&gt;&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/wF9COpHJ9b4?rel=0?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Imagine your company isn&amp;rsquo;t Spotify with one product pipeline, but 20 independent government projects running on Ansible-provisioned VMs—some on Kubernetes, some delivered into other customer data centers, some operated by clients themselves. Each team is building its own CI/CD, secrets management, and integrations. You’re left with a million reinvented wheels.&lt;/p&gt;
&lt;p&gt;That was the situation my team and I faced at SEITENBAU, a German software company specializing in custom development for both public and private sectors. Unlike the typical platform engineering stories—where teams build infrastructure for a single product with multiple microservices—we had a fundamentally different challenge: &lt;strong&gt;How do you build a platform that serves dozens of completely independent projects, each with their own technology choices, deployment targets, and operational models?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The answer? Stop trying to force everyone onto the same plate. Instead, build a buffet.&lt;/p&gt;
&lt;h2 id="the-buffet-philosophy"&gt;The Buffet Philosophy&lt;/h2&gt;
&lt;p&gt;&lt;img src="slide-02.png" alt="Buffet visualization with labeled containers showing NPM Artifact Storage, Container Registry, and various infrastructure components"&gt;&lt;/p&gt;
&lt;p&gt;Think of a traditional platform as a set menu—also known as prix fixe—where everyone gets the same courses, prepared the same way, in the same order. But what if your diners include vegans, carnivores, and people with various dietary restrictions? What if some want a quick snack while others need a seven-course meal?&lt;/p&gt;
&lt;p&gt;That was exactly the situation we faced. We had:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Government projects that needed to run on-premises due to data sovereignty requirements.&lt;/li&gt;
&lt;li&gt;Modern cloud-native applications leveraging Kubernetes and GitOps.&lt;/li&gt;
&lt;li&gt;Legacy systems still operating on virtual machines managed with Ansible.&lt;/li&gt;
&lt;li&gt;Projects we ran as managed services and others we simply delivered to customers.&lt;/li&gt;
&lt;li&gt;Teams using various CI/CD tools, artifact repositories, and deployment strategies.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A one-size-fits-all platform would have been a disaster. Instead, my team built a platform offering a rich selection of pre-configured, production-ready components. Teams could mix and match according to their specific needs, creating their own unique infrastructure combinations.&lt;/p&gt;
&lt;h2 id="the-challenge-beyond-microservices"&gt;The Challenge: Beyond Microservices&lt;/h2&gt;
&lt;p&gt;Most platform engineering resources assume you&amp;rsquo;re building infrastructure for a single product – Spotify&amp;rsquo;s Backstage, for example. But what happens when you&amp;rsquo;re dealing with over 20 completely independent projects, each with distinct requirements?&lt;/p&gt;
&lt;p&gt;Each project is distinct, featuring its own:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Technology stack (Java predominates, but we support various frameworks and languages).&lt;/li&gt;
&lt;li&gt;Infrastructure requirements (cloud, on-premises, or hybrid setups).&lt;/li&gt;
&lt;li&gt;Security and compliance constraints.&lt;/li&gt;
&lt;li&gt;Operational models (managed by us, the client, or jointly).&lt;/li&gt;
&lt;li&gt;Release cadences and deployment strategies.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="slide-07.png" alt="Choose your menu"&gt;&lt;/p&gt;
&lt;p&gt;Initially, this diversity seemed chaotic, but we quickly noticed that underneath these variations, common patterns emerged. Every project consistently required source control, CI/CD pipelines, artifact storage, secrets management, and deployment targets. The critical realization was that &lt;strong&gt;we didn&amp;rsquo;t need to standardize what teams chose—we needed to standardize how they connected these building blocks.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s where Pulumi became essential. Its flexibility allowed us to construct an infrastructure buffet that genuinely accommodated the diverse appetites and needs of every team.&lt;/p&gt;
&lt;h2 id="building-the-kitchen-architecture-with-pulumi"&gt;Building the Kitchen: Architecture with Pulumi&lt;/h2&gt;
&lt;p&gt;&lt;img src="slide-09.png" alt="Unified project configuration"&gt;&lt;/p&gt;
&lt;p&gt;Behind every great buffet is a well-organized kitchen. For our infrastructure, that kitchen is powered by Pulumi and Python, orchestrating a complex web of infrastructure services, configurations, and deployments. Using Pulumi together with Python has proven extremely valuable—it makes our day-to-day tasks easier to maintain and understand.&lt;/p&gt;
&lt;h3 id="the-multi-stack-approach"&gt;The Multi-Stack Approach&lt;/h3&gt;
&lt;p&gt;Rather than creating a single monolithic infrastructure program, we adopted a modular approach with multiple Pulumi stacks. This decision was essential for managing complexity and empowering team autonomy.&lt;/p&gt;
&lt;p&gt;&lt;img src="slide-10.png" alt="A look in the kitchen"&gt;&lt;/p&gt;
&lt;p&gt;Our architecture comprises several specialized Pulumi programs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitLab System&lt;/strong&gt;: Manages installations and configurations of GitLab.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Project Configuration&lt;/strong&gt;: Sets up team-specific GitLab groups, CI/CD runners, and agents.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cluster Management&lt;/strong&gt;: Provisions Kubernetes clusters complete with standard add-ons.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;VM Infrastructure&lt;/strong&gt;: Manages traditional VM-based deployments utilizing Ansible.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Platform Services&lt;/strong&gt;: Configures shared services such as artifact repositories and identity providers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each program can maintain multiple stacks (dev, staging, prod), allowing us to manage various environments independently while leveraging shared infrastructure code.&lt;/p&gt;
&lt;h2 id="from-ingredients-to-ready-made-dishes-reusable-components"&gt;From Ingredients to Ready-Made Dishes: Reusable Components&lt;/h2&gt;
&lt;p&gt;&lt;img src="slide-06.png" alt="Platform vision showing the complete self-service platform"&gt;&lt;/p&gt;
&lt;p&gt;The real magic happens in our component library. Just as a buffet offers complete dishes rather than raw ingredients, we&amp;rsquo;ve built high-level components that encapsulate complex infrastructure patterns.&lt;/p&gt;
&lt;h3 id="example-the-kubernetes-storage-component"&gt;Example: The Kubernetes Storage Component&lt;/h3&gt;
&lt;p&gt;&lt;img src="slide-15.png" alt="KubeLonghorn component diagram"&gt;&lt;/p&gt;
&lt;p&gt;Consider our Longhorn storage component. Instead of requiring teams to understand Helm charts, storage classes, and snapshot configurations, they simply declare they need persistent storage.&lt;/p&gt;
&lt;p&gt;The component handles all the complexity: installing Longhorn, configuring appropriate replication for test vs. production environments, setting up snapshot classes, and integrating with our internal certificate authority.&lt;/p&gt;
&lt;h2 id="two-years-later-the-buffet-is-open"&gt;Two Years Later: The Buffet is Open&lt;/h2&gt;
&lt;p&gt;After two years of running this platform in production, we&amp;rsquo;ve learned what works and what we&amp;rsquo;d do differently.&lt;/p&gt;
&lt;p&gt;Building a platform for diverse, independent projects requires a different mindset than traditional platform engineering. Instead of forcing standardization, we&amp;rsquo;ve embraced flexibility while maintaining consistency where it matters.&lt;/p&gt;
&lt;p&gt;The buffet approach—powered by Pulumi&amp;rsquo;s flexibility and Python&amp;rsquo;s expressiveness—has allowed us to serve 20+ different projects efficiently while giving teams the autonomy they need. It&amp;rsquo;s not about having one perfect dish; it&amp;rsquo;s about having a kitchen flexible enough to satisfy everyone.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re facing similar challenges with diverse project requirements, consider building a buffet instead of a prix fixe menu. Your teams will thank you for it.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Want to build your own infrastructure buffet? &lt;a href="https://www.pulumi.com/docs/get-started/"&gt;Get started with Pulumi&lt;/a&gt; and join &lt;a href="https://slack.pulumi.com/"&gt;our community Slack&lt;/a&gt; to connect with engineers solving similar challenges.&lt;/em&gt;&lt;/p&gt;</description><author>Nico Thomas</author><author>Adam Gordon Bell</author><category>platform-engineering</category><category>kubernetes</category><category>infrastructure-as-code</category></item><item><title>Platform Engineering: Evolution or Rebranding?</title><link>https://www.pulumi.com/blog/platform-eng-rebrand/</link><pubDate>Mon, 12 May 2025 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/platform-eng-rebrand/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/platform-eng-rebrand/index.png" /&gt;
&lt;p&gt;2 Months ago, user &lt;a href="https://old.reddit.com/r/devops/comments/1izpca1/platform_engineering_fad/"&gt;&lt;code&gt;deacon91&lt;/code&gt;&lt;/a&gt;, after years of working in the industry has declared DevOps to be a dead end on Reddit&amp;rsquo;s &lt;code&gt;r/devops&lt;/code&gt;. And I&amp;rsquo;ve been thinking about his thoughts on the industry since then.&lt;/p&gt;
&lt;img src="Picyard_1746641886795.png" alt="crossroads photo" style="width:100%;" /&gt;
&lt;p&gt;His point was that DevOps, the breaking down of silos between development and operations, had been tried. We learned the lessons of this approach and need to do something better: Platform Engineering.&lt;/p&gt;
&lt;p&gt;And platform engineering has certainly emerged as a new hot area. But is it truly an evolutionary step toward a greater way of developing and operating software? Or is it, as others in that same Reddit group declare, merely a fad and a rebranding of existing ideas?&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s what I want to explore: is platform engineering a rebrand or a genuine evolution?&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s start by summarizing the cynical case - that it&amp;rsquo;s merely a rebrand.&lt;/p&gt;
&lt;h2 id="the-rebrand-case"&gt;The Rebrand Case&lt;/h2&gt;
&lt;img src="Picyard_1746642618981.png" alt="There were fad chasers yesterday, there are fad chasers today, and there will be fad chasers tomorrow until the sun burns out" style="width:100%;" /&gt;
&lt;h3 id="title-drift"&gt;Title Drift&lt;/h3&gt;
&lt;p&gt;This title drift happens in many fields. Just as the garbage man becomes the sanitation worker so does the sysadmin becomes the DevOps engineer.&lt;/p&gt;
&lt;p&gt;This creates title inflation in our competitive industry. People who job-hop regularly update their LinkedIn profiles from &amp;ldquo;business analyst&amp;rdquo; to &amp;ldquo;product manager.&amp;rdquo; Meanwhile, the person who stays put for years might still be called a &amp;ldquo;programmer&amp;rdquo; while everyone around them becomes a &amp;ldquo;Staff-plus Senior Backend Engineer.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;That ops wizard who&amp;rsquo;s been at the same company for eight years? Still an &amp;ldquo;Operations Engineer.&amp;rdquo; Their friend who switches jobs every 18 months? Now a &amp;ldquo;Senior SRE&amp;rdquo; making 50% more money.&lt;/p&gt;
&lt;p&gt;Title drift becomes a self-reinforcing cycle. When the recruiter discovers that the best DevOps candidates now call themselves &amp;ldquo;Platform Engineers,&amp;rdquo; your company&amp;rsquo;s job postings change overnight—even if no one plans to build an actual platform.&lt;/p&gt;
&lt;p&gt;This is how you end up with people like &lt;code&gt;u/hajimenogio92&lt;/code&gt; doing the exact same sysadmin work they&amp;rsquo;ve always done, just with &amp;ldquo;Platform Engineer&amp;rdquo; on their LinkedIn profile.&lt;/p&gt;
&lt;p&gt;This is one aspect of what people mean why they say &amp;ldquo;platform engineering is just a rebrand.&amp;rdquo; The other thing they sometimes mean is that it&amp;rsquo;s a fad everyone is chasing.&lt;/p&gt;
&lt;h3 id="hype-cycle-reboot"&gt;Hype Cycle Reboot&lt;/h3&gt;
&lt;img src="Picyard_1746642250343.png" alt="There were fad chasers yesterday, there are fad chasers today, and there will be fad chasers tomorrow until the sun burns out" style="width:100%;" /&gt;
&lt;p&gt;Tech movements decay in predictable ways. Remember Extreme Programming? It began as a revolution to empower developers. Then it morphed into Agile, and in some places, into the 12 steps, 7 competencies, and many processes of SAFe.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Scaled Agile Framework® (SAFe®) is a set of organization and workflow patterns for implementing agile practices at enterprise scale.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Maybe SAFe makes sense for your work place, but it&amp;rsquo;s certainly a lot more complex than it&amp;rsquo;s root and this pattern is what skeptics see in platform engineering.&lt;/p&gt;
&lt;p&gt;DevOps started at grassroots conferences with a simple message: &amp;ldquo;Let&amp;rsquo;s tear down the wall between developers and operations.&amp;rdquo; But once it peaked, everyone scrambled to slap &amp;ldquo;DevOps&amp;rdquo; on whatever they were already doing. Just like companies today adding &amp;ldquo;AI&amp;rdquo; to their products.&lt;/p&gt;
&lt;p&gt;Eventually, the original movement gets so diluted that we need a fresh start. New terminology. New conferences. New keynote speakers recycling old ideas with updated slides. New vendors selling solutions to problems you didn&amp;rsquo;t know you had.&lt;/p&gt;
&lt;p&gt;In this view, platform engineering isn&amp;rsquo;t revolutionary—it&amp;rsquo;s just the next square on the buzzword bingo card. DevOps plateaued. SRE plateaued. The Kubernetes experts ran out of things to say. So the industry needed something new to hype.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s some truth to this cynicism, but it blinds us to what platform engineering actually offers. More importantly, this view misses something crucial: where a platform approach succeeds, it reveals fundamental flaws in the original DevOps vision.&lt;/p&gt;
&lt;h2 id="the-case-for-evolution"&gt;The Case for Evolution&lt;/h2&gt;
&lt;img src="stickers.png" alt="laptop stickers" style="width:100%;" /&gt;
&lt;p&gt;The original DevOps movement introduced valuable ideas about breaking down silos and encouraging collaboration. However, its purest vision—where &amp;ldquo;everybody does everything&amp;rdquo;—faces practical limitations at scale.&lt;/p&gt;
&lt;p&gt;Totally cross-functional teams work well at small organizations but struggle when engineering teams grow beyond 20 people because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;People naturally have different specializations and preferences&lt;/li&gt;
&lt;li&gt;Few individuals want to perform both development and operations roles&lt;/li&gt;
&lt;li&gt;Staying current on everything in your Ops and Dev stack is often unrealistic&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But most importantly, as organizations grow, specializations natuarally emerge. Governance and corporate policies often force the issue. A platform team is one such specialization and a powerful one if done right.&lt;/p&gt;
&lt;p&gt;Instead of focusing on eliminating silos, it focuses on creating productive abstractions and interfaces between application teams and operating services. The platform functions as an abstraction layer (similar to how a database abstracts data storage), with dedicated teams treating it as a product with clear boundaries.&lt;/p&gt;
&lt;p&gt;This approach contradicts the original DevOps vision but represents a necessary evolution. Platform Engineering acknowledges inevitable organizational divisions while still enabling collaboration. It leverages skills and organizational workflows from application development and product management to build a way to operate software effectively.&lt;/p&gt;
&lt;h2 id="why-not-both"&gt;Why not both?&lt;/h2&gt;
&lt;img src="bingo.png" alt="buzzword bingo" style="width:100%;" /&gt;
&lt;p&gt;So yeah, title drift is a thing. And the hype cycle is real. But the idea of a dedicated team removing frictions by building tools and reusable abstractions is an idea that&amp;rsquo;s here to stay.&lt;/p&gt;
&lt;p&gt;In fact, it&amp;rsquo;s just a way to scale the principles inherent in the original ideas of DevOps. It may technically diverge from the literal principles of DevOps, but it is most definitely true to the spirit.&lt;/p&gt;
&lt;p&gt;At Pulumi, we see this evolution firsthand. Our most forward-looking users aren’t chasing titles—they’re building internal platforms that empower teams, reduce friction, and let developers focus on what matters most. That’s why we’re investing in &lt;a href="https://www.pulumi.com/blog/announcing-pulumi-idp/#platform-teams"&gt;platform team features&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Not because it’s trendy—but because, done well, it works. And I think that’s the insight &lt;code&gt;deacon91&lt;/code&gt; was getting at.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://info.pulumi.com/ebook/platform-engineering-iac-recommended-practices"&gt;&lt;strong&gt;Get a Free Platform Engineering Guide →&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;</description><author>Adam Gordon Bell</author><category>platform-engineering</category></item><item><title>Security as an Enabler: Building Trust into Your Platform</title><link>https://www.pulumi.com/blog/platform-engineering-pillars-5/</link><pubDate>Fri, 11 Apr 2025 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/platform-engineering-pillars-5/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/platform-engineering-pillars-5/index.png" /&gt;
&lt;p&gt;In previous articles, we looked at how &lt;a href="https://www.pulumi.com/blog/series/platform-engineering-pillars/"&gt;platform engineering&lt;/a&gt; fixes infrastructure chaos, enables self-service, and improves developer workflows. These pillars work together to boost both developer productivity and organizational speed.&lt;/p&gt;
&lt;p&gt;But there&amp;rsquo;s still one critical element that can make or break all this progress: security.&lt;/p&gt;
&lt;p&gt;Traditional security efforts — even &amp;ldquo;shift-left&amp;rdquo; initiatives — often create friction instead of clearing the way for innovation. Embedding security directly into your platform changes that. By weaving in policy-as-code, centralized secrets management, and identity-based authentication, you turn security from a blocker into an enabler. And with the right metrics, you can measure how well your platform balances protection and speed.&lt;/p&gt;
&lt;p&gt;When security is baked into your internal developer platform, it stops being a gatekeeper. Developers move faster — and with more confidence.&lt;/p&gt;
&lt;p&gt;Done right, your teams gain autonomy without compromising safety. Security people become partners instead of blockers. Your organization builds trust that speeds up innovation rather than slowing it down.&lt;/p&gt;
&lt;h2 id="the-problem-security-as-a-gatekeeper"&gt;The Problem: Security as a Gatekeeper&lt;/h2&gt;
&lt;p&gt;Traditional security practices often create friction rather than enable innovation. Developers run into several pain points: last-minute security reviews that delay deployments, unclear policies that feel arbitrary, and security teams that act more like blockers than partners.&lt;/p&gt;
&lt;p&gt;This friction has real consequences. When security becomes a bottleneck, developers find ways around it. They take shortcuts. All of this increases risk through inconsistent security practices.&lt;/p&gt;
&lt;p&gt;The end result is that innovation slows down. Developer autonomy shrinks. And the agility your platform was supposed to deliver disappears.&lt;/p&gt;
&lt;p&gt;Many organizations try to fix this by &amp;ldquo;shifting security left&amp;rdquo; - moving security checks earlier in development. But just dumping security responsibilities onto developers isn&amp;rsquo;t the answer. It overwhelms them and can still create inconsistencies.&lt;/p&gt;
&lt;p&gt;The real solution isn&amp;rsquo;t just about changing when security happens. It&amp;rsquo;s about changing how it works.&lt;/p&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;h3 id="key-security-functions-within-a-platform"&gt;Key Security Functions within a Platform&lt;/h3&gt;
&lt;p&gt;Effective platform security involves several key functions, regardless of how they are distributed across teams or individuals in your organization:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Defining Security Strategy &amp;amp; Policy:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Developing and maintaining security policies, standards, and architectural best practices tailored to the organization.&lt;/li&gt;
&lt;li&gt;Providing security guidance, education, and support across engineering teams.&lt;/li&gt;
&lt;li&gt;Performing threat modeling, security architecture reviews, and proactive security assessments.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Monitoring &amp;amp; Responding to Threats:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Actively monitoring systems to detect security events and potential threats in real-time.&lt;/li&gt;
&lt;li&gt;Managing security tooling (like SIEMs, vulnerability scanners) and orchestrating incident response procedures.&lt;/li&gt;
&lt;li&gt;Investigating incidents, coordinating remediation efforts, and managing security automation.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Integrating Security into Development &amp;amp; Infrastructure:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Embedding security controls directly into the development lifecycle and infrastructure provisioning processes.&lt;/li&gt;
&lt;li&gt;Implementing secure-by-default configurations, automated security checks (SAST, DAST, SCA), and policy-as-code guardrails.&lt;/li&gt;
&lt;li&gt;Ensuring security considerations are built into CI/CD pipelines, service templates, and infrastructure modules from the start.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Collaboration &lt;em&gt;between&lt;/em&gt; these functions is crucial. A successful platform ensures that policy definition, operational monitoring, and practical implementation work together seamlessly to create a secure environment without hindering velocity.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 id="the-solution-embedding-security-into-developer-workflows"&gt;The Solution: Embedding Security into Developer Workflows&lt;/h3&gt;
&lt;p&gt;The key to solving security friction isn&amp;rsquo;t just adding more checkpoints earlier. It&amp;rsquo;s weaving security directly into your platform and your developers&amp;rsquo; daily work.&lt;/p&gt;
&lt;p&gt;Adding automated scans to your CI/CD pipeline is a good start. Tools like Dependabot, Snyk, and Trivy catch problems early and alert developers quickly. But these &amp;ldquo;shift-left&amp;rdquo; checks alone aren&amp;rsquo;t enough. Developers still need to interpret findings and fix issues, often without adequate support.&lt;/p&gt;
&lt;p&gt;Platform engineering takes a different approach. It builds security in from the start, rather than treating it as a final hurdle or dumping complex responsibilities on developers. It uses secure-by-default components and automated guardrails that work continuously.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s see what this looks like in practice.&lt;/p&gt;
&lt;h3 id="policy-as-code-for-security-automating-trust"&gt;Policy-as-Code for Security: Automating Trust&lt;/h3&gt;
&lt;p&gt;If you&amp;rsquo;ve implemented the &lt;a href="https://www.pulumi.com/blog/platform-engineering-pillars-3/"&gt;intent-based approach&lt;/a&gt; we covered earlier, your developers already use secure-by-default modules. They simply request what they need (&amp;ldquo;I need a Java service with Kafka and PostgreSQL&amp;rdquo;), and the platform handles security details like IAM roles, encryption, and permissions.&lt;/p&gt;
&lt;p&gt;But secure defaults aren&amp;rsquo;t enough. You still need automated guardrails. Policy-as-Code adds this enforcement layer, keeping security standards consistent—even when someone tries to bypass or change the modules. It&amp;rsquo;s a backup system: secure modules provide your first defense, and Policy-as-Code catches anything that might slip through.&lt;/p&gt;
&lt;p&gt;For Example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;IAM Roles with Least Privilege:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Secure Default (Module):&lt;/strong&gt; The platform team&amp;rsquo;s IAM module automatically generates roles with least-privilege permissions. Developers don&amp;rsquo;t specify IAM roles directly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Policy-as-Code (Additional Guardrail):&lt;/strong&gt; A policy explicitly checks all IAM roles provisioned by the platform, ensuring no role grants overly broad permissions (e.g., no wildcard permissions). If a module is accidentally modified or misconfigured, the policy catches it immediately.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Mandatory Encryption at Rest:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Secure Default (Module):&lt;/strong&gt; The platform team&amp;rsquo;s database module automatically provisions databases with encryption at rest enabled. Developers don&amp;rsquo;t specify encryption settings directly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Policy-as-Code (Additional Guardrail):&lt;/strong&gt; A policy explicitly checks all storage resources to ensure encryption at rest is always enabled. If a developer or platform engineer accidentally modifies the module or bypasses it, the policy immediately flags the issue.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When you add Policy-as-Code to your platform, security becomes automatic and clear. Developers get instant feedback they can trust, and security teams maintain control without slowing things down. Your teams can move fast without taking shortcuts that create risk.&lt;/p&gt;
&lt;h3 id="protecting-sensitive-credentials-by-default"&gt;Protecting Sensitive Credentials by Default&lt;/h3&gt;
&lt;p&gt;Policy-as-Code secures your infrastructure, but you still need to protect how applications access sensitive resources like databases and APIs.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what often happens: A developer copies database passwords into a local .env file. They accidentally commit this file to Git, exposing the credentials to anyone with repository access. Or they inadvertently bake these secrets into a Docker image that others can download. Worse yet, if their laptop gets hacked, attackers can use these credentials to break into production systems.&lt;/p&gt;
&lt;p&gt;When these breaches happen, everything stops. Teams scramble to rotate credentials, investigate the damage, and fix systems—creating delays and increasing risk.&lt;/p&gt;
&lt;p&gt;Centralized secrets management provides a robust solution to credential security. It stores sensitive information in a secure vault and offers controlled access methods at runtime. Developers work with the secrets management solution instead of handling credentials directly, which significantly reduces exposure risk.&lt;/p&gt;
&lt;p&gt;Platform teams build these secure patterns into their service templates. They create clear pathways for accessing credentials that guide developers toward secure practices in both development and production environments.&lt;/p&gt;
&lt;p&gt;With this approach, your deployments become both faster and safer. Developers follow simpler, more consistent security procedures, while security teams gain confidence that credentials are properly protected throughout your systems.&lt;/p&gt;
&lt;h3 id="identity-and-access-management-frictionless-security-through-identity"&gt;Identity and Access Management: Frictionless Security through Identity&lt;/h3&gt;
&lt;p&gt;Your platform must also solve another security problem: how people and systems safely access cloud resources.&lt;/p&gt;
&lt;p&gt;Developers hate juggling multiple credentials—cloud provider keys, passwords, or tokens. These credentials can expire, get lost, or leak, causing headaches and security risks.&lt;/p&gt;
&lt;p&gt;Consider a common scenario: A developer trying to fix a broken production system finds their credentials have expired. Now they&amp;rsquo;re stuck hunting for new credentials instead of fixing the problem.&lt;/p&gt;
&lt;p&gt;Identity-based authentication fixes this. Developers just use their regular login. When developers log in normally, they automatically get secure access to cloud resources through OIDC or SAML. No more managing credentials. Access stays secure and current.&lt;/p&gt;
&lt;p&gt;This approach simplifies access and removes barriers. Developers get secure access to resources when they need them, speeding up work without weakening security.&lt;/p&gt;
&lt;p&gt;Identity-based authentication, combined with policy-as-code guardrails and centralized secrets management, creates a comprehensive security system that protects without impeding work. To see how these practices function together, let&amp;rsquo;s look at a real example.&lt;/p&gt;
&lt;h2 id="real-world-example-security-enablement-from-build-to-incident-response"&gt;Real-World Example: Security Enablement from Build to Incident Response&lt;/h2&gt;
&lt;p&gt;A developer deploys a new microservice. They select a secure template from the service catalog. Policy-as-code instantly checks that encryption and permissions are set correctly, and credentials are setup in a centralized secrets management environment.&lt;/p&gt;
&lt;p&gt;As the service builds, automatic scans check dependencies and container images for vulnerabilities. The system creates a dependency list (SBOM), tracking every component used. Build pipelines verify and sign code, so only trusted code reaches production.&lt;/p&gt;
&lt;p&gt;Once deployed, security monitoring begins automatically, showing the service&amp;rsquo;s security status in real time. Later, someone discovers a vulnerability in a library used by the microservice. Thanks to the dependency list, the security team immediately knows which services are vulnerable. Monitoring spots suspicious activity and sends clear alerts to the team&amp;rsquo;s dashboards.&lt;/p&gt;
&lt;p&gt;Clear incident response plans help teams contain and fix the problem. The team patches the vulnerable component, using the same secure pipelines to quickly roll out the fix.&lt;/p&gt;
&lt;p&gt;In this scenario, security helps rather than blocks at every stage. From building to responding to incidents, your platform helps teams build secure software quickly.&lt;/p&gt;
&lt;p&gt;But how do you know if your security practices are truly enabling your teams? To answer that, you need clear, actionable metrics.&lt;/p&gt;
&lt;h2 id="metrics-measuring-security-enablement"&gt;Metrics: Measuring Security Enablement&lt;/h2&gt;
&lt;p&gt;To check if your security approach works, track two things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Security Incident Rate:&lt;/strong&gt;&lt;br&gt;
Count how many security problems reach production. Fewer incidents means your security is working.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Developer Security Friction Score:&lt;/strong&gt;&lt;br&gt;
Include security questions in your regular developer surveys. Ask how much security processes slow their work or cause frustration. Improving scores show that security fits naturally into their workflow.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These two measures combined tell you your security works and at what cost, so your team can build good software fast.&lt;/p&gt;
&lt;h2 id="pulumi-and-security-enablement"&gt;Pulumi and Security Enablement&lt;/h2&gt;
&lt;p&gt;Achieving these goals, a decreasing incident rate coupled with a low developer friction score, requires a platform built with the right foundations. Pulumi helps you construct such a platform by providing built-in capabilities that directly embed security into your infrastructure workflows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://www.pulumi.com/crossguard/"&gt;Policy as Code (CrossGuard)&lt;/a&gt;:&lt;/strong&gt; Automatically enforce security and compliance standards.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://www.pulumi.com/product/esc/"&gt;Secure Secrets Management (Pulumi ESC)&lt;/a&gt;:&lt;/strong&gt; Centralize and securely inject secrets without manual handling.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://www.pulumi.com/docs/pulumi-cloud/access-management/"&gt;Identity-Based Authentication&lt;/a&gt;:&lt;/strong&gt; Simplify secure access to cloud resources using existing identities.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With Pulumi, security becomes an integrated, frictionless part of your platform—accelerating innovation while building trust.&lt;/p&gt;
&lt;h2 id="security-as-a-platform-feature"&gt;Security as a Platform Feature&lt;/h2&gt;
&lt;p&gt;Security can help, not hinder. By building security into your platform, you help developers work faster and safer. Security becomes a feature, not an obstacle.&lt;/p&gt;
&lt;p&gt;Security completes the platform foundation we&amp;rsquo;ve built in previous articles on &lt;a href="https://www.pulumi.com/blog/platform-engineering-pillars-2/"&gt;Provisioning&lt;/a&gt;, &lt;a href="https://www.pulumi.com/blog/platform-engineering-pillars-3/"&gt;Self-Service&lt;/a&gt;, and &lt;a href="https://www.pulumi.com/blog/platform-engineering-pillars-4/"&gt;Developer Experience&lt;/a&gt;, creating a platform that speeds up development.&lt;/p&gt;
&lt;p&gt;But developers still need to see what&amp;rsquo;s happening in their systems. Next time, we&amp;rsquo;ll explore how Observability helps developers spot and fix problems quickly.&lt;/p&gt;</description><author>Adam Gordon Bell</author><category>platform-engineering</category></item><item><title>Improve Developer Experience: Increase Dev Productivity with Internal Developer Platforms</title><link>https://www.pulumi.com/blog/platform-engineering-pillars-4/</link><pubDate>Thu, 13 Mar 2025 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/platform-engineering-pillars-4/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/platform-engineering-pillars-4/index.png" /&gt;
&lt;p&gt;In the last article in this &lt;a href="https://www.pulumi.com/blog/series/platform-engineering-pillars/"&gt;Platform Engineering Pillars series&lt;/a&gt;, we explored how &lt;strong&gt;self-service infrastructure&lt;/strong&gt; frees developers from bottlenecks and dependency gates. By providing reusable infrastructure modules and intent-based configurations, platform teams dramatically reduce infrastructure friction. This self-service model powers faster deployments, increased autonomy, and fewer delays.&lt;/p&gt;
&lt;p&gt;However, &lt;strong&gt;infrastructure provisioning alone isn’t enough to improve developer experience&lt;/strong&gt;. Even with efficient provisioning, developers can still face inconsistent local setups, sluggish CI/CD pipelines, poor documentation, and fragmented tooling. These obstacles quietly reduce &lt;strong&gt;developer productivity&lt;/strong&gt;, slow &lt;strong&gt;developer velocity&lt;/strong&gt;, and increase operational overhead.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/is83TV8nrTg?rel=0?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Your platform’s ability to support daily workflows directly impacts core success metrics like &lt;strong&gt;time to first commit&lt;/strong&gt;, &lt;strong&gt;time to production&lt;/strong&gt;, and &lt;strong&gt;developer satisfaction&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;p&gt;When improving &lt;strong&gt;developer experience&lt;/strong&gt; as part of a platform engineering initiative, measure:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Time to First Commit&lt;/strong&gt; – How quickly a developer can become productive in a new environment or project. Pay particular attention to new hires.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Time to Production&lt;/strong&gt; – How fast code moves from commit to successful production release.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Developer Satisfaction&lt;/strong&gt; – Regularly survey friction points and prioritize fixing the top issues.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Friction logs&lt;/strong&gt; are also a powerful way to identify roadblocks, recording every obstacle encountered during common workflows. This reveals hidden friction and provides a roadmap for improvement.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;A great &lt;strong&gt;developer experience&lt;/strong&gt; accelerates iteration, improves problem-solving, and drives better products. An &lt;strong&gt;internal developer platform (IDP)&lt;/strong&gt; designed for &lt;a href="https://www.pulumi.com/blog/developer-experience-business-critical/"&gt;DevEx is a fundamental competitive advantage&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="the-service-catalog-foundation-for-developer-experience"&gt;The Service Catalog: Foundation for Developer Experience&lt;/h2&gt;
&lt;p&gt;Developer friction often starts with something simple: not knowing what’s already available or who owns it. Without a single, reliable source of truth, developers reinvent the wheel, duplicate work, and waste time chasing information.&lt;/p&gt;
&lt;p&gt;A well-structured &lt;strong&gt;service catalog&lt;/strong&gt; in your internal developer platform addresses this by providing a central repository for all services and applications. At its most basic, it&amp;rsquo;s just a web page with each service&amp;rsquo;s README, service metadata, and operational information.&lt;/p&gt;
&lt;p&gt;But, properly structured, this delivers some large benefits:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Context at a Glance&lt;/strong&gt;: Every catalog entry follows a standardized template, capturing core information such as purpose, interfaces (REST, gRPC, events, etc.), language/runtime, and environment requirements. New developers can quickly grasp a service&amp;rsquo;s role and easily jump into productive work.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Discoverability and Reuse&lt;/strong&gt;: Without a catalog, teams often recreate services they don&amp;rsquo;t realize already exist. Duplicate services not only waste resources but lead to fragmented knowledge and inconsistencies. With a catalog, developers quickly find and leverage existing solutions, increasing reusability and reducing churn.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ownership Transparency&lt;/strong&gt;: Each entry in the catalog explicitly names team ownership, CODEOWNERS files, and pertinent contact details like Slack channels or on-call rotations. Clear ownership streamlines communication, issue resolution, and collaboration, saving precious time and avoiding frustrations.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A &lt;strong&gt;service catalog&lt;/strong&gt; supports autonomy, reduces wasted cycles, and increases &lt;strong&gt;developer velocity&lt;/strong&gt; by making essential information instantly accessible.&lt;/p&gt;
&lt;p&gt;But a Service Catalog alone isn&amp;rsquo;t enough. To fully streamline and enhance your development workflow, you must pair it with consistent local environments and reliable CI/CD processes. Both of which can be serviced by standardized service templates.&lt;/p&gt;
&lt;h2 id="service-templates-golden-paths-for-improving-developer-productivity"&gt;Service Templates: Golden Paths for Improving Developer Productivity&lt;/h2&gt;
&lt;p&gt;Earlier in this &lt;a href="https://www.pulumi.com/blog/platform-engineering-pillars-2/"&gt;series&lt;/a&gt;, we introduced &lt;strong&gt;service templates&lt;/strong&gt; as a way to scaffold consistent, ready-to-go services. In an &lt;strong&gt;internal developer platform&lt;/strong&gt;, templates aren’t just for infrastructure, they’re the &lt;strong&gt;&lt;a href="https://www.pulumi.com/blog/golden-paths-infrastructure-components-and-templates/"&gt;golden paths&lt;/a&gt;&lt;/strong&gt; that define how developers start and succeed.&lt;/p&gt;
&lt;p&gt;If 90% of new services are Go GRPC services, then a carefully crafted, opinionated go GRPC service blueprint will remove a lot of friction. It will give developers everything they need to begin delivering real business value immediately. A few select curated options of common starting points will help guide developers to generate projects complete with standardized structure, built-in quality checks, documentation, and clear guidelines for contribution.&lt;/p&gt;
&lt;p&gt;A great service template provides:&lt;/p&gt;
&lt;h3 id="project-bootstrapping"&gt;Project Bootstrapping&lt;/h3&gt;
&lt;p&gt;The template quickly scaffolds an entire service—including all necessary files and directories. Everything essential for your organization&amp;rsquo;s standards should be included, from CI/CD configurations and dependency management to directory structures and environment-specific configuration files.&lt;/p&gt;
&lt;h3 id="built-in-quality-tools"&gt;Built-in Quality Tools&lt;/h3&gt;
&lt;p&gt;Templates embed guardrails that maintain consistency and high-quality standards across teams:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Linting rules and formatters&lt;/strong&gt;: Tools like ESLint, flake8, or Checkstyle that ensure code style and consistency across your codebase.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Testing frameworks&lt;/strong&gt;: Unit tests, integration tests, and contract testing tools set up and pre-configured (JUnit, pytest, Jest, Pact), helping teams maintain high quality right out of the gate.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="documentation-and-contribution-guides"&gt;Documentation and Contribution Guides&lt;/h3&gt;
&lt;p&gt;Many platform teams overlook how clear, consistent documentation is crucial for initial and ongoing developer productivity. Every new service should arrive out-of-the-box with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;README&lt;/strong&gt;: Describing at a glance what the project does, why it exists, and how to run it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documentation&lt;/strong&gt;: Service template establishes a place for service-specific documentation that will be surfaced in the Service catalog. Markdown explanations, example input/output, and real-world use-cases.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contribution guidelines and coding standards&lt;/strong&gt;: Clearly documenting the expectations, conventions, and standards every developer can follow with confidence.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Embedding this documentation into templates ensures that even brand-new services are discoverable, understandable, and maintainable — right from their first commit.&lt;/p&gt;
&lt;p&gt;A great template means the service catalog becomes more than just an organizational listing. These thoughtfully designed templates can directly power it. Every new service entering your catalog aligns neatly with your organization&amp;rsquo;s best practices in code quality, documentation, and process standards.&lt;/p&gt;
&lt;p&gt;When paired with your &lt;strong&gt;service catalog&lt;/strong&gt;, templates ensure every new service aligns with best practices — boosting &lt;strong&gt;developer productivity&lt;/strong&gt; and &lt;strong&gt;developer velocity&lt;/strong&gt; from day one.&lt;/p&gt;
&lt;h2 id="streamlining-local-development-to-reduce-friction"&gt;Streamlining Local Development to Reduce Friction&lt;/h2&gt;
&lt;p&gt;One of the fastest ways to improve &lt;strong&gt;time to first commit&lt;/strong&gt; is to standardize local development workflows through your &lt;strong&gt;internal developer platform&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Your service templates provide the ideal mechanism for simplifying how quickly developers can get projects running locally. By embedding standardized local-development tooling into these templates, you ensure they&amp;rsquo;re available consistently across your service catalog. Good templates typically contain:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A preconfigured, containerized environments:&lt;/strong&gt; Using Dev Containers, Docker Compose, or similar tools lets developers launch an environment that closely resembles production with minimal friction, completely avoiding dependency drift or local environment inconsistencies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simple, standardized command runner:&lt;/strong&gt; Makefiles, just scripts, package.json scripts—whatever works as they&amp;rsquo;re consistently documented and easy to run commands such as &lt;code&gt;make build&lt;/code&gt;, &lt;code&gt;make test&lt;/code&gt;, or &lt;code&gt;make lint&lt;/code&gt;. Teams might diverge on preferred tooling over time; if so, simply record clearly in the project&amp;rsquo;s readme (and thus in your service catalog) exactly what&amp;rsquo;s needed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Built-in documentation:&lt;/strong&gt; Each service template includes clear instructions in a README outlining step-by-step processes for setup, running tests, and other everyday developer tasks.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With these pieces fully integrated into your catalog and templates, you significantly reduce friction and speed up development cycles, moving you meaningfully closer to improving your core developer-experience metrics.&lt;/p&gt;
&lt;p&gt;The next critical area we need to address is how smoothly and quickly code moves from a local commit to a production release.&lt;/p&gt;
&lt;h2 id="cicd-integration-fast-reliable-pipelines-for-experienced-developers"&gt;CI/CD Integration: Fast, Reliable Pipelines for Experienced Developers&lt;/h2&gt;
&lt;p&gt;Experienced developers cite unreliable or slow pipelines as major productivity killers. Their frustrations often revolve around flaky builds, unreliable tests, and cumbersome environment setups. Slow or unpredictable pipelines break developer flow, causing frustration, delays, and lower productivity.&lt;/p&gt;
&lt;p&gt;A strong &lt;strong&gt;internal developer platform&lt;/strong&gt; integrates fast, stable, and predictable CI/CD processes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Stable, Fast Feedback:&lt;/strong&gt;&lt;br&gt;
Every new service comes pre-configured with build acceleration strategies like intelligent caching (Gradle, Bazel, Docker layers) and automatic parallel test execution. Stable and speedy pipelines help teams iterate quickly and confidently.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ephemeral, Self-service Environments:&lt;/strong&gt;&lt;br&gt;
Instead of battling shared staging environments – which get blocked by other teams— developers spin up short-lived testing environments directly from pull requests. Need to test your payment-service changes against the latest user-authentication service? Create a dedicated, temporary environment on-the-fly, validate interactions, then shut it down automatically when finished.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Test Reliability:&lt;/strong&gt;&lt;br&gt;
Strictly enforce policies to quarantine flaky tests, quickly escalate notifications to responsible developers, and provide clear paths for fixing instabilities. Proactive flakiness management ensures credibility and reliability for test pipelines over time.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Reliable CI/CD pipelines protect developer focus, reduce cognitive load, maintain &lt;strong&gt;developer velocity&lt;/strong&gt;, and prevent wasted time, increasing &lt;strong&gt;developer productivity&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;p&gt;Developer experience is a vast, nuanced topic, and there&amp;rsquo;s much more a comprehensive platform can include than we&amp;rsquo;ve been able to explore fully here. Even just within your Service Catalog and developer workflows, valuable additions might include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Onboarding Guides&lt;/strong&gt;: Step-by-step guides rapidly bringing new developers up to speed on teams, tools, and processes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Frictionless Product Backlog Integration&lt;/strong&gt;: Quick access from codebases to the product backlog to connect code directly with the business context.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Code Coverage &amp;amp; Quality Dashboards&lt;/strong&gt;: Easy-to-use visualizations offering fast insights into quality metrics and test coverage.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deprecation Notices &amp;amp; Change Management&lt;/strong&gt;: Automated notifications about changes, upcoming maintenance, or decommission plans to keep developers informed and prepared.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pre-built Common Components&lt;/strong&gt;: Shared libraries and components solving everyday needs like logging, authentication, validation, and configuration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And that&amp;rsquo;s truly just scratching the surface. But remember: Platform engineering is an iterative journey, you don&amp;rsquo;t need to tackle every single area all at once. If you start with friction logs and surveys to regularly identify and address pain points affecting daily workflows, you&amp;rsquo;ll already be heading confidently in the right direction.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="conclusion-idps-as-a-devex-multiplier"&gt;Conclusion: IDPs as a DevEx Multiplier&lt;/h2&gt;
&lt;p&gt;A great &lt;strong&gt;developer experience&lt;/strong&gt; is not optional — it’s a force multiplier for &lt;strong&gt;developer productivity&lt;/strong&gt; and &lt;strong&gt;developer velocity&lt;/strong&gt;. An &lt;strong&gt;internal developer platform&lt;/strong&gt; with service catalogs, golden path templates, streamlined local dev, and integrated CI/CD removes friction at every step of the developer journey.&lt;/p&gt;
&lt;p&gt;The result? Faster onboarding, quicker delivery, and happier teams.&lt;/p&gt;
&lt;p&gt;With Pulumi, platform teams can automate consistent environments, implement golden paths, and enable self-service infrastructure that scales with your organization.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/templates/"&gt;Organization Templates&lt;/a&gt; enables standardized service creation and consistent developer experiences&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/concepts/stack/#stackreferences"&gt;Stack References&lt;/a&gt; for managing dependencies between environments and services&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/pulumi-cloud/deployments/"&gt;Pulumi Deployments&lt;/a&gt; to streamline CI/CD workflows and enable self-service infrastructure&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/pulumi-cloud/deployments/review-stacks/"&gt;Review Stacks&lt;/a&gt; for creating ephemeral, on-demand testing environments&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/automation-api/"&gt;Automation API&lt;/a&gt; for programmatically managing infrastructure and implementing platform workflows&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By prioritizing &lt;strong&gt;developer experience&lt;/strong&gt; in your &lt;a href="https://www.pulumi.com/blog/idp-strategy-planning-self-service-infrastructure-that-balances-developer-autonomy-with-operational-control/"&gt;platform engineering strategy&lt;/a&gt;, you empower teams to move faster, build better, and maintain momentum, making your internal developer platform one of the most valuable assets in your organization.&lt;/p&gt;</description><author>Adam Gordon Bell</author><category>platform-engineering</category></item><item><title>Self-Service Infrastructure: From Tickets to Tools</title><link>https://www.pulumi.com/blog/platform-engineering-pillars-3/</link><pubDate>Thu, 06 Mar 2025 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/platform-engineering-pillars-3/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/platform-engineering-pillars-3/index.png" /&gt;
&lt;p&gt;&lt;a href="https://www.pulumi.com/blog/series/platform-engineering-pillars/"&gt;Previous articles&lt;/a&gt; in this series explored &lt;a href="https://www.pulumi.com/what-is/what-is-platform-engineering/"&gt;platform engineering&lt;/a&gt; principles and how Infrastructure as Code creates a solid foundation. But there&amp;rsquo;s still an important challenge to address: the infrastructure provisioning process itself. Without proper modularity and a clear separation between intent and infrastructure details, things get messy—leading to friction, delays, and unnecessary complexity.&lt;/p&gt;
&lt;h2 id="the-missing-layer-abstraction-through-modularity"&gt;The Missing Layer: Abstraction Through Modularity&lt;/h2&gt;
&lt;p&gt;The root issue here is how teams define and work with infrastructure. Without good abstraction layers, infrastructure code typically becomes unwieldy and complex.&lt;/p&gt;
&lt;p&gt;You end up with&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Highly detailed specifications that require deep domain knowledge&lt;/li&gt;
&lt;li&gt;Environment-specific configurations with subtle variations across resources&lt;/li&gt;
&lt;li&gt;Complex interdependencies that are difficult to trace and verify&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This approach burdens developers with low-level details they don’t need to worry about, while platform teams become gatekeepers, reviewing intricate configurations. Alternatively, a traditional ticketing model takes over, discarding the benefits of modern practices.&lt;/p&gt;
&lt;div style="text-align: center; width: 100%; margin: 0 auto;"&gt;
&lt;img src="platform.png" alt="" style="width: 100%;"&gt;
&lt;figcaption&gt;
&lt;i&gt;Solution: A Platform Engineering Approach&lt;/i&gt;
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;p&gt;What’s missing is a proper abstraction layer between raw infrastructure code and developer needs. &lt;strong&gt;That layer is the platform. A Platform for self-service infrastructure.&lt;/strong&gt; Without modular, reusable infrastructure patterns, every new application deployment becomes an exercise in starting from scratch and defining every detail instead of just stating what the application requires and deferring details to the platform.&lt;/p&gt;
&lt;h3 id="common-anti-patterns-without-proper-abstraction"&gt;Common Anti-Patterns Without Proper Abstraction&lt;/h3&gt;
&lt;p&gt;Without this modular platform approach to cloud infrastructure, organizations typically fall into one of two problematic patterns.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Copy-Paste Anti-Pattern&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Developers copy infrastructure code from previous projects. They find something similar, duplicate large chunks of configuration, make a few changes, and hope everything is right, and submit it for review. This leads to a brittle system with siloed knowledge. Security fixes must be applied manually across every instance – no one has a full picture of the infrastructure – and Day 2 Operations become error prone.&lt;/p&gt;
&lt;p&gt;And this isn&amp;rsquo;t limited to infrastructure: CI scripts get copied and modified, Dockerfiles and Kubernetes manifests get copied and modified.&lt;/p&gt;
&lt;p&gt;It feels like control, but it&amp;rsquo;s not. Developers still rely on platform specialists to validate changes and catch mistakes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Ticketing Anti-Pattern&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Alternatively, some organizations rely on rigid ticketing systems for infrastructure changes. A platform specialist applies writes and applies all changes, ensuring consistency — but also creating bottlenecks. Developers might wait days or weeks for simple modifications, leaving the process entirely unfinished.&lt;/p&gt;
&lt;p&gt;This guarantees expert oversight but strips developers of autonomy, making the platform team a bottleneck for every deployment.&lt;/p&gt;
&lt;p&gt;Neither anti-pattern strikes a balance between standardization and autonomy, leading to either sloppy infrastructure (copy-paste) or slow deployments (ticketing).&lt;/p&gt;
&lt;h3 id="the-resulting-problems"&gt;The Resulting Problems&lt;/h3&gt;
&lt;h4 id="day-1-bottleneck-slow-manual-infrastructure--developer-gridlock"&gt;Day 1 Bottleneck: Slow, Manual Infrastructure = Developer Gridlock&lt;/h4&gt;
&lt;p&gt;When provisioning becomes a bottleneck, developers with finished code wait days for infrastructure. This disrupts focus and forces frequent context switching. They juggle tasks to stay productive and then have to rebuild focus when the requested infrastructure is ready.&lt;/p&gt;
&lt;p&gt;These delays slow the entire delivery pipeline. Teams add buffer time for infrastructure delays, projects slip, and the business sees technology as slow-moving. Meanwhile, the real culprit is slow provisioning that kills momentum.&lt;/p&gt;
&lt;h4 id="day-2-and-beyond-chaos-sprawling-unpredictable-infrastructure--operational-nightmare"&gt;Day 2 And Beyond Chaos: Sprawling, Unpredictable Infrastructure = Operational Nightmare&lt;/h4&gt;
&lt;p&gt;Day 1 bottlenecks frustrate developers, but the real long-term cost is patching hell. Copy-paste infrastructure creates hundreds of snowflakes, each needing manual attention for every security fix or version bump. One-off configurations and custom environments pile up, creating technical debt no one fully understands.&lt;/p&gt;
&lt;p&gt;Operations teams are overwhelmed by the basic, critical task of simply keeping the lights on across a sprawling, inconsistent landscape.&lt;/p&gt;
&lt;h2 id="the-solution-a-platform-of-options-and-self-service"&gt;The Solution: A Platform of Options and Self Service&lt;/h2&gt;
&lt;p&gt;A Self-service platform is the solution to these problems. The platform is this curated list of modules, providing application developers with a set of pre-built options that let them work at a higher level of abstraction. By choosing from these modules, developers express their intent for infrastructure, leaving the platform to handle the implementation details. Let me explain.&lt;/p&gt;
&lt;p&gt;A strong self-service approach divides responsibilities into two clear layers:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Level 1: Platform Team (Module Definition)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Builds and updates reusable infrastructure modules&lt;/li&gt;
&lt;li&gt;Bakes in security, compliance, and best practices&lt;/li&gt;
&lt;li&gt;Hides complexity so developers don’t need to worry about copy and pasting low-level details&lt;/li&gt;
&lt;li&gt;Sets guardrails for scalability, security, and compliance&lt;/li&gt;
&lt;li&gt;Updates modules as technologies and needs change&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Level 2: Application Developers (Module Consumption)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Browse a catalog of ready-made modules&lt;/li&gt;
&lt;li&gt;Compose pre-built modules to set up application infrastructure&lt;/li&gt;
&lt;li&gt;Spend time on code, not cloud configurations&lt;/li&gt;
&lt;li&gt;Operate within built-in guardrails&lt;/li&gt;
&lt;li&gt;Deploy applications faster using standardized components&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This setup keeps things fast and flexible. Developers get the autonomy to build without reinventing infrastructure, while platform teams maintain control where it matters.&lt;/p&gt;
&lt;h2 id="the-power-of-intent-based-specification"&gt;The Power of Intent-Based Specification&lt;/h2&gt;
&lt;div style="text-align: center; width: 100%; margin: 0 auto;"&gt;
&lt;img src="developer-intent.png" alt="" style="width: 100%;"&gt;
&lt;figcaption&gt;
&lt;i&gt;Layer of Abstraction to the Rescue&lt;/i&gt;
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;p&gt;The two-level approach works by replacing over-specification with intent-based design.&lt;/p&gt;
&lt;p&gt;Over-specification happens when developers specify too many infrastructure details that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Require expertise they might not have&lt;/li&gt;
&lt;li&gt;Tightly coupled infrastructure to specific implementations&lt;/li&gt;
&lt;li&gt;Make future changes hard or even impossible&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Intent-based specification means developers state what their app needs, not how to implement it, meaning:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Platform teams can update underlying infrastructure without breaking applications&lt;/li&gt;
&lt;li&gt;New capabilities can be added without disrupting existing applications&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Focusing on what developers need, not how it&amp;rsquo;s built, creates a system that’s easier to maintain and evolve for both platform teams and developers.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s walk through an example.&lt;/p&gt;
&lt;h2 id="practical-example-java-application-with-kafka-postgresql-and-redis"&gt;Practical Example: Java Application with Kafka, PostgreSQL, and Redis&lt;/h2&gt;
&lt;p&gt;The platform team provides reusable modules:&lt;/p&gt;
&lt;h3 id="java-application-module"&gt;Java Application Module&lt;/h3&gt;
&lt;p&gt;This module handles everything needed to run a Java app. It covers the Java runtime, memory settings, JVM options, scaling rules, health checks, monitoring, and Kubernetes namespacing. It also manages containers, security scans, resource limits, and logging integration—so developers don’t have to.&lt;/p&gt;
&lt;h4 id="kafka-integration-module"&gt;Kafka Integration Module&lt;/h4&gt;
&lt;p&gt;Similarly, this module simplifies Kafka integration, handling broker connections, authentication, topics, and security. It takes care of SASL/SSL authentication, replication factors, ACLs, and encryption. Developers just specify topics and schemas.&lt;/p&gt;
&lt;p&gt;And so on for common infrastructure components used in the org.&lt;/p&gt;
&lt;h3 id="level-2-developer-implementation"&gt;Level 2: Developer Implementation&lt;/h3&gt;
&lt;p&gt;Developers use a simpler interface. To deploy a new Java service with Kafka, PostgreSQL, and Redis, they only need to specify the required input parameters in the platform-defined modules:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Developer&amp;#39;s intent-based configuration&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;application&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;order-processing-service&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;module&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;java-application&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;2GB&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;scaling&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;integrations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;module&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;kafka&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;topics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;incoming-orders&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;partitions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;processed-orders&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;partitions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;consumer_group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;order-processors&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;module&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;postgresql&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;orders_db&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;medium&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;backup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;enabled&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;module&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;redis&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;small&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3600&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;( Note: YAML not required. This pseudo-code could be UI driven, or it could be written in your programming language of choice. The point is it specifies intent and defers specifics to the platform. )&lt;/p&gt;
&lt;h2 id="solving-day-1-and-day-2-challenges"&gt;Solving Day 1 and Day 2 Challenges&lt;/h2&gt;
&lt;p&gt;The intent-based specification solves immediate and long-term problems:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Self-Service Transformation in Action:&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Developer Experience&lt;/th&gt;
&lt;th&gt;Platform Response&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Submits simple intent-based configuration&lt;/td&gt;
&lt;td&gt;Expands into complete infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Focuses on application needs, not infrastructure details&lt;/td&gt;
&lt;td&gt;Provisions resources with appropriate sizing and security controls&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Specifies &amp;ldquo;what&amp;rdquo; is needed, not &amp;ldquo;how&amp;rdquo; to configure it&lt;/td&gt;
&lt;td&gt;Handles networking, monitoring, backups, and credentials&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Limited expertise required in Kubernetes, networking, or security&lt;/td&gt;
&lt;td&gt;Implements best practices automatically across all deployments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Maintains and updates the underlying implementation over time&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This approach transforms infrastructure from a developer burden into an organizational asset that becomes more valuable over time, rather than decaying into technical debt.&lt;/p&gt;
&lt;h3 id="developer-interaction-models-choosing-your-platform-interface"&gt;&lt;strong&gt;Developer Interaction Models: Choosing Your Platform Interface&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Hopefully, now you are bought in on the value of separate layers of abstraction and building a platform to expose those.&lt;/p&gt;
&lt;p&gt;The next key decision is how developers will actually &lt;em&gt;use&lt;/em&gt; this platform. The ideal interface balances ease of use with the necessary flexibility. Organizations and requirements vary, so a spectrum of interaction models exists, each with its own trade-offs. Tree common approaches are: CLI-Driven Infrastructure as Code, the Self-Service Portal, and a Hybrid model.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;CLI-Driven&lt;/th&gt;
&lt;th&gt;Self-Service Portal&lt;/th&gt;
&lt;th&gt;Hybrid Approach&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Developer Experience&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Code-based, using IaC modules and components directly&lt;/td&gt;
&lt;td&gt;Click-based UI with forms and catalogs&lt;/td&gt;
&lt;td&gt;Portal for common tasks, code for customization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Code Visibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full visibility and control&lt;/td&gt;
&lt;td&gt;Limited or no visibility&lt;/td&gt;
&lt;td&gt;Visible but with varying levels of access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Governance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Trust-based or PR reviews&lt;/td&gt;
&lt;td&gt;Enforced through portal constraints&lt;/td&gt;
&lt;td&gt;Guided paths with managed exceptions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Learning Curve&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Higher (requires IaC knowledge)&lt;/td&gt;
&lt;td&gt;Lower (minimal infrastructure knowledge)&lt;/td&gt;
&lt;td&gt;Moderate (basic UI with optional advanced use)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best For&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Experienced teams needing flexibility&lt;/td&gt;
&lt;td&gt;Teams prioritizing speed and standardization&lt;/td&gt;
&lt;td&gt;Organizations with diverse skill levels&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id="1-cli-driven-infrastructure-as-code-empowering-developers-with-familiar-tools"&gt;&lt;strong&gt;1. CLI-Driven Infrastructure as Code: Empowering Developers with Familiar Tools&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;In this approach, platform teams build reusable infrastructure modules while developers consume them directly through code. Developers use familiar IaC tools like Pulumi CLI to instantiate these modules, with changes flowing through version control like any other code.&lt;/p&gt;
&lt;p&gt;The key strength is transparency and familiarity. Developers maintain visibility and ownership of their intent-based definitions. This code-centric workflow integrates naturally with existing development practices.&lt;/p&gt;
&lt;p&gt;However, this flexibility creates governance challenges and requires either strong trust or approval processes that might reintroduce bottlenecks. The approach also assumes developers have some IaC knowledge, making it best suited for organizations with mature DevOps practices and experienced teams.&lt;/p&gt;
&lt;h4 id="2-self-service-portal-click-ops-simplicity-for-rapid-provisioning"&gt;&lt;strong&gt;2. Self-Service Portal: Click-Ops Simplicity for Rapid Provisioning&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;The self-service portal approach creates a web interface where developers provision infrastructure without writing code. They simply browse a catalog, fill out forms, and click buttons to deploy standardized resources—all pre-configured by the platform team.&lt;/p&gt;
&lt;p&gt;This approach keeps infrastructure code completely centralized and hidden from developers. The portal translates simple selections (&amp;ldquo;Add Database: Medium Size&amp;rdquo;) into the appropriate underlying configurations, ensuring perfect compliance with organizational standards.&lt;/p&gt;
&lt;p&gt;The result is dramatically reduced friction and learning curve, enabling even infrastructure novices to deploy resources in minutes. However, this simplicity comes at the cost of flexibility. Customizations and unique needs typically require platform team involvement, making this model ideal for organizations with predictable workloads or where standardization outweighs customization needs.&lt;/p&gt;
&lt;h4 id="3-hybrid-approach-code-backed-self-service"&gt;&lt;strong&gt;3. Hybrid Approach: Code-Backed Self-Service&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;The hybrid approach generates and modifies infrastructure code via pull requests from UI interactions. When developers configure resources through the portal, the system commits code to repositories automatically, creating a transparent workflow where changes are visible, reviewable, and auditable through standard Git processes.&lt;/p&gt;
&lt;p&gt;This model provides both simplicity and flexibility. Routine tasks remain point-and-click while complex scenarios allow direct code editing within the same PR workflow. The approach maintains a complete audit trail through commit history while giving platform teams visibility into all changes.&lt;/p&gt;
&lt;p&gt;A significant tradeoff is implementation complexity: building a system that generates commits and PRs based on version control integration requires substantial investment. Organizations must weigh this upfront cost against the long-term benefits of combining UI simplicity with code transparency.&lt;/p&gt;
&lt;h4 id="lifecycle-management"&gt;Lifecycle Management&lt;/h4&gt;
&lt;p&gt;More effective lifecycle management is an advantage of a platform approach. Consider migrating from Kafka to AWS Kinesis. Traditionally, this would involve significant application developer effort, including updating connection details, authentication, and application logic. However, with the two-level abstraction and intent-based approach described earlier, this migration becomes simpler. Platform teams introduce a new Kinesis module matching the existing Kafka module&amp;rsquo;s intent-based interface. Data is mirrored, and developers can plan a switchover. Migration will not be free, but efforts and coordination will be reduced.&lt;/p&gt;
&lt;p&gt;Life-Cycle management is a huge topic in Day 2 operations and outside the scope of what can be covered here, but introducing abstractions and a platform of modules will pay off long term.&lt;/p&gt;
&lt;h3 id="looking-ahead-operational-excellence-and-beyond"&gt;Looking Ahead: Operational Excellence and Beyond&lt;/h3&gt;
&lt;p&gt;The two-level abstraction model does not just simplify provisioning. It also lays a strong foundation for operational excellence. By standardizing infrastructure through reusable modules and intent-based specifications, we create consistency and visibility that significantly simplify critical operational concerns such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Drift Detection and Reconciliation:&lt;/strong&gt; Standardized modules make it easier to detect and correct deviations from desired state.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Continuous Compliance and Security:&lt;/strong&gt; Centralized module definitions allow security and compliance controls to be consistently enforced and audited.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cost Management and FinOps:&lt;/strong&gt; Intent-based specifications enable clearer cost attribution and optimization opportunities.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Observability and Incident Response:&lt;/strong&gt; Consistent infrastructure patterns simplify monitoring, alerting, and troubleshooting.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Later articles in this series will explore each of these areas in greater depth, demonstrating how a platform approach provides a powerful foundation for addressing them effectively.&lt;/p&gt;
&lt;h2 id="self-service-with-pulumi"&gt;Self-Service with Pulumi&lt;/h2&gt;
&lt;p&gt;The two-level architecture of IDP self-service creates a powerful interface between platform teams and application developers. By embracing intent-based specification, this approach enables developers to clearly express what they need with minimal complexity while giving platform teams the flexibility to implement and evolve the underlying infrastructure.&lt;/p&gt;
&lt;p&gt;As you build out your self-service approach, consider how Pulumi might fit in. With Pulumi, you get:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/iac/concepts/resources/components/"&gt;Component resources&lt;/a&gt; for creating reusable infrastructure modules that encapsulate best practices.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/pulumi-cloud/developer-portals/templates/"&gt;Organization templates&lt;/a&gt; for building a catalog of infrastructure templates.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/product/packages/"&gt;Packages&lt;/a&gt; in your language of choice for a code-first self-service workflow.&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://www.pulumi.com/product/internal-developer-platforms/"&gt;Platform-in-the-box&lt;/a&gt; experience for those who want a portal-first experience for self-service infrastructure.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/pulumi-cloud/developer-portals/backstage/"&gt;A Backstage plugin&lt;/a&gt; for those who prefer to use Backstage for an IDP.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A great self-service workflow and abstraction layer are key to making platform engineering work. &lt;a href="https://www.pulumi.com/blog/series/platform-engineering-pillars/"&gt;Next in this series&lt;/a&gt;, we’ll tackle developer experience directly.&lt;/p&gt;</description><author>Adam Gordon Bell</author><category>platform-engineering</category></item><item><title>Provisioning: From Chaos to Control</title><link>https://www.pulumi.com/blog/platform-engineering-pillars-2/</link><pubDate>Thu, 27 Feb 2025 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/platform-engineering-pillars-2/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/platform-engineering-pillars-2/index.png" /&gt;
&lt;p&gt;Provisioning is the first &lt;a href="https://www.pulumi.com/blog/series/platform-engineering-pillars/"&gt;pillar of platform engineering&lt;/a&gt;. Without consistent infrastructure provisioning – the automated creation and management of the underlying cloud resources – the rest of the platform suffers. Self-service, governance, and streamlined developer workflows all depend on it. Ultimately, a self-service layer on top of your cloud infrastructure is the goal, enabling developers to quickly and safely provision the resources they need, while adhering to organizational best practices and policies. But before self-service, the foundation of a good IDP is a robust and reliable provisioning system.&lt;/p&gt;
&lt;p&gt;By defining cloud resources as code and automating deployments, platform engineering teams ensure every environment – development, staging, and production – stays consistent and maintainable. This cuts down on configuration drift, reduces manual work, and supports auditable, collaborative workflows for every change.&lt;/p&gt;
&lt;p&gt;Let’s explore how platform engineering teams can achieve this by version-controlling infrastructure, automating deployments, separating environments properly, and limiting console interventions. By applying these principles, teams can create a platform where developers can move fast without breaking things, and where infrastructure supports innovation rather than slowing it down.&lt;/p&gt;
&lt;div class="note note-warning"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-warning-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;h3 id="top-5-iac-anti-patterns"&gt;Top 5 IaC anti-patterns&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Console-Only Changes:&lt;/strong&gt; Making &amp;ldquo;quick fixes&amp;rdquo; in production without updating IaC leads to drift, confusion, and hidden risks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hard-Coded Secrets:&lt;/strong&gt; Sensitive values in code or config files are a security liability. Use encrypted state or a secrets manager.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Copy-Paste Config:&lt;/strong&gt; Reusing half-baked snippets across projects creates cruft. Break out shared modules or templates.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No Clear File Structure:&lt;/strong&gt; Throwing all configs into a single file or folder obscures dependencies. Organize by environment, service, or module.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Skipping Peer Reviews:&lt;/strong&gt; Infrastructure mistakes can be costly. A simple pull request process can catch errors before deployment.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="-tip-read-our-list-of-iac-best-practices-"&gt;💡 Tip: Read our list of &lt;a href="https://www.pulumi.com/blog/iac-recommended-practices-code-organization-and-stacks/"&gt;IaC Best Practices&lt;/a&gt; 💡&lt;/h4&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="building-a-reliable-iac-foundation"&gt;Building a reliable IaC foundation&lt;/h2&gt;
&lt;p&gt;&lt;img src="infra-workflow.png" alt="infra-workflow"&gt;&lt;/p&gt;
&lt;p&gt;It’s not enough to simply define your resources in code. A reliable IaC foundation should ensure that infrastructure changes move smoothly from idea to production, without surprising developers or straining operations. Here’s how to structure these workflows so they’re both transparent and resilient:&lt;/p&gt;
&lt;h2 id="version-control-as-the-single-source-of-truth"&gt;Version control as the single source of truth&lt;/h2&gt;
&lt;p&gt;&lt;img src="commits.png" alt="iac commits"&gt;&lt;/p&gt;
&lt;p&gt;Whether you’re using GitHub, GitLab, or something else, every infrastructure definition needs to live in version control. This is just the basics – and hopefully, you are already doing this, as you have been for years. Version-controlled changes allow you to track history, compare revisions, and peer review. A pull request might catch a misconfigured subnet or a database that was accidentally left publicly accessible, mistakes that could be catastrophic if they slip into production. This also means anyone can propose improvements or bug fixes, expanding the pool of expertise beyond a single “infrastructure guru.”&lt;/p&gt;
&lt;h2 id="automating-changes-with-cicd"&gt;Automating changes with CI/CD&lt;/h2&gt;
&lt;p&gt;&lt;img src="ci.png" alt="iac ci"&gt;&lt;/p&gt;
&lt;p&gt;Next, consider how to apply infrastructure modifications after they’re merged into main. A CI pipeline can automatically run a “plan” or “preview,” then only proceed with the actual update if no blockers appear. Policy checks can also be added, enforcing rules such as “no public S3 buckets” or “use encrypted volumes only.” By codifying these conditions, you avoid manual verification steps and reduce the chance of risky configurations slipping past manual review.&lt;/p&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;h3 id="trunk-based-infrastructure-workflow"&gt;Trunk-based infrastructure workflow&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Protected main Branch:&lt;/strong&gt; No direct commits&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pull Request Merges:&lt;/strong&gt; Integrate changes via Pull Requests&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automated PR Checks:&lt;/strong&gt; Previews and Policy-as-Code run on every PR&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Merge:&lt;/strong&gt; Merge to main triggers automated deployments&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="distinguish-each-environment"&gt;Distinguish each environment&lt;/h2&gt;
&lt;p&gt;&lt;img src="envs.png" alt="each environment"&gt;&lt;/p&gt;
&lt;p&gt;Not all environments have the same requirements or budgets, so it’s wise to separate dev, staging, and production configurations. You might share core modules across these environments, but keep each environment’s parameters and secrets in independent config files or stack definitions. This deliberate separation ensures that dev changes are tested in a realistic but contained environment before they can impact production. As a result, teams can experiment more confidently without risking live workloads.&lt;/p&gt;
&lt;h2 id="modularity-and-reusability"&gt;Modularity and reusability&lt;/h2&gt;
&lt;p&gt;A key principle of effective IaC, and a cornerstone of any good Internal Developer Platform, is reusability. You shouldn&amp;rsquo;t be copying and pasting large chunks of infrastructure code. Instead, you should build reusable components (or modules, or constructs, depending on the tool&amp;rsquo;s terminology) that encapsulate best practices and can be easily consumed by developers. This achieves several critical goals:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Consistency:&lt;/strong&gt; Ensures that similar resources are provisioned in the same way, reducing configuration drift.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Abstraction:&lt;/strong&gt; Hides complexity from developers. They don&amp;rsquo;t need to know how a secure, compliant database is created, just that it is.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Maintainability:&lt;/strong&gt; Changes to the underlying implementation of a component can be rolled out to all consumers of that component.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Speed:&lt;/strong&gt; Developers can provision infrastructure faster by using pre-built components.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reduced Errors:&lt;/strong&gt; Standard components means that there will be less opportunities to make mistakes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Think of these reusable components as the &amp;ldquo;building blocks&amp;rdquo; of your IDP. Your platform team creates and maintains them, and developers use them to assemble their applications.&lt;/p&gt;
&lt;p&gt;Regardless of the specific IaC tool you choose, reusable components generally share these characteristics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Parameterized Inputs:&lt;/strong&gt; Components accept inputs (parameters, properties, configuration – the term varies) that allow users to customize their behavior. For example, a &amp;ldquo;database&amp;rdquo; component might accept a database_size parameter.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Defined Outputs:&lt;/strong&gt; Components expose outputs that other parts of the infrastructure can consume. For example, a &amp;ldquo;database&amp;rdquo; component might output the database&amp;rsquo;s connection string.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Encapsulation:&lt;/strong&gt; The internal implementation details of the component are hidden from the consumer. The consumer interacts with a well-defined interface (inputs and outputs).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Versioned:&lt;/strong&gt; Components should be versioned (e.g., using semantic versioning) to allow for controlled updates and rollbacks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Discoverable:&lt;/strong&gt; Components should be easily discoverable by developers, often through a central catalog or registry.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="example-an-iam-roles-component"&gt;Example: An IAM roles component&lt;/h3&gt;
&lt;p&gt;IAM roles are one of the most commonly misconfigured AWS resources, making them a perfect candidate for abstraction into a reusable component. Developers frequently make mistakes when setting up roles, leading to overly permissive access, broken permissions, or operational headaches.&lt;/p&gt;
&lt;h3 id="common-iam-role-mistakes"&gt;Common IAM role mistakes&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Overly Broad Permissions&lt;/strong&gt; – Developers often grant &lt;code&gt;*&lt;/code&gt; permissions instead of following the principle of least privilege.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Missing Assume Role Policies&lt;/strong&gt; – Without a proper trust policy, services may not be able to use the role at all.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Directly Attaching Policies to Users&lt;/strong&gt; – Instead of assigning roles to groups, users might manually attach policies, leading to hard-to-audit permission structures.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lack of Naming Conventions &amp;amp; Tagging&lt;/strong&gt; – Unstructured role names and missing tags make governance difficult.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="how-a-reusable-iam-module-fixes-this"&gt;How a reusable IAM module fixes this&lt;/h4&gt;
&lt;p&gt;A platform team can provide a self-service IAM role module that enforces best practices while still allowing customization. This module would:&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;Require a Least Privilege Policy&lt;/strong&gt; – The module only allows explicitly defined permissions, preventing wildcards. &lt;br&gt;
✅ &lt;strong&gt;Auto-Generate Trust Policies&lt;/strong&gt; – Ensures correct settings, reducing misconfigurations. &lt;br&gt;
✅ &lt;strong&gt;Enforce Naming Standards &amp;amp; Tagging&lt;/strong&gt; – Ensures roles are easily identifiable and trackable.&lt;/p&gt;
&lt;p&gt;By using a reusable IAM role module, developers can self-service their permissions while the platform team ensures security, consistency, and maintainability. This removes a major source of security risk and operational overhead.&lt;/p&gt;
&lt;h2 id="no-console-interventions"&gt;No console interventions&lt;/h2&gt;
&lt;p&gt;No matter how carefully IaC is managed, emergencies may occasionally necessitate direct console changes. However, such interventions are strictly forbidden except under an official “break glass” procedure. If a quick fix is applied in the cloud console, it’s critical that the change is immediately documented and returned to code. Regular drift detection can help with this: once a day or once a week, automated scans can compare actual cloud resources to your declared infrastructure. When it detects a mismatch, you decide whether the code or the running resource is the correct version. Either way, human “hotfixes” never linger as hidden surprises for long.&lt;/p&gt;
&lt;p&gt;By combining version control, automation pipelines, environment-based configs, and drift checks, you build a solid provisioning foundation. Once you have that in place, the next step is making these patterns easy to use, so teams can start new services without reinventing the wheel.&lt;/p&gt;
&lt;p&gt;But first, let&amp;rsquo;s cover some metrics and roadblocks.&lt;/p&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;h3 id="metrics-that-matter"&gt;Metrics that matter&lt;/h3&gt;
&lt;p&gt;The classic &lt;a href="https://dora.dev/guides/dora-metrics-four-keys/"&gt;DORA metrics&lt;/a&gt; focus on software delivery. Adapting and modifying them for infrastructure provisioning can help measure how effectively your platform delivers reliable, self-service cloud resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Lead Time for Infrastructure Changes:&lt;/strong&gt; How fast does a merged PR turn into running infrastructure? Shorter indicates efficient automation and fewer manual steps.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Infrastructure Drift Frequency:&lt;/strong&gt; Are developers constantly making console tweaks, or does your infrastructure stay in sync with code? A low drift rate means your code and actual resources stay aligned.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Infrastructure Change Failure Rate:&lt;/strong&gt; What percentage of infrastructure changes result in incidents or rollbacks? A low failure rate indicates a robust, well-tested infrastructure pipeline, reducing wasted effort and potential outages.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Platform Self-Service Rate:&lt;/strong&gt; What percentage of provisioning changes are implemented by the requesting team (without needing to involve a central operations team)? A high self-service rate shows that your platform empowers developers and reduces bottlenecks.&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="common-roadblocks"&gt;Common roadblocks&lt;/h2&gt;
&lt;p&gt;Adopting Infrastructure as Code helps teams automate provisioning, but common pitfalls can slow progress before they see results. Left unaddressed, problems hidden configuration tweaks, scattered code, or reliance on a single &amp;ldquo;infrastructure hero&amp;rdquo; make it hard to keep environments consistent and secure.&lt;/p&gt;
&lt;p&gt;By spotting these roadblocks early, teams can build a stronger foundation for a repeatable, collaborative IaC workflow.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Knowledge silos:&lt;/strong&gt; Relying on just one person (&amp;ldquo;Go ask Sarah&amp;rdquo;) for all infrastructure tasks creates a risky bottleneck. The broader team can&amp;rsquo;t replicate or improve the process, slowing new initiatives and leaving you vulnerable if that individual is out of office or leaves the company. Moving to a collaborative, version-controlled IaC model ensures that no single engineer is a gatekeeper for new environments or configuration changes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Overly complex or unstructured IaC:&lt;/strong&gt; Bloated or disorganized infrastructure code leads to confusion, duplication, and frequent errors. Engineers might copy-paste configurations from previous projects or cram all resources into a single file. By refactoring IaC into smaller modules, adopting a clear folder structure, and removing dead code, teams gain consistency and accelerate ramp-up times for new services.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Learning curves:&lt;/strong&gt; Teams new to IaC often encounter a steep learning curve. There is initially a lot to learn, and resistance from people used to quickly working through cloud UI can slow down full adoption. But time invested in learning tools and setting cultural norms pay off in agility and a platform strategy with guidance and golden paths can help reduce the burden.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cultural inertia:&lt;/strong&gt; The adoption of a platform engineering approach is a shift in how organizations manage infrastructure, and like all, change is not merely technical but also organizational. Resistance to change and organizational inertia can make it difficult to implement new ways of getting work done. Expecting, understanding, and working through this resistance is key to overcoming it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Lack of comprehensive approach:&lt;/strong&gt; IaC tools are great for provisioning, but without a full platform approach, covering monitoring, logging, and the full life cycle of cloud resources, provisioning efforts may fall short. We will see in this series how the pillars of a comprehensive approach strengthen themselves.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These roadblocks highlight why a thoughtful, well-structured approach to provisioning is essential. But that’s not enough because the next step is to make these consistent patterns easily accessible so teams can spin up entire services without starting from scratch. And that’s where “golden path” templates come in.&lt;/p&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;h3 id="platform-team-provisioning-responsibilities"&gt;Platform team provisioning responsibilities&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Setting up the initial IaC foundation&lt;/li&gt;
&lt;li&gt;Creating and maintaining templates&lt;/li&gt;
&lt;li&gt;Automating workflows&lt;/li&gt;
&lt;li&gt;Monitoring usage and compliance&lt;/li&gt;
&lt;li&gt;Internal Platform Evangelism&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="service-templates"&gt;Service templates&lt;/h2&gt;
&lt;p&gt;&lt;img src="abstraction-levels.png" alt="abstraction-levels"&gt;&lt;/p&gt;
&lt;p&gt;Even with a solid IaC foundation and reusable modules, spinning up a brand-new service can still require developers to understand how to pull in the underlying infrastructure components and how to use them. That’s where templates come in.&lt;/p&gt;
&lt;p&gt;These are pre-vetted, ready-to-use blueprints that capture your organization’s best practices for a complete service or application, including infrastructure, application, and security components. They are the key to enabling true self-service.&lt;/p&gt;
&lt;p&gt;Think of service templates as the next step up from the reusable components we discussed earlier. While a component might represent a single &amp;ldquo;building block&amp;rdquo; (like a secure S3 bucket or a database), a service template represents an entire structure built from those blocks (like a complete web application with a database, load balancer, and containerized application). They combine those building blocks with sensible default configurations and best practices for things like logging, Kubernetes setup, and security baked in.&lt;/p&gt;
&lt;p&gt;Beyond bundling your organization’s best practices into a single repository, these service templates can also include a preconfigured CI/CD pipeline.&lt;/p&gt;
&lt;p&gt;This means that when a team spins up a new service from the template, they inherit not just the infrastructure modules and best-practice configurations, but a fully operational CI/CD workflow. No extra setup is required. Developers get immediate feedback on their changes, and the merge to main triggers automatic deployments that keep every environment consistent.&lt;/p&gt;
&lt;p&gt;Instead of cobbling together snippets from old repos, teams can scaffold out a service by simply selecting the template that fits their need – whether it’s a containerized web app, a serverless function, or a data-processing pipeline – and providing a few key parameters.&lt;/p&gt;
&lt;p&gt;&lt;img src="service-template.png" alt="service template"&gt;&lt;/p&gt;
&lt;p&gt;These curated paths cut onboarding time, reduce configuration missteps, and ensure that the IaC modules are used in new services. If developers don’t have to remember which tags to apply or how to route logs, or even where to import infrastructure modules from, they can focus on delivering features rather than wrestling with infrastructure details. And as your platform matures, and new versions of your modules are introduced, you can update these templates, automatically sharing improvements with every new project.&lt;/p&gt;
&lt;h2 id="bringing-it-all-together-the-impact-of-effective-provisioning"&gt;Bringing it all together: The impact of effective provisioning&lt;/h2&gt;
&lt;p&gt;With reusable modules and service templates in place, and a deployment workflow that takes changes from git commits all the way to your cloud account, your platform engineering team creates a foundation that transforms how developers interact with infrastructure. This transformation yields measurable benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Lower Infrastructure Change Failure Rate:&lt;/strong&gt; Automated validations and peer reviews catch issues early, reducing the number of failed deployments.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Increased Platform Self-Service Efficiency:&lt;/strong&gt; Developers can provision resources in minutes instead of days, boosting productivity and reducing bottlenecks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reduced Lead Time for Infrastructure Changes:&lt;/strong&gt; Streamlined automation shortens the time from code commit to deployed infrastructure.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Minimized Infrastructure Drift:&lt;/strong&gt; Fewer manual console interventions mean your actual infrastructure stays aligned with what’s declared in code.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(In the upcoming article, we’ll dig deeper into how governance guards these golden paths, ensuring that new projects comply with security and corporate policies right from day one.)&lt;/p&gt;
&lt;h2 id="managing-infrastructure-provisioning-with-pulumi"&gt;Managing infrastructure provisioning with Pulumi&lt;/h2&gt;
&lt;p&gt;As you build out your platform engineering plan, consider how Pulumi might fit in. With Pulumi, you get:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/what-is/what-is-infrastructure-as-code/"&gt;Infrastructure as Code&lt;/a&gt; in the language of your choice.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/iac/concepts/resources/components/"&gt;Component resources&lt;/a&gt; for creating reusable infrastructure modules that encapsulate best practices.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/pulumi-cloud/deployments/"&gt;Pulumi Deployments&lt;/a&gt; for a fully managed infrastructure deployment solution.&lt;/li&gt;
&lt;li&gt;And a &lt;a href="https://www.pulumi.com/docs/pulumi-cloud/deployments/"&gt;Platform in the box&lt;/a&gt; experience.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Reliable infrastructure provisioning is the cornerstone that supports your entire platform engineering initiative. By implementing the practices outlined in this article – version-controlled IaC, automated CI/CD workflows, environment separation, and golden-path templates – you establish a foundation that enables consistency, speed, and trust.&lt;/p&gt;
&lt;p&gt;But provisioning alone isn’t enough. In upcoming articles, we’ll explore how policy-as-code and broader governance tie into this foundation, ensuring that all those newly created services meet security and compliance standards from the moment they’re deployed. But first, we’ll look at expanding the provisioning foundation covered here into a true self-service platform, so teams can quickly spin up services without risking the organizational chaos that often accompanies speed.&lt;/p&gt;
&lt;p&gt;Provisioning is the foundation – &lt;a href="https://www.pulumi.com/blog/series/platform-engineering-pillars/"&gt;next in this series&lt;/a&gt; we’ll build on it.&lt;/p&gt;</description><author>Adam Gordon Bell</author><category>platform-engineering</category></item><item><title>Platform Pillars: Build Platforms, Not Infrastructure</title><link>https://www.pulumi.com/blog/platform-engineering-pillars-1/</link><pubDate>Wed, 26 Feb 2025 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/platform-engineering-pillars-1/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/platform-engineering-pillars-1/index.png" /&gt;
&lt;p&gt;Software drives innovation. Development teams face pressure to ship features faster. But speed collides with infrastructure complexity. Developers struggle with cloud setups, juggle scattered tools, and wait on operations teams for resources. The result is friction and slower innovation.&lt;/p&gt;
&lt;p&gt;This is where &lt;a href="https://www.pulumi.com/what-is/what-is-platform-engineering/"&gt;Platform Engineering&lt;/a&gt; comes in. It helps developers move faster by creating tools that actually work. A good internal platform lets teams self-serve infrastructure, find documentation, follow best practices, and focus on what they do best: writing useful software.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/AUCt28gVR6Y?rel=0?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Building a platform isn&amp;rsquo;t about finding one perfect tool. It&amp;rsquo;s about assembling the right pieces, or pillars. These pillars define what every successful internal developer platform needs.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.pulumi.com/blog/series/platform-engineering-pillars/"&gt;This series&lt;/a&gt; explores these key pillars of Platform Engineering, offering a practical guide to building platforms that remove barriers to developer speed. Each pillar addresses a specific challenge organizations face when scaling developer productivity. The first challenge is overcoming infrastructure chaos.&lt;/p&gt;
&lt;img align="right" style="margin: 15px;" width="300px" src="provisioning.png"&gt;
&lt;h3 id="problem-infrastructure-chaos-and-bottlenecks"&gt;&lt;strong&gt;Problem: Infrastructure chaos and bottlenecks&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Developers face many pain points with manual provisioning, which is why &lt;a href="https://www.pulumi.com/what-is/what-is-infrastructure-as-code/"&gt;Infrastructure as Code&lt;/a&gt; has become so essential:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Inconsistent environments&lt;/strong&gt;: Dev, staging, and production drift apart. &amp;ldquo;Works on my machine&amp;rdquo; becomes everyone&amp;rsquo;s excuse.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Slow processes&lt;/strong&gt;: Console clicking and waiting on ops creates bottlenecks. Developers wait days for basic infrastructure.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configuration drift&lt;/strong&gt;: Manual configs lead to errors. Quick production fixes become undocumented mysteries that cause problems later.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lack of repeatability&lt;/strong&gt;: Spinning up new environments or replicating existing ones is a complex, time-consuming, and often unreliable process.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&amp;rsquo;s why provisioning is the first pillar to master if you want to build a successful developer platform.&lt;/p&gt;
&lt;img align="left" style="margin: 15px; margin-right:25px;" width="300px" src="self-service.png"&gt;
&lt;h3 id="problem-dependency-bottlenecks-and-limited-developer-autonomy"&gt;&lt;strong&gt;Problem: Dependency bottlenecks and limited developer autonomy&lt;/strong&gt;&lt;/h3&gt;
&lt;span style="margin-left:-15px"&gt;
Even with automated provisioning, another challenge emerges - developers still struggle when tools are provided without considering usability.
&lt;/span&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hard to find what you need&lt;/strong&gt;: Developers waste time hunting for the right infrastructure modules. They end up searching docs, bothering colleagues, or rebuilding something that already exists.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Too much complexity&lt;/strong&gt;: Most developers aren&amp;rsquo;t infrastructure experts. Learning all the options and parameters creates hesitation and mistakes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lack of real autonomy&lt;/strong&gt;: Developers have the tools but not the confidence to use them. They still need approvals or fear breaking production.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inconsistent workflows&lt;/strong&gt;: Teams create their own ways of using infrastructure, making collaboration harder.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By implementing self-service infrastructure provisioning, you transform your platform from a centrally controlled system to a developer-centric environment. You unlock developer autonomy, accelerate development cycles, and free up operations teams to focus on higher-value activities.&lt;/p&gt;
&lt;img align="right" style="margin: 15px;" width="300px" src="developer-workflow.png"&gt;
&lt;h3 id="problem-inefficient-and-fragmented-development-processes"&gt;&lt;strong&gt;Problem: Inefficient and fragmented development processes&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Infrastructure is just one piece of the puzzle. Developers still face daily friction moving code to production.&lt;/p&gt;
&lt;p&gt;A typical developer&amp;rsquo;s day is full of frustrations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Inconsistent local environments&lt;/strong&gt;: Variations in local setups, dependency conflicts, and operating system differences lead to debugging headaches and wasted time getting started.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Slow CI/CD pipelines&lt;/strong&gt;: Long builds, flaky tests, and mysterious pipeline configs block progress. Developers stare at progress bars instead of writing code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cumbersome testing and staging setups&lt;/strong&gt;: Provisioning environments for testing and staging can be a manual process. Environments may not accurately mirror production, leading to surprises and integration issues late in the development cycle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lack of discoverability and reusability&lt;/strong&gt;: Nobody knows what code already exists, so everyone rebuilds it. Duplication becomes the norm.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Developer toil and context switching&lt;/strong&gt;: Developers spend more time on environment setup and troubleshooting than on writing features.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These inefficiencies add up, creating significant friction in the developer workflow. Developers spend more time wrestling with processes and less time focused on building and innovating. A Platform approach can help solve these challenges.&lt;/p&gt;
&lt;img align="left" style="margin: 15px;" width="300px" src="security.png"&gt;
&lt;h3 id="problem-security-as-a-bottleneck-and-source-of-friction"&gt;&lt;strong&gt;Problem: Security as a bottleneck and source of friction&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Speed and efficiency matter, but security can&amp;rsquo;t be compromised. Traditionally, security acts as a gate – a necessary hurdle before releasing software. Platform Engineering aims to transform security from a bottleneck into an enabler.&lt;/p&gt;
&lt;p&gt;For many developers, security feels like a roadblock, leading to common friction points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Last-minute security&lt;/strong&gt;: Checks happen at the end of development, causing costly rework when issues are found.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Restrictive policies&lt;/strong&gt;: Security rules feel arbitrary and unclear, slowing down deployments.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The &amp;ldquo;No&amp;rdquo; department&lt;/strong&gt;: Security teams are seen as blockers who enforce policies without offering solutions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Speed vs. security mindset&lt;/strong&gt;: Everyone believes you must choose between going fast and being secure.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security is &amp;ldquo;Someone else&amp;rsquo;s job&amp;rdquo;&lt;/strong&gt;: Developers think security is only the security team&amp;rsquo;s responsibility.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This &amp;ldquo;security as a gate&amp;rdquo; approach not only slows things down but leads to workarounds as developers try to bypass security to meet deadlines.&lt;/p&gt;
&lt;p&gt;By integrating security into your platform, you transform security from a gatekeeper into a built-in feature of the development process. Security becomes an enabler of trust and speed, letting developers innovate with confidence.&lt;/p&gt;
&lt;img align="right" style="margin: 15px;" width="300px" src="observability.png"&gt;
&lt;h3 id="problem-data-overload-without-insights"&gt;&lt;strong&gt;Problem: Data overload without insights&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Developers in modern cloud environments often find themselves in a paradox: they are drowning in data, yet starving for actionable information. Monitoring systems pump out metrics, logs, and traces, but finding what matters feels impossible. An ideal internal developer platform shifts the focus from data collection to actionable insights.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what developers typically experience:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tool sprawl&lt;/strong&gt;: Teams use different tools for metrics, logs, and traces. Developers waste time jumping between dashboards and manually connecting the dots.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alert noise&lt;/strong&gt;: Developers get bombarded with alerts lacking context or clear next steps. Important signals get buried in the noise.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dashboard overload&lt;/strong&gt;: Existing dashboards overwhelm developers with granular metrics that are hard to connect to application behavior.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reactive debugging&lt;/strong&gt;: Troubleshooting becomes a time-consuming fire drill. Developers spend hours sifting through logs to pinpoint issues.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Missing proactive insights&lt;/strong&gt;: Observability is only used for firefighting. Developers lack insights to optimize performance or prevent issues before they affect users.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Disconnected from production&lt;/strong&gt;: Many developers feel blind to how their code actually runs in production, limiting their ability to improve it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This data flood without clear insights leaves developers reactive rather than proactive, hampering their ability to build reliable applications.&lt;/p&gt;
&lt;p&gt;By improving observability in your platform, you transform it from an operations function into a developer superpower. Developers gain the insights they need to build better applications with greater confidence, using AI to focus on what truly matters.&lt;/p&gt;
&lt;img align="left" style="margin: 15px;" width="300px" src="goverance.png"&gt;
&lt;h3 id="problem-balancing-autonomy-with-control-and-cost-efficiency"&gt;&lt;strong&gt;Problem: Balancing autonomy with control and cost efficiency&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Empowering developers with self-service is essential, but unchecked autonomy leads to chaos. As platform adoption grows, organizations need to maintain control without creating bottlenecks. This is where Governance as Code becomes critical, establishing guardrails while preserving speed.&lt;/p&gt;
&lt;p&gt;Self-service creates new challenges:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Configuration drift&lt;/strong&gt;: In decentralized environments, infrastructure setups diverge from standards, creating security risks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloud cost explosion&lt;/strong&gt;: Without proper controls, self-service can lead to wasted resources and budget overruns. Teams over-provision or forget to clean up unused environments.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compliance headaches&lt;/strong&gt;: Meeting regulatory requirements becomes harder when infrastructure is created in an uncontrolled way.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audit gaps&lt;/strong&gt;: Without clear governance, tracking who created what and when becomes nearly impossible.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Manual governance slows everything&lt;/strong&gt;: If governance relies on approvals and tickets, it defeats the purpose of self-service.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These challenges highlight the need for automated governance that scales with your platform, ensuring control without slowing developers down.&lt;/p&gt;
&lt;p&gt;By implementing Governance as Code, you ensure that self-service doesn&amp;rsquo;t conflict with security, compliance, and cost efficiency. Governance becomes an automated part of your platform, enabling responsible scaling.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;These six challenges represent the core obstacles standing between your organization and true developer velocity. When properly addressed, each transforms into a capability that empowers developers and accelerates innovation.&lt;/p&gt;
&lt;p&gt;The upcoming articles in this series will explore each area in depth, with practical strategies and real-world examples to help you build an effective internal developer platform.&lt;/p&gt;
&lt;p&gt;Ready to implement these principles in your organization? Check out &amp;ldquo;Platform Engineering: Recommended Practices for Infrastructure as Code&amp;rdquo;. It provides detailed guidance on building scalable internal developer platforms.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How to structure infrastructure code for scalability&lt;/li&gt;
&lt;li&gt;Managing development environments with developer stacks and Git branches&lt;/li&gt;
&lt;li&gt;Designing modular and reusable Pulumi projects&lt;/li&gt;
&lt;li&gt;Implementing cross-stack dependencies with stack references&lt;/li&gt;
&lt;li&gt;Setting up RBAC, security, and governance at scale&lt;/li&gt;
&lt;li&gt;Automating infrastructure management with stack orchestration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://info.pulumi.com/ebook/platform-engineering-iac-recommended-practices"&gt;&lt;strong&gt;Get the Free Platform Engineering Ebook →&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And don&amp;rsquo;t miss the next article in this series, where we&amp;rsquo;ll explore provisioning as the foundation of your developer platform journey.&lt;/p&gt;</description><author>Adam Gordon Bell</author><category>platform-engineering</category></item><item><title>Host your Python app for $1.12 a month</title><link>https://www.pulumi.com/blog/serverless-api/</link><pubDate>Wed, 29 Jan 2025 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/serverless-api/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/serverless-api/index.png" /&gt;
&lt;p&gt;&lt;strong&gt;TL;DR (2026 pricing):&lt;/strong&gt; Packaging a Flask app as a container and running it on AWS Lambda behind an HTTP API Gateway costs &lt;strong&gt;~$1.12/month&lt;/strong&gt; worst-case, or &lt;strong&gt;$0 when idle&lt;/strong&gt;. The breakdown: $0.04 for API Gateway requests, $0.07 for Lambda compute (fully absorbed by the always-free tier), and $1.08 for 12 GB of egress at $0.09/GB (covered by AWS&amp;rsquo;s 100 GB/month free egress allowance unless other services in the account have already used it). Pulumi handles the entire infrastructure. Verified against AWS pricing as of April 2026.&lt;/p&gt;
&lt;p&gt;How cheap can you host a Python app in 2026? For a low-traffic Flask API (say, 40,000 requests per month at 512 MB of memory), the answer is roughly &lt;strong&gt;$1.12/month on AWS&lt;/strong&gt; worst-case, dropping to &lt;strong&gt;$0 when idle&lt;/strong&gt;. The trick is to stop thinking of &lt;a href="https://www.pulumi.com/registry/packages/aws/api-docs/lambda/function/"&gt;AWS Lambda&lt;/a&gt; as &amp;ldquo;one function per endpoint&amp;rdquo; and instead package your entire web framework as a container, deploy it to Lambda, and put it behind an HTTP API Gateway. Your code stays standard Flask. Your bill stays in the loose-change zone.&lt;/p&gt;
&lt;p&gt;This post walks through the whole setup with Pulumi, then compares the resulting cost against &lt;a href="https://www.pulumi.com/registry/packages/gcp/api-docs/cloudrun/service/"&gt;Google Cloud Run&lt;/a&gt;, Fly.io, Railway, and Vercel using current 2026 prices.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/cjLpOJn0B6g?rel=0?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Most developers maintain at least one low-traffic service that still needs to be reliably available: an internal reporting API, a webhook receiver, a side project with occasional use. Traditional hosting means paying for 24/7 server time, even when the service sits idle. AWS Lambda&amp;rsquo;s container support flips that: pay only when your API is being called, and any web framework that handles HTTP requests (Flask, FastAPI, Express) works without modification.&lt;/p&gt;
&lt;h3 id="how-does-the-pricing-break-down-2026"&gt;How does the pricing break down? (2026)&lt;/h3&gt;
&lt;p&gt;Plugging 40,000 requests per month at 512 MB memory and ~200 ms average execution into 2026 AWS pricing:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Lambda compute&lt;/strong&gt;: 40,000 × 0.5 GB × 0.2 s = 4,000 GB-seconds × $0.0000166667 = &lt;strong&gt;$0.067&lt;/strong&gt;, fully covered by the always-free tier (400,000 GB-seconds free per month).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lambda requests&lt;/strong&gt;: 40,000 × $0.20 / 1,000,000 = &lt;strong&gt;$0.008&lt;/strong&gt;, also covered by the free tier (1M requests free per month).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTP API Gateway&lt;/strong&gt;: 40,000 × $1.00 / 1,000,000 = &lt;strong&gt;$0.04&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data transfer out&lt;/strong&gt;: 12 GB × $0.09/GB = &lt;strong&gt;$1.08&lt;/strong&gt; if billed, but AWS&amp;rsquo;s always-free tier (expanded to 100 GB/month outbound in late 2021) covers this row outright unless other services in the same account have already exhausted that allowance.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Total&lt;/strong&gt;: &lt;strong&gt;~$0.04/month&lt;/strong&gt; (egress in the free tier, only API Gateway bills) to &lt;strong&gt;~$1.12/month&lt;/strong&gt; (12 GB egress billed in full: $1.08 + $0.04 API Gateway), with Lambda compute and requests always fully covered by the free tier.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The underlying math hasn&amp;rsquo;t changed since this post was first published in early 2025; Lambda and API Gateway pricing has been remarkably stable, and AWS has only expanded free-tier allowances since.&lt;/p&gt;
&lt;h3 id="cheap-python-hosting-compared-2026"&gt;Cheap Python hosting compared (2026)&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left"&gt;&lt;strong&gt;Provider&lt;/strong&gt;&lt;/th&gt;
&lt;th style="text-align: left"&gt;&lt;strong&gt;Approx. cost / month&lt;/strong&gt;&lt;/th&gt;
&lt;th style="text-align: left"&gt;&lt;strong&gt;2026 notes&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;AWS Lambda + HTTP API&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;strong&gt;~$1.12&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;~$1.08 for 12 GB egress at $0.09/GB + $0.04 for 40k API Gateway requests at $1/M; Lambda compute ($0.067) is fully covered by the always-free tier (1M req/400k GB-s).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Google Cloud Run&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;strong&gt;~$1.44&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;2M requests, 360,000 GiB-seconds of memory, and 180,000 vCPU-seconds are free every month — compute is $0. Egress dominates: Premium tier is &lt;del&gt;$0.12/GB, so 12 GB ≈ $1.44. Standard tier (&lt;/del&gt;$0.085/GB) lands near $1.02.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Fly.io&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;strong&gt;~$2.18&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;Cheapest persistent &lt;code&gt;shared-cpu-1x@256MB&lt;/code&gt; machine is ~$1.94/mo; 12 GB outbound at $0.02/GB (NA/EU) adds ~$0.24 (free tier was retired in late 2024).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Railway&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;strong&gt;~$6.00&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;Hobby plan starts at $5/mo and includes $5 of usage; a 512 MB always-on container plus 12 GB egress lands around $5.50–6.50 total.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Vercel (Pro)&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;strong&gt;$20+&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;Pro plan has a $20/mo floor; 40k function invocations are well under the included quotas, so you mostly pay for the seat.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div style="text-align: center; width: 100%; margin: 20px auto;"&gt;
&lt;figcaption&gt;
&lt;i&gt;Approximate 2026 hosting costs for a 512 MB Python API at 40,000 requests/month with 12 GB egress. Egress dominates at this scale; even 1 million requests/month stays under $30 on Lambda.&lt;/i&gt;
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;p&gt;What we&amp;rsquo;ll cover:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/serverless-api/#why-use-aws-lambda-for-low-traffic-python-apps"&gt;Why use AWS Lambda for low-traffic Python apps?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/serverless-api/#how-do-i-build-a-flask-app-for-lambda"&gt;How do I build a Flask app for Lambda?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/serverless-api/#how-do-i-containerize-a-flask-app-for-lambda"&gt;How do I containerize a Flask app for Lambda?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/serverless-api/#do-i-need-to-learn-lambda-specific-patterns"&gt;Do I need to learn Lambda-specific patterns?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/serverless-api/#how-do-i-deploy-a-lambda-container-with-pulumi"&gt;How do I deploy a Lambda container with Pulumi?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/serverless-api/#why-is-this-approach-worth-it"&gt;Why is this approach worth it?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/serverless-api/#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="why-use-aws-lambda-for-low-traffic-python-apps"&gt;Why use AWS Lambda for low-traffic Python apps?&lt;/h2&gt;
&lt;p&gt;&amp;ldquo;But Lambda means one function per REST endpoint, right?&amp;rdquo; While that&amp;rsquo;s how serverless was initially positioned, the Lambda monolith approach here is a more straightforward pattern. Take any REST API service, package it as a container, put it in Lambda, and give it an entire HTTP route behind an IP, domain name, or API Gateway prefix. Done.&lt;/p&gt;
&lt;p&gt;This approach gives you several benefits:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Minimal Serverless Knowledge Required&lt;/strong&gt;: You don&amp;rsquo;t need to be an expert in Lambda architecture or serverless patterns. If you can build a REST API and containerize it, you&amp;rsquo;re ready to go.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Standard Development Experience&lt;/strong&gt;: Write your API exactly as you would for any other hosting platform: no special frameworks, no Lambda-specific patterns to learn.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simple Local Development&lt;/strong&gt;: Run your container locally, and it will behave exactly like it will in production. No complex serverless emulation is needed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Zero Cost at Zero Usage&lt;/strong&gt;: Get all the cost benefits of serverless without restructuring your application.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The trade-off? Cold starts when Lambda spins up a new container instance. But this is often a worthwhile exchange for operational simplicity and zero-cost idle periods for low-traffic services.&lt;/p&gt;
&lt;p&gt;Let me show you how to set this up. With some Pulumi Python code for setting up the Infra and some containerization code, you get an easy hosting solution.&lt;/p&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;h3 id="why-containers"&gt;Why containers?&lt;/h3&gt;
&lt;p&gt;Containers let us package everything—code, system dependencies, runtime—into one unit. Any HTTP service that fits in a container will work: Flask, Express, Rails, plus whatever system tools you need. Local testing? Just start the container. No Lambda-specific configuration is required.&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="how-do-i-build-a-flask-app-for-lambda"&gt;How do I build a Flask app for Lambda?&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s start with the fun part - writing some Python code, but I will keep it really simple. We&amp;rsquo;ll build a simple Flask application that demonstrates why running a full web framework in Lambda makes sense.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s our starting point in &lt;code&gt;scripts/simple_server.py&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;mangum&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Mangum&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Create Flask app&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/hello&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Hello from Flask!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/echo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;POST&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;echo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;reversed&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;message&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;][::&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Create Lambda handler&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;lambda_handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Mangum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;0.0.0.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Notice a few key things about this code:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;It&amp;rsquo;s Just Flask&lt;/strong&gt;: No Lambda-specific code, no special frameworks. If you know Flask, you know how to modify this.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multiple Endpoints&lt;/strong&gt;: We have several routes demonstrating different HTTP methods and URL patterns.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="how-do-i-containerize-a-flask-app-for-lambda"&gt;How do I containerize a Flask app for Lambda?&lt;/h2&gt;
&lt;p&gt;To containerize our Flask app for Lambda, we&amp;rsquo;ll start with a Dockerfile that meets AWS&amp;rsquo;s requirements. The key is using the right base image and adapting our application to Lambda&amp;rsquo;s runtime interface.&lt;/p&gt;
&lt;p&gt;First, let&amp;rsquo;s create our Dockerfile:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-dockerfile" data-lang="dockerfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;public.ecr.aws/lambda/python:3.12&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Set up working directory&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/app&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; scripts/simple_server.py scripts/&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; scripts/simple_entrypoint.sh scripts/&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Install dependencies&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; pip install --no-cache-dir &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; flask&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;3.0.0 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mangum&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;0.17.0&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Make entrypoint script executable&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; chmod +x scripts/simple_entrypoint.sh&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Set environment variables&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;FLASK_APP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;scripts/simple_server.py&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;PYTHONPATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/app&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;RUN_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;lambda
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;3000&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Use our entrypoint script&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/app/scripts/simple_entrypoint.sh&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Notice that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We&amp;rsquo;re using AWS&amp;rsquo;s official Lambda Python base image. You can use your own base image and your Python package manager of choice, but starting with this base and &lt;code&gt;pip install&lt;/code&gt; is the easiest.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;${LAMBDA_TASK_ROOT}&lt;/code&gt; is a special directory where Lambda expects to find your code&lt;/li&gt;
&lt;li&gt;We&amp;rsquo;re introducing a new file: &lt;code&gt;simple_entrypoint.sh&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="do-i-need-to-learn-lambda-specific-patterns"&gt;Do I need to learn Lambda-specific patterns?&lt;/h2&gt;
&lt;p&gt;The beauty of everything covered so far is that it&amp;rsquo;s just a container. You can work with it locally with Docker run, put it in Kubernetes, or run it in one of the &lt;a href="https://www.pulumi.com/blog/cursed-container-iceberg/"&gt;1000 places you can run containers&lt;/a&gt;. Lambda has some odd requirements about function calling that get in the way of this, but &lt;code&gt;simple_entrypoint.sh&lt;/code&gt; saves the day:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$RUN_MODE&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;local&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Starting in local mode...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;exec&lt;/span&gt; python scripts/simple_server.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Starting in Lambda mode...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;exec&lt;/span&gt; python -m awslambdaric scripts.simple_server.lambda_handler
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;All this means is that locally, you don&amp;rsquo;t need to know about lambdas at all. If you supply &lt;code&gt;RUN_MODE=local&lt;/code&gt; it runs the standard way you are used to:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Build the container&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker build -t simple-flask -f infra/Dockerfile .
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run -d -p 3000:3000 -e &lt;span class="nv"&gt;RUN_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt; simple-flask
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Test the endpoints:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 1. Test GET /hello&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl http://localhost:3000/hello
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 2. Test POST /echo with a message&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -X POST &lt;span class="s2"&gt;&amp;#34;http://localhost:3000/echo&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s2"&gt;&amp;#34;Content-Type: application/json&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -d &lt;span class="s1"&gt;&amp;#39;{&amp;#34;message&amp;#34;: &amp;#34;hello world&amp;#34;}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This local testing setup is crucial because it helps catch issues before deployment.&lt;/p&gt;
&lt;p&gt;The beauty of this setup is that once it works locally, it&amp;rsquo;ll work the same way in AWS.&lt;/p&gt;
&lt;p&gt;And since this is just a standard Python application in a container, you can use all your favorite Python tools and patterns – your preferred package manager (pip, poetry, uv), pytest for testing, standard logging, type hints, linting, and pre-commit hooks.&lt;/p&gt;
&lt;p&gt;( &lt;em&gt;I&amp;rsquo;m skipping those details here to focus on deployment, but the container approach means there are no serverless-specific limitations on your Python workflow.&lt;/em&gt; )&lt;/p&gt;
&lt;h2 id="how-do-i-deploy-a-lambda-container-with-pulumi"&gt;How do I deploy a Lambda container with Pulumi?&lt;/h2&gt;
&lt;p&gt;Now let&amp;rsquo;s get our containerized Flask app into AWS. While you could click around in the AWS console, we&amp;rsquo;re going to do this properly with infrastructure as code. You are welcome to use &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html"&gt;Cloudformation&lt;/a&gt; or manual setup, but it&amp;rsquo;s nice to be able to keep everything in Python, and this is the Pulumi blog. Using Python to define our AWS resources means we get type-checking and proper IDE support.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s our infrastructure code:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Don&amp;rsquo;t let the infrastructure code below intimidate you. It&amp;rsquo;s just typed Python code, hooking up the Lambda, that you can version control and modify easily.&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pulumi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pulumi_aws&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;aws&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pulumi_awsx&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;awsx&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# First, create an API Gateway&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;api_gateway&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apigatewayv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apigatewayv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;flask-api&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Flask Lambda API&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;protocol_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;HTTP&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;route_selection_expression&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;$request.method $request.path&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Create IAM role for Lambda&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;lambda_role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Role&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;flask-lambda-role&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;assume_role_policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2012-10-17&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Statement&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Action&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;sts:AssumeRole&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Principal&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Service&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;lambda.amazonaws.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Effect&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Allow&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Sid&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Attach basic execution policy - Lambda needs this to write logs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;lambda_role_policy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RolePolicyAttachment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RolePolicyAttachment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;lambda-role-policy&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;lambda_role&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;policy_arn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Create ECR repository for our container&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ecr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Repository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ecr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Repository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;flask-app-repo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;flask-app-repo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;force_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# Makes cleanup easier for testing&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;image_scanning_configuration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ecr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RepositoryImageScanningConfigurationArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;scan_on_push&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Build and push the Docker image to ECR&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;awsx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ecr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;awsx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ecr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;flask-app-image&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;repository_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repository_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# Path to your Dockerfile&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;linux/amd64&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;# Important for M1/M2 Mac users&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Create the Lambda function&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;lambda_function&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lambda_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lambda_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;flask-app&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;flask-app&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;package_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Image&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;image_uri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image_uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;lambda_role&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;memory_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;variables&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;FLASK_ENV&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;production&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;PYTHONUNBUFFERED&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Create API Gateway stage&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apigatewayv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apigatewayv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;api-stage&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;api_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;api_gateway&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;$default&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;auto_deploy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Connect API Gateway to Lambda&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;integration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apigatewayv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Integration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apigatewayv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Integration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;lambda-integration&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;api_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;api_gateway&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;integration_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;AWS_PROXY&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;integration_uri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;lambda_function&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;integration_method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;POST&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;payload_format_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;2.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Create catch-all route&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apigatewayv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apigatewayv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;catch-all-route&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;api_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;api_gateway&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;route_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ANY /{proxy+}&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;integration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;integrations/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Allow API Gateway to invoke Lambda&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;lambda_permission&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lambda_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Permission&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lambda_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Permission&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;api-lambda-permission&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;lambda:InvokeFunction&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;lambda_function&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;principal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;apigateway.amazonaws.com&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;source_arn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;api_gateway&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execution_arn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;arn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;arn&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/*/*&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Export the API Gateway URL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;url&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_gateway&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api_endpoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s break down what&amp;rsquo;s happening here:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;API Gateway&lt;/strong&gt;: We create an HTTP API (v2) that will receive all web requests and forward them to our Lambda.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IAM Role&lt;/strong&gt;: Lambda needs permissions to execute and write logs. We create a role with the minimum necessary permissions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Container Registry&lt;/strong&gt;: We create an ECR repository and use Pulumi&amp;rsquo;s &lt;code&gt;awsx&lt;/code&gt; package to build and push our Docker image automatically.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lambda Function&lt;/strong&gt;: We create the Lambda function using our container image. Notice how we set memory and timeout - these are important for a web application.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integration&lt;/strong&gt;: We connect API Gateway to Lambda using the AWS_PROXY integration type. &lt;code&gt;ANY /{proxy+}&lt;/code&gt; means we route everything through with the full path.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Need a custom domain, that&amp;rsquo;s easy to use as well. Setup some Route53 records, CNAME a domain name appropriately and you&amp;rsquo;ll have a container backed by a custom domain name.&lt;/p&gt;
&lt;h2 id="why-is-this-approach-worth-it"&gt;Why is this approach worth it?&lt;/h2&gt;
&lt;p&gt;The cool thing about using Pulumi and lambdas is that they make complex infrastructure changes safe and repeatable.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Want to increase the Lambda memory? Change one number and run &lt;code&gt;pulumi up&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;You get &lt;a href="https://www.pulumi.com/registry/packages/aws/"&gt;complete AWS Lambda and API Gateway management&lt;/a&gt; for your low-traffic REST APIs&lt;/li&gt;
&lt;li&gt;And &lt;a href="https://www.pulumi.com/docs/iac/concepts/testing/"&gt;Infrastructure testing capabilities&lt;/a&gt; mean you can validate your serverless configuration before deployment&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can find the full code in the &lt;a href="https://github.com/adamgordonbell/service-status-monitor"&gt;service status monitor repo&lt;/a&gt; from my &lt;a href="https://www.pulumi.com/blog/python-for-devops/"&gt;Python devops article&lt;/a&gt;. A larger version with rolling updates is coming soon.&lt;/p&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;h3 id="alternative-approaches"&gt;Alternative approaches&lt;/h3&gt;
&lt;p&gt;There are other ways to run containers that scale to zero. &lt;a href="https://www.pulumi.com/registry/packages/gcp/api-docs/cloudrun/service/"&gt;Google Cloud Run&lt;/a&gt; offers similar functionality on GCP, and &lt;a href="https://www.pulumi.com/blog/deploy-applications-with-aws-app-runner/"&gt;AWS App Runner&lt;/a&gt; is another AWS service that can do this. Both have similar pricing models—very cheap for low-volume services. And &lt;a href="https://www.pulumi.com/blog/from-cdk-pulumi-evolution-of-sst/"&gt;SST&lt;/a&gt; is a similar solution for getting TypeScript / JavaScript solutions into an AWS Lambda.&lt;/p&gt;
&lt;p&gt;The beauty of the container approach used here is that switching between these services is straightforward. Since we&amp;rsquo;re just running a standard container with a REST endpoint, there&amp;rsquo;s nothing AWS-specific in our application code. If you need to move to a different platform later, you can take your container with you.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Running Flask applications in Lambda containers represents a sweet spot between development simplicity and operational efficiency. You get:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;True zero-cost periods&lt;/strong&gt; when your application isn&amp;rsquo;t being used&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Familiar development patterns&lt;/strong&gt; - it&amp;rsquo;s just Flask&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Infrastructure as code&lt;/strong&gt; with Pulumi&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automatic scaling&lt;/strong&gt; without managing servers&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The trade-offs - primarily cold starts and some AWS-specific configuration - are worth it for many applications, especially:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;APIs with sporadic traffic&lt;/li&gt;
&lt;li&gt;Cost-sensitive projects&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The key is understanding that this isn&amp;rsquo;t just about saving money - it&amp;rsquo;s about reducing operational complexity. No more worrying about scaling policies, server maintenance, or capacity planning. Your application scales automatically, and you only pay for what you use.&lt;/p&gt;
&lt;p&gt;Is this approach right for every application? No, but this combination of familiar local development with serverless operations is under utilized.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d love to hear about your experiences with monolithic serverless applications. What hosting solutions have you tried? What challenges have you encountered?&lt;/p&gt;
&lt;h2 id="related-reading"&gt;Related reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/iac/get-started/aws/"&gt;Get started with Pulumi on AWS&lt;/a&gt;: the official quickstart for the tooling used in this post.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/registry/packages/aws/api-docs/lambda/function/"&gt;AWS Lambda function reference&lt;/a&gt;: every property on the resource we used above.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/code-deploy-and-manage-a-serverless-rest-api-on-aws-with-pulumi/"&gt;Code, deploy, and manage a serverless REST API on AWS with Pulumi&lt;/a&gt;: the function-per-route alternative to the monolith pattern.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/controlling-aws-costs-with-lambda-and-pulumi/"&gt;Controlling AWS costs with Lambda and Pulumi&lt;/a&gt;: patterns for keeping your bill predictable as traffic grows.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/cursed-container-iceberg/"&gt;The cursed container iceberg&lt;/a&gt;: every other place you can run the same container if you outgrow Lambda.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/python-for-devops/"&gt;Python for DevOps&lt;/a&gt;: the companion post that ships the full source code for this example.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="changelog"&gt;Changelog&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;2026-04-30&lt;/strong&gt;: Refreshed for 2026. Re-verified Lambda/API Gateway/egress math, replaced the comparison table with current Cloud Run, Fly.io, Railway, and Vercel pricing, restructured H2s as user questions, added an answer-first intro and TL;DR, added a worked cost example and a HowTo schema for the deployment steps.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2025-03-13&lt;/strong&gt;: Minor copy edits.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2025-01-29&lt;/strong&gt;: Original publication.&lt;/li&gt;
&lt;/ul&gt;
&lt;script type="application/ld+json"&gt;
{
"@context": "https://schema.org",
"@type": "HowTo",
"name": "Host a Python Flask app on AWS Lambda for about $1.12/month",
"description": "Package a Flask application as a container, deploy it to AWS Lambda behind an HTTP API Gateway, and manage the infrastructure with Pulumi to host a low-traffic Python API for roughly $1.12/month worst-case in 2026.",
"totalTime": "PT30M",
"estimatedCost": {
"@type": "MonetaryAmount",
"currency": "USD",
"value": "1.12"
},
"tool": [
{"@type": "HowToTool", "name": "Pulumi CLI"},
{"@type": "HowToTool", "name": "Docker"},
{"@type": "HowToTool", "name": "AWS Account"},
{"@type": "HowToTool", "name": "Python 3.12"}
],
"supply": [
{"@type": "HowToSupply", "name": "Flask"},
{"@type": "HowToSupply", "name": "Mangum"},
{"@type": "HowToSupply", "name": "pulumi-aws"},
{"@type": "HowToSupply", "name": "pulumi-awsx"}
],
"step": [
{
"@type": "HowToStep",
"position": 1,
"name": "Write a standard Flask application",
"text": "Create simple_server.py with your Flask routes and wrap the app with Mangum (lambda_handler = Mangum(app)) so the same module can serve HTTP locally and respond to Lambda invocations in production.",
"url": "https://www.pulumi.com/blog/serverless-api/#how-do-i-build-a-flask-app-for-lambda"
},
{
"@type": "HowToStep",
"position": 2,
"name": "Containerize the app for Lambda",
"text": "Write a Dockerfile based on public.ecr.aws/lambda/python:3.12, install Flask and Mangum, and copy in an entrypoint script that runs python locally when RUN_MODE=local and awslambdaric in production.",
"url": "https://www.pulumi.com/blog/serverless-api/#how-do-i-containerize-a-flask-app-for-lambda"
},
{
"@type": "HowToStep",
"position": 3,
"name": "Test the container locally",
"text": "Build the image with docker build -t simple-flask -f infra/Dockerfile . and run it with docker run -d -p 3000:3000 -e RUN_MODE=local simple-flask. Hit /hello and /echo with curl to confirm the app behaves the same as it will in production.",
"url": "https://www.pulumi.com/blog/serverless-api/#do-i-need-to-learn-lambda-specific-patterns"
},
{
"@type": "HowToStep",
"position": 4,
"name": "Define the AWS infrastructure with Pulumi",
"text": "In a Python Pulumi program, declare an HTTP API Gateway, an IAM role for Lambda, an ECR repository, an awsx.ecr.Image to build and push the container, an aws.lambda_.Function with package_type='Image' and 512 MB memory, an integration plus catch-all ANY /{proxy+} route, and a Lambda permission so API Gateway can invoke the function.",
"url": "https://www.pulumi.com/blog/serverless-api/#how-do-i-deploy-a-lambda-container-with-pulumi"
},
{
"@type": "HowToStep",
"position": 5,
"name": "Deploy with pulumi up",
"text": "Run pulumi up to build the image, push to ECR, create the Lambda and API Gateway, and export the public URL. The first invocation pays a cold start; idle time is free.",
"url": "https://www.pulumi.com/blog/serverless-api/#how-do-i-deploy-a-lambda-container-with-pulumi"
}
]
}
&lt;/script&gt;</description><author>Adam Gordon Bell</author><category>python</category><category>serverless</category></item><item><title>Top 15 Python Tools for DevOps</title><link>https://www.pulumi.com/blog/python-for-devops/</link><pubDate>Mon, 06 Jan 2025 00:23:24 -0500</pubDate><guid>https://www.pulumi.com/blog/python-for-devops/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/python-for-devops/index.png" /&gt;
&lt;p&gt;Have you ever had one of those moments: Elasticsearch is crashing, logs are filling up too fast, or a deployment needs data from three different APIs. The big enterprise solutions aren&amp;rsquo;t quite right, and your team estimates six months to build something proper. But you know that with Python and a few choice libraries, you could hack something together by tomorrow.&lt;/p&gt;
&lt;span style="width: 40%; float: left; margin-right: 20px;"&gt;
&lt;span style="text-align:center"&gt;
&lt;img src="web-dashboard.png" alt="gha dash"&gt;
&lt;figcaption&gt;
&lt;i&gt;&lt;a href="https://github.com/adamgordonbell/service-status-monitor"&gt;Let's build a little web dashboard in Python.&lt;/a&gt;&lt;/i&gt;
&lt;/figcaption&gt;
&lt;/span&gt;
&lt;/span&gt;
&lt;p&gt;That&amp;rsquo;s where Python really shines. It lets you be the scrappy problem-solver who can travel up and down the stack. But &lt;a href="https://www.pulumi.com/why-is-python-so-popular/"&gt;Python isn&amp;rsquo;t just for quick fixes&lt;/a&gt;. The same language that helps you hack tomorrow&amp;rsquo;s solution can also build your company&amp;rsquo;s long-term infrastructure.&lt;/p&gt;
&lt;p&gt;This guide covers both ends of that spectrum. I&amp;rsquo;ll show you python packages for &amp;ldquo;we need it yesterday&amp;rdquo; moments, and then move into solutions that can grow with your team. And along the way I&amp;rsquo;ll build &lt;a href="https://github.com/adamgordonbell/service-status-monitor"&gt;a little example service monitor and package capturer&lt;/a&gt; to demonstrate the python packages I&amp;rsquo;m covering. I&amp;rsquo;ll wrap it in a CLI, in a web dashboard and deploy it to AWS, all while staying in Python.&lt;/p&gt;
&lt;p&gt;So here&amp;rsquo;s my list of Python libraries for DevOps work, starting from #15 to the most important last at number #1.&lt;/p&gt;
&lt;h3 id="dashboards--monitoring"&gt;Dashboards / monitoring&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s start with building web dashboards. Long-term, it&amp;rsquo;s usually better if there&amp;rsquo;s an off-the-shelf tool for that specific dashboard you need. But if you can create a quick visual on a Django endpoint, you can move on to the next issue.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;15 and 14. Django and Flask:&lt;/strong&gt; (&lt;a href="https://www.djangoproject.com/"&gt;Django&lt;/a&gt;, &lt;a href="https://flask.palletsprojects.com/"&gt;Flask&lt;/a&gt;) While tools like Grafana excel at metrics visualization, sometimes you need custom dashboards that integrate with internal systems. These frameworks let you build dashboards to track and visualize various aspects of your systems. I find Django a great fit here.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;13. Prometheus:&lt;/strong&gt; (&lt;a href="https://github.com/prometheus/client_python"&gt;prometheus-client&lt;/a&gt;) This library is a versatile tool for metrics. Want to expose custom metrics to Prometheus? Use the client to create counters, gauges, or histograms. Need to pull data from a weird API and expose it as metrics? Write a quick exporter that fetches from your API every few minutes and translates the responses into Prometheus metrics.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;render_template_string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;https://www.githubstatus.com/api/v2/status.json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;indicator&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;description&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;critical&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Unable to reach GitHub API&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;critical&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;none&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;bg-green-500&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;minor&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;bg-yellow-500&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;major&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;bg-red-500&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;critical&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;bg-red-700&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;bg-gray-500&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;render_template_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;span style="text-align:center"&gt;
&lt;figcaption&gt;
&lt;i&gt;&lt;a href="https://github.com/adamgordonbell/service-status-monitor"&gt;Code&lt;/a&gt; for a simple Dashboard&lt;/i&gt;
&lt;/figcaption&gt;
&lt;/span&gt;
&lt;div class="note note-tip"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-lightbulb-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;
&lt;p&gt;&lt;strong&gt;You might also like:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.pulumi.com/blog/python-uv-toolchain/"&gt;
Pulumi &amp;#43; uv: Fast Python Package and Project Management
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.pulumi.com/blog/pulumi-loves-python/"&gt;
Pulumi &amp;#43; Python: Bringing the Best of Modern Python to IaC
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.pulumi.com/blog/next-level-iac-briding-the-declarative-gap/"&gt;
Next-level IaC: Bridging the Declarative Gap
&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 id="task-scheduling-and-orchestration"&gt;Task scheduling and orchestration&lt;/h3&gt;
&lt;p&gt;Sometimes you need to run things on a schedule. And while I love cron, sometimes you need more. Maybe you need to coordinate multiple tasks, handle retries, or manage complex job dependencies. Here&amp;rsquo;s where Python can help:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;12. Schedule:&lt;/strong&gt; (&lt;a href="https://github.com/dbader/schedule"&gt;schedule&lt;/a&gt;) When cron is too heavy but you need more than a bash script. I use it for quick automation tasks like &amp;ldquo;check this API every hour and alert if something&amp;rsquo;s wrong.&amp;rdquo; It runs in your Python process - no system configuration needed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;11. RQ (Redis Queue):&lt;/strong&gt; (&lt;a href="https://python-rq.org/"&gt;rq&lt;/a&gt;) Perfect for local development queues. Need to prototype a GitHub webhook handler that&amp;rsquo;ll eventually use SQS? With Redis in Docker and RQ, you can have a working queue in minutes. No AWS needed until production.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;10. Airflow:&lt;/strong&gt; (&lt;a href="https://airflow.apache.org/"&gt;airflow&lt;/a&gt;) The big gun of scheduling. Yes, it&amp;rsquo;s complex, but sometimes you need that power. I&amp;rsquo;ve seen teams replace entire ETL systems with a few Airflow DAGs. When you need to coordinate multiple steps, handle failures gracefully, and have everything visible in a nice UI, it delivers.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;schedule_url_checks&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;every&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;check_url_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_scheduler&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run_pending&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;schedule_url_checks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;run_scheduler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;daemon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;span style="text-align:center"&gt;
&lt;figcaption&gt;
&lt;i&gt;Adding Schedule to our &lt;a href="https://github.com/adamgordonbell/service-status-monitor"&gt;code, so we can request in the background.&lt;/a&gt;&lt;/i&gt;
&lt;/figcaption&gt;
&lt;/span&gt;
&lt;h3 id="network-analysis-and-security"&gt;Network analysis and security&lt;/h3&gt;
&lt;p&gt;When your network issues go beyond what tcpdump can tell you, or when you need to automate security checks, Python is here to help:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;9. Scapy:&lt;/strong&gt; (&lt;a href="https://scapy.net/"&gt;scapy&lt;/a&gt;) The Python alternative to Wireshark for programmatic network traffic analysis. Need to figure out why your microservices aren&amp;rsquo;t talking to each other? Want to check your load balancer? Use it to capture and analyze packets with a few lines of code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;8. Bandit:&lt;/strong&gt; (&lt;a href="https://github.com/PyCQA/bandit"&gt;bandit&lt;/a&gt;) Security scanning for your CI pipeline. Point it at your Python codebase and it&amp;rsquo;ll flag common issues like hardcoded credentials or unsafe deserialization.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;packet_callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;packet&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;packet_stats&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;packet_stats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;total_packets&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;packet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;haslayer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;HTTPRequest&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;packet_stats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;http_requests&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start_packet_sniffer&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;sniff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;packet_callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Capture packets every 60 seconds&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;start_packet_sniffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;daemon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;span style="text-align:center"&gt;
&lt;figcaption&gt;
&lt;i&gt;Adding packet sniffing to our &lt;a href="https://github.com/adamgordonbell/service-status-monitor"&gt;code, mainly just because we can.&lt;/a&gt;&lt;/i&gt;
&lt;/figcaption&gt;
&lt;/span&gt;
&lt;h3 id="containerization-and-cloud-interaction"&gt;Containerization and cloud interaction&lt;/h3&gt;
&lt;p&gt;Sometimes you need more control over your containers than Dockerfiles can provide. Maybe you need to dynamically build images based on environment variables, or automate container management:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;7. Docker SDK for Python:&lt;/strong&gt; (&lt;a href="https://docker-py.readthedocs.io/"&gt;docker-py&lt;/a&gt;) When you need to automate Docker operations programmatically. I use it to clean up old containers and images, check container health, and build images dynamically from configuration files. More powerful than chaining Docker CLI commands.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;6. Dagger:&lt;/strong&gt; (&lt;a href="https://dagger.io/"&gt;dagger-io&lt;/a&gt;) Write container build logic in Python instead of Dockerfiles. Need to build multiple similar containers with slight variations? Want to pull in files from different sources based on environment? It lets you programmatically define container contents and build steps. (Bonus: this same code can later be used in CI/CD pipelines.)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;dagger&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Connect to the Dagger Engine&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;dagger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;python:3.11-slim&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;with_exec&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;python&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;-c&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;print(&amp;#39;Hello from Dagger!&amp;#39;)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Execute the container and get the output&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;asyncio&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;span style="text-align:center"&gt;
&lt;figcaption&gt;
&lt;i&gt;Using Dagger makes it easy to stay in Python while building containers.&lt;/i&gt;
&lt;/figcaption&gt;
&lt;/span&gt;
&lt;h3 id="building-command-line-tools"&gt;Building command-line tools&lt;/h3&gt;
&lt;span style="width: 50%; float: right; margin-left: 20px;"&gt;
&lt;span style="text-align:center"&gt;
&lt;img src="cli.png" alt="gha dash"&gt;
&lt;figcaption&gt;
&lt;i&gt;&lt;a href="https://github.com/adamgordonbell/service-status-monitor"&gt;Let's build a CLI version of our service status dashboard in Python.&lt;/a&gt;&lt;/i&gt;
&lt;/figcaption&gt;
&lt;/span&gt;
&lt;/span&gt;
&lt;p&gt;Every useful script eventually needs to become a proper tool. When your team starts asking &amp;ldquo;can I use that script too?&amp;rdquo; it&amp;rsquo;s time to enhance your CLI skills:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;5/4. Click/Typer:&lt;/strong&gt; (&lt;a href="https://click.palletsprojects.com/"&gt;Click&lt;/a&gt;, &lt;a href="https://typer.tiangolo.com/"&gt;Typer&lt;/a&gt;) Turn your quick scripts into proper CLI tools. Instead of remembering &lt;code&gt;python cleanup.py --older-than 7 --dry-run&lt;/code&gt;, you get tab completion, help text, and proper argument handling. Typer is particularly nice if you&amp;rsquo;re using modern Python - it uses type hints to build the CLI interface.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3. Rich:&lt;/strong&gt; (&lt;a href="https://rich.readthedocs.io/"&gt;rich&lt;/a&gt;) Make your terminal output actually readable. Need to display tables of data? Want progress bars for long-running tasks? Rich makes it easy to build user-friendly CLIs. I use it to turn wall-of-text logs into colored, formatted output that helps spot problems quickly.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-the-essential-toolkit"&gt;2. The essential toolkit&lt;/h3&gt;
&lt;p&gt;The foundation of any DevOps tooling consists of these essential libraries:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Requests&lt;/strong&gt; (&lt;a href="https://requests.readthedocs.io/"&gt;requests&lt;/a&gt;) - For making HTTP requests&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pytest&lt;/strong&gt; (&lt;a href="https://docs.pytest.org/"&gt;pytest&lt;/a&gt;) - For testing your code&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;logging&lt;/strong&gt; (&lt;a href="https://docs.python.org/3/library/logging.html"&gt;Python logging&lt;/a&gt;) - For structured logging&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;YAML/JSON/TOML parsers&lt;/strong&gt; (&lt;a href="https://pyyaml.org/"&gt;PyYAML&lt;/a&gt;, &lt;a href="https://docs.python.org/3/library/json.html"&gt;json&lt;/a&gt;, &lt;a href="https://github.com/uiri/toml"&gt;toml&lt;/a&gt;) - For config file handling&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While not flashy, these libraries form the backbone of every tool I build. And so I update my little tool to have some tests, some logging and store the api status urls in a toml file.&lt;/p&gt;
&lt;p&gt;Now, #1 and it’s IaC time. I can get my dashboard onto AWS in a lambda using Pulumi.&lt;/p&gt;
&lt;span style="width: 50%; float: left; margin-right: 20px;"&gt;
&lt;span style="text-align:center"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Build and push the Docker image to ECR&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;awsx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ecr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;app-server-image&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;repository_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repository_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;..&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;dockerfile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Dockerfile&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;linux/amd64&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;figcaption&gt;
&lt;i&gt;Push my Image to ECR&lt;/i&gt;
&lt;/figcaption&gt;
&lt;/span&gt;
&lt;/span&gt;
&lt;h3 id="1-pulumi"&gt;1. Pulumi&lt;/h3&gt;
&lt;p&gt;While most tools in this list excel at quick solutions, infrastructure demands a more robust approach. This is where &lt;strong&gt;Pulumi&lt;/strong&gt; stands apart. It&amp;rsquo;s not just another scrappy tool—it&amp;rsquo;s an enterprise-grade Infrastructure as Code platform that happens to harness Python&amp;rsquo;s power and flexibility.&lt;/p&gt;
&lt;p&gt;With support for real Python code instead of configuration languages, you can define, deploy, and manage your cloud resources using familiar programming concepts and practices.&lt;/p&gt;
&lt;p&gt;With Pulumi, you get:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.pulumi.com/docs/iac/concepts/"&gt;Complete programming language support&lt;/a&gt; for using loops, functions, and classes in your infrastructure code&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.pulumi.com/docs/iac/concepts/testing/"&gt;Infrastructure testing capabilities&lt;/a&gt; to validate your deployments before they go live&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.pulumi.com/templates/container-service/"&gt;Container deployment solutions&lt;/a&gt; for managing Docker images and Lambda functions&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.pulumi.com/registry/packages/aws/"&gt;Rich AWS integration&lt;/a&gt; for comprehensive cloud resource management&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/automation-api/"&gt;Infrastructure automation&lt;/a&gt; to streamline your deployment workflows&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Create a Lambda function&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;lambda_function&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lambda_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;custom-lambda&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;custom-lambda&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;package_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Image&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;image_uri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image_uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;lambda_role&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;memory_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;variables&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;FLASK_ENV&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;production&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;span style="text-align:center"&gt;
&lt;figcaption&gt;
&lt;i&gt;Start it up in a Lambda&lt;/i&gt;
&lt;/figcaption&gt;
&lt;/span&gt;
&lt;div class="rounded-lg bg-violet-50 p-6 my-8"&gt;
&lt;p class="heading-4 m-0 mb-3 flex items-center gap-1.5"&gt;Manage your cloud in Python&lt;/p&gt;
&lt;div class="body-base m-0 text-gray-950"&gt;Define AWS Lambda, ECR, and the rest of your infrastructure with real Python instead of YAML, and reuse the same loops, functions, and tests you already know.&lt;/div&gt;
&lt;a href="https://app.pulumi.com/signup" data-track="blog-body-cta" class="btn btn-primary mt-4"&gt;
Get started
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--regular size-4" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-arrow-right-regular"/&gt;&lt;/svg&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id="bringing-it-all-together"&gt;Bringing it all together&lt;/h2&gt;
&lt;span style="width: 50%; float: right; margin-left: 20px;"&gt;
&lt;span style="text-align:center"&gt;
&lt;img src="lambda.png" alt="dashboard deployed as lambda"&gt;
&lt;figcaption&gt;
&lt;i&gt;Dashboard is still pretty basic, but now it's on AWS with a little Python infrastructure code.&lt;/i&gt;
&lt;/figcaption&gt;
&lt;/span&gt;
&lt;/span&gt;
&lt;p&gt;Here&amp;rsquo;s where Python excels - combining these tools to solve real problems. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Need a dashboard for infrastructure costs? Combine Flask with the AWS SDK and Pulumi Insights.&lt;/li&gt;
&lt;li&gt;Want to automate container cleanup? Mix Docker SDK with Schedule for regular maintenance.&lt;/li&gt;
&lt;li&gt;Building a custom GitHub webhook processor? Use Flask to receive webhooks, RQ for asynchronous handling, and Rich for readable logs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Start simple (maybe just a script with Requests and PyYAML), then gradually add more tools as needed. That&amp;rsquo;s the beauty of Python in DevOps - you can start small and grow your solution naturally.&lt;/p&gt;
&lt;h2 id="the-art-of-getting-things-done"&gt;The art of getting things done&lt;/h2&gt;
&lt;p&gt;The tools I&amp;rsquo;ve covered span the full spectrum of DevOps needs—from quick scripts to enterprise-grade solutions. Python&amp;rsquo;s versatility makes it uniquely suited for both ends of this spectrum:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For urgent needs, tools like Schedule, Rich, and Click let you rapidly prototype solutions.&lt;/li&gt;
&lt;li&gt;For long-term infrastructure, Pulumi and Airflow provide the robustness needed for production systems.&lt;/li&gt;
&lt;li&gt;For everything in between, Python&amp;rsquo;s extensive library ecosystem helps you gradually evolve quick fixes into mature tools.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The key is knowing when to use each tool.&lt;/p&gt;</description><author>Adam Gordon Bell</author><category>python</category><category>devops</category><category>infrastructure-as-code</category></item><item><title>AI Engineering Lessons from Building Pulumi Copilot</title><link>https://www.pulumi.com/blog/copilot-lessons/</link><pubDate>Thu, 12 Dec 2024 13:56:13 -0500</pubDate><guid>https://www.pulumi.com/blog/copilot-lessons/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/copilot-lessons/index.png" /&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;Note: This post discusses Pulumi Copilot, which Pulumi Neo has replaced. &lt;a href="https://www.pulumi.com/docs/ai/"&gt;Learn about Neo →&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Building AI-powered developer tools comes with unique challenges, and now that we&amp;rsquo;ve &lt;strong&gt;&lt;a href="https://www.pulumi.com/blog/pulumi-copilot-rest/"&gt;launched our REST API&lt;/a&gt;&lt;/strong&gt;, we want to share some lessons we&amp;rsquo;ve learned building Pulumi Copilot, an AI assistant for cloud infrastructure.&lt;/p&gt;
&lt;p&gt;One of the big challenges was determining what &amp;lsquo;working&amp;rsquo; really meant. So when a message landed in our feedback channel after months of rigorous testing - &amp;lsquo;Your tool doesn&amp;rsquo;t know anything!&amp;rsquo; - it caused some mild panic. We&amp;rsquo;d just made some changes, so we braced for the worst. But our evals were still looking strong, so what was going on?&lt;/p&gt;
&lt;p&gt;The user was attempting to force-delete a stack that still contained resources. But when we dug deeper, we found something fascinating: Copilot had confidently suggested a &amp;lsquo;&amp;ndash;force&amp;rsquo; flag, which would have been a logical solution&amp;hellip; except this flag doesn&amp;rsquo;t exist in Pulumi. Our AI was hallucinating exactly what the user wanted. But this wasn&amp;rsquo;t just a bug - it was the first of many insights that would reshape how we approach AI-powered tools.&lt;/p&gt;
&lt;p&gt;To understand how we got here - and why this &amp;ldquo;error&amp;rdquo; actually taught us something valuable about our product - let&amp;rsquo;s start with the core challenge we faced: balancing traditional software engineering with this new world of prompt engineering.&lt;/p&gt;
&lt;h2 id="engineering-for-reality-prompt-engineering-vs-software-engineering"&gt;Engineering for Reality: Prompt Engineering vs Software Engineering&lt;/h2&gt;
&lt;p&gt;&lt;img src="soft-eng.png" alt="Software Engineering vs Prompt Engineering"&gt;&lt;/p&gt;
&lt;p&gt;When building LLM-powered applications, it&amp;rsquo;s tempting to throw every task at the model. Modern LLMs can generate code, format text, and create clickable links. But this approach carries hidden costs.&lt;/p&gt;
&lt;p&gt;Working on Copilot taught us a key lesson: let the LLM do what it does best and use good old imperative code for everything else.&lt;/p&gt;
&lt;p&gt;Take a seemingly simple feature: listing a user&amp;rsquo;s Pulumi stacks with clickable links based on data from a backend API. Our first implementation used a complex prompt instructing the LLM to construct URLs in the format &lt;code&gt;app.pulumi.com/org/project/stack&lt;/code&gt;. The prompt explained the format, provided examples, and asked the LLM to generate these links from JSON data it had.&lt;/p&gt;
&lt;p&gt;It worked - almost all of the time. But we were seeing occasional malformed URLs and more importantly, this was burning input tokens (and money) on a complicated prompt that made AI construct strings that could be deterministically generated.&lt;/p&gt;
&lt;p&gt;The solution was straightforward: generate the full links in the backend service and include them directly in the context. The LLM then needs no instructions on how to create them. Simple stuff, but it gave faster responses and perfect URLs at a lower cost.&lt;/p&gt;
&lt;p&gt;When you find yourself writing elaborate prompts to handle structured data transformations, stop and ask: Could traditional code do this better? Could this be decomposed so that the LLM does less?&lt;/p&gt;
&lt;p&gt;To validate this approach, we tested Copilot ourselves to see what worked.&lt;/p&gt;
&lt;h2 id="copilot-in-action-real-world-dogfooding"&gt;Copilot in Action: Real-World Dogfooding&lt;/h2&gt;
&lt;p&gt;The internal testing phase taught us invaluable lessons about how people would actually use the tool. We watched our team try Copilot in their daily work, and three common use cases emerged:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Debugging Deployments:&lt;/strong&gt; LLMs clearly excel at summarization. One of the first questions our internal users asked was, &amp;lsquo;Why did my latest infrastructure deployment fail?&amp;rsquo; Using Copilot to extract a clear natural language explanation requests like these has been a clear win.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Understanding Complex Infrastructure:&lt;/strong&gt; Copilot helped our engineers gain insights into Pulumi&amp;rsquo;s own infrastructure. Asking, &amp;lsquo;How many resources are in production?&amp;rsquo; &amp;lsquo;What expensive compute is running&amp;rsquo; or &amp;lsquo;What version are the EKS clusters in EU?&amp;quot; shows the value of allowing users to express infrastructure questions in natural language.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Generating Code:&lt;/strong&gt; One of the first queries logged was, &amp;lsquo;I want a static website on AWS behind a CloudFront CDN.&amp;rsquo; Another came from a Solutions Engineer, tasked with demonstrating Pulumi&amp;rsquo;s CrossGuard policy engine to a prospect, asking Copilot to generate policy code.&lt;/p&gt;
&lt;p&gt;These early experiences showed the value of Copilot. But they also revealed the need for a systematic approach to handling diverse user queries. This led to the development of what we call skills.&lt;/p&gt;
&lt;h2 id="skillful-slicing-modular-mastery"&gt;Skillful Slicing: Modular Mastery&lt;/h2&gt;
&lt;p&gt;As Copilot grew, we broke it into smaller pieces we call skills. Each skill does one specific job. The Insights skill handles queries about resource usage and configuration (&amp;ldquo;How many S3 buckets do I have?&amp;rdquo;), the Cloud Skill interacts with the Pulumi Service API to manage infrastructure (&amp;ldquo;Show me my stacks.&amp;rdquo;), the Code Skill generates Pulumi code snippets (&amp;ldquo;Write a Typescript program&amp;hellip;&amp;rdquo;), and the Docs Skill retrieves information from Pulumi documentation (&amp;ldquo;How do I use &lt;a href="https://www.pulumi.com/docs/iac/concepts/update-plans/"&gt;update plans&lt;/a&gt;?&amp;rdquo;).&lt;/p&gt;
&lt;p&gt;When you ask Copilot something, it figures out what you need and picks the right skill for the job – like a manager deciding which expert to send your question to. This &lt;a href="https://platform.openai.com/docs/guides/function-calling"&gt;function-calling&lt;/a&gt; approach, orchestrated by a component we call the &amp;ldquo;outer loop,&amp;rdquo; allows Copilot to access and process information beyond its internal knowledge base.&lt;/p&gt;
&lt;p&gt;Fortunately, Pulumi Cloud already exposes a &lt;a href="https://www.pulumi.com/docs/pulumi-cloud/reference/cloud-rest-api/"&gt;rich API&lt;/a&gt; - in fact, this is what powers the Pulumi Console and the Pulumi CLI - so all we had to do is to build a &lt;em&gt;skill&lt;/em&gt; that maps the user query to the appropriate Pulumi Cloud REST API. A question like &amp;ldquo;Show me my stacks&amp;rdquo; translates into the &lt;a href="https://www.pulumi.com/docs/pulumi-cloud/reference/cloud-rest-api/#list-stacks"&gt;List Stacks&lt;/a&gt; API call. A question like &amp;lsquo;Show me my untagged EC2 instances&amp;rsquo; is a bit more complex but it breaks down into clear components - resource type (EC2), filter condition (untagged) - that route to the Insights skill. This mapping helped us handle the many ways users phrase the same technical request.&lt;/p&gt;
&lt;p&gt;Refining this routing system revealed another opportunity: streamlining the Debug button workflow.&lt;/p&gt;
&lt;h2 id="debug-dispatch"&gt;Debug Dispatch&lt;/h2&gt;
&lt;p&gt;&lt;img src="optimize.png" alt="Before and After"&gt;&lt;/p&gt;
&lt;p&gt;Originally, when a user clicked &amp;lsquo;Debug with Copilot&amp;rsquo;, the system would send a text query like &amp;ldquo;Analyze this update and explain any errors.&amp;rdquo; Copilot would then:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Determines the user wants to analyze an update&lt;/li&gt;
&lt;li&gt;Identifies which API to call&lt;/li&gt;
&lt;li&gt;Calls the API&lt;/li&gt;
&lt;li&gt;Summarize the result&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Having the LLM figure out what the user wants is the right approach in general, but in this case, we already know the user&amp;rsquo;s intent - they clicked a debug button. So, we can directly call the analysis API to get the results and use the LLM solely for what it does best: summarizing technical output into clear, actionable explanations.&lt;/p&gt;
&lt;p&gt;This is another small win for the &amp;ldquo;Software Engineering over Prompt Engineering&amp;rdquo; approach. Traditional code handles the predictable parts, while AI focuses on the human-facing explanations.&lt;/p&gt;
&lt;p&gt;But while minimizing the LLM workload helped with efficiency, we soon faced an even trickier challenge: the deceptive polish of AI-generated outputs.&lt;/p&gt;
&lt;h2 id="the-illusion-of-correctness"&gt;The Illusion of Correctness&lt;/h2&gt;
&lt;p&gt;&lt;img src="false-info.png" alt="Before and After"&gt;&lt;/p&gt;
&lt;p&gt;Large language models excel at generating well-structured, grammatically correct output. They make neat tables, tell good stories, and generally sound confident. That&amp;rsquo;s what makes them dangerous because this polished presentation can mask underlying flaws in the information itself, creating a false sense of confidence for users.&lt;/p&gt;
&lt;p&gt;One of our early testers, Pablo, a data engineer at Pulumi, encountered this firsthand. He posed a query to Pulumi Copilot, asking for a summary of resources within a specific project. The response he received was impeccably formatted, neatly categorizing resources by type and providing counts for each. It &lt;em&gt;looked&lt;/em&gt; right, and for us humans sometimes looking right carries a lot of weight.&lt;/p&gt;
&lt;p&gt;However, a closer inspection revealed the numbers were way off. Copilot had asked for the wrong data and then summarized it beautifully - but incorrectly. This highlighted our next challenge: how do you systematically test a system that can be confidently wrong while sounding completely right?&lt;/p&gt;
&lt;h2 id="testing-the-untestable-validating-llm-outputs"&gt;Testing the Untestable: Validating LLM Outputs&lt;/h2&gt;
&lt;p&gt;When testing traditional code, we expect consistent, predictable outputs. With LLMs, even correct answers can vary. Here&amp;rsquo;s how we tackle this challenge.&lt;/p&gt;
&lt;p&gt;Our first approach was simple: keyword checks. For example, when testing the update analysis feature, we checked if the LLM&amp;rsquo;s response included the word &amp;ldquo;security&amp;rdquo; and described the error. This worked for straightforward cases but broke down quickly. Take a question like, &amp;ldquo;How many Lambdas am I running?&amp;rdquo; The LLM might give the right numbers but skip the word &amp;ldquo;running,&amp;rdquo; failing the test even though the answer was correct.&lt;/p&gt;
&lt;p&gt;These early failures revealed the limitations of keyword-based validation and underscored the need for a more nuanced approach. Inspired by platforms like LangSmith and Promptfoo, we began leveraging LLMs themselves as evaluators. For deterministic tasks, simple keyword checks suffice, but for more complex scenarios—like assessing whether a response answers a specific question—we rely on an &amp;ldquo;LLM Judge.&amp;rdquo; This approach balances efficiency and flexibility, reserving LLM evaluation for cases where it truly adds value. The test suite now integrates both methods, running against every code change to validate response content, accuracy, and format.&lt;/p&gt;
&lt;p&gt;&lt;img src="promptfoo.png" alt="Example PromptFoo Eval"&gt;&lt;/p&gt;
&lt;p&gt;The eval suite keeps getting more robust, which means that when new AI models drop, we can quickly catch any weirdness before it hits production. The generative AI space moves crazy fast and the code changes a lot, but the evals are a safety net - catching hallucinations, maintaining quality, and making sure we don&amp;rsquo;t ship anything that&amp;rsquo;ll annoy our users.&lt;/p&gt;
&lt;p&gt;So, while hallucinations are now much rarer, what about that one with the &lt;code&gt;--force&lt;/code&gt; flag? Yes, it&amp;rsquo;s &amp;ldquo;just another bug,&amp;rdquo; but it taught us something fascinating about these AI errors.&lt;/p&gt;
&lt;div class="note note-tip"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-lightbulb-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;
&lt;p&gt;&lt;strong&gt;You might also like:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.pulumi.com/blog/codegen-learnings/"&gt;
A Recipe for a Better AI-based Code Generator
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.pulumi.com/blog/pulumi-copilot-rest/"&gt;
Announcing the Pulumi Copilot REST API Preview
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.pulumi.com/blog/pulumi-copilot/"&gt;
Introducing Pulumi Copilot: Intelligent Cloud Management
&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="llms-think-like-humans-sort-of"&gt;LLMs think like humans (sort of)&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;--force&lt;/code&gt; hallucination wasn&amp;rsquo;t totally wrong - it was revealing what users intuitively expect from the CLI, and the LLM accidentally showed us what was missing. Force deletion is a common pattern across developer tools, and the LLM, trained on vast amounts of documentation and code, simply reflects these established conventions.&lt;/p&gt;
&lt;p&gt;This has fundamentally changed how we view hallucinations. While the team works constantly to minimize them – and our eval work means they happen way less frequently – some of them are clearly product signals. The LLM, in this light, becomes an unexpected source of user research, drawing on its training across thousands of developer tools and experiences.&lt;/p&gt;
&lt;p&gt;This insight is one of the key lessons of building Copilot:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Minimize LLM Usage:&lt;/strong&gt; Let traditional code handle deterministic tasks, reserve LLMs for natural language work&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Decompose into Skills:&lt;/strong&gt; Break complex tasks into modular units that combine LLM and traditional code appropriately&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test Rigorously:&lt;/strong&gt; Use multiple validation approaches, including LLMs testing LLMs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Learn from Hallucinations:&lt;/strong&gt; Sometimes incorrect outputs reveal user expectations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Learn from Users Continuously:&lt;/strong&gt; User interactions improve our AI systems - from training better skills to catching hallucinations and revealing product opportunities.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These lessons helped shape our latest release: &lt;strong&gt;&lt;a href="https://www.pulumi.com/blog/pulumi-copilot-rest/"&gt;the Pulumi Copilot REST API&lt;/a&gt;&lt;/strong&gt;, now available in preview. You can integrate these same capabilities and skills into your own tools and workflows. Whether you&amp;rsquo;re building CLI extensions, chat integrations, or automated deployment checks, the API provides a contextual understanding of Copilot. &lt;strong&gt;&lt;a href="https://www.pulumi.com/docs/pulumi-cloud/copilot/api/"&gt;Try it out&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We can&amp;rsquo;t wait to see what you build!&lt;/p&gt;</description><author>Artur Laksberg</author><author>Simon Howe</author><author>Adam Gordon Bell</author><category>copilot</category><category>ai</category><category>infrastructure-as-code</category></item><item><title>105 Ways to Run Containers: The Cloud Container Iceberg</title><link>https://www.pulumi.com/blog/cursed-container-iceberg/</link><pubDate>Tue, 03 Dec 2024 10:29:54 -0500</pubDate><guid>https://www.pulumi.com/blog/cursed-container-iceberg/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/cursed-container-iceberg/index.png" /&gt;
&lt;p&gt;If a computer can be connected to the internet, someone has tried to run a &lt;a href="https://www.pulumi.com/containers"&gt;container&lt;/a&gt; on it. From quantum computers to smart toasters, from phones in AWS racks to CI pipelines, there&amp;rsquo;s many ways to deploy containers. While most people only know about the mainstream cloud providers at the surface, there&amp;rsquo;s actually a vast world of increasingly unusual and specialized options beneath.&lt;/p&gt;
&lt;p&gt;This guide serves two purposes: to showcase the surprising breadth of container options available today, and to help you understand the full spectrum of choices – from practical to very experimental. Whether you&amp;rsquo;re looking for production-ready solutions or just curious about what&amp;rsquo;s out there in the dark depths, you&amp;rsquo;ll find something interesting here.&lt;/p&gt;
&lt;h2 id="constraints"&gt;Constraints&lt;/h2&gt;
&lt;p&gt;But first, we need ground rules for our container adventures. All listed items should:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Technically possible:&lt;/strong&gt; Must work today with existing tech, even if the setup makes you question your life choices.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Runs real containers:&lt;/strong&gt; OCI containers only. No JavaScript &amp;ldquo;containers&amp;rdquo; pretending to be the real thing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Actually doable:&lt;/strong&gt; You can set this up right now with a laptop and a credit card. No theoretical schemes allowed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="iceberg.jpg" alt="Visual iceberg diagram titled &amp;ldquo;The Cloud Container Iceberg,&amp;rdquo; categorizing over 100 container platforms from mainstream options like EKS and GKE at the top to obscure and experimental environments like AWS Device Farm and Azure Quantum at the bottom."&gt;&lt;/p&gt;
&lt;h2 id="above-the-iceberg--the-usual-suspects"&gt;Above the iceberg – The usual suspects&lt;/h2&gt;
&lt;p&gt;These are the familiar faces of container orchestration—the ones you&amp;rsquo;d introduce to your parents. Reliable, well-documented, and about as exciting as watching paint dry.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Amazon Elastic Kubernetes Service (EKS)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Amazon Elastic Container Service (ECS)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AWS Fargate&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Azure Kubernetes Service (AKS)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Google Kubernetes Engine (GKE)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Google Cloud Run&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Amazon EC2&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AWS App Runner&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Azure App Service for Containers&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Google Compute Engine with Containers&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="on-the-iceberg--the-slightly-less-obvious"&gt;On the iceberg – The slightly less obvious&lt;/h2&gt;
&lt;p&gt;As we dip just below the surface, we find services like a rare B-side acoustic release from a big band. It&amp;rsquo;s not as mainstream but still a respectable choice for running your containers.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AWS Elastic Beanstalk&lt;/strong&gt; – Deploy containers, debug YAML&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Amazon Lightsail Containers&lt;/strong&gt; – AWS with the guard rails on&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Azure Container Apps&lt;/strong&gt; – Serverless, but complicated&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Google App Engine Flexible Environment&lt;/strong&gt; – PaaS for Containerized Apps&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Azure Container Instances (ACI)&lt;/strong&gt; – Kubernetes without the chaos&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="the-kubernetes-multiverse--because-one-universe-of-k8s-wasnt-enough"&gt;The kubernetes multiverse – Because one Universe of k8s wasn&amp;rsquo;t enough&lt;/h2&gt;
&lt;p&gt;Diving deeper, we discover there&amp;rsquo;s an entire multiverse of regional and specialty K8s providers, helpfully cataloged &lt;a href="https://landscape.cncf.io/guide#platform--certified-kubernetes-hosted"&gt;by the CNCF&lt;/a&gt;. Think parallel universes, but instead of evil twins, you get slightly different container runtime implementations.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The Global Titans&lt;/strong&gt; – The international tech giants couldn&amp;rsquo;t resist joining the K8s party: Huawei Cloud Container Engine, Tencent, and Baidu bring their own flavor of cloud-native orchestration to the mix. Add in Samsung, China Mobile KCS, JD Cloud, Inspur, and QingCloud, and you have enough options to start a diplomatic incident.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The European Union of Containers&lt;/strong&gt; – Europeans take their container privacy seriously: IONOS and plusserver bring German engineering to K8s, OVH adds French flair, Exoscale offers Swiss precision, and STACKIT, SysEleven, WIIT, Gcore, and Previder ensure your containers comply with every possible EU regulation. Conoa Proact throws in some Swedish design principles for good measure.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The Asia-Pacific Pod Party&lt;/strong&gt; – The APAC region isn&amp;rsquo;t missing out: NHN brings Korean optimization to your clusters, while ARDC Nectar and Catalyst let you run containers upside down in Australia and New Zealand. TKS Cloud Service ensures your containers can run anywhere from Tokyo to Tasmania.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The Specialists&amp;rsquo; Society&lt;/strong&gt; – For when regular K8s isn&amp;rsquo;t quirky enough: OKESTRO VIOLA orchestrates your containers with musical flair, while Petasus, De Novo, Aruba, and Taikun provide enough specialty K8s options to ensure every cluster is a unique snowflake.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="below-the-water--kubernetes-everywhere--let-a-thousand-k8s-bloom"&gt;Below the water – Kubernetes everywhere – Let a thousand K8s bloom&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s plunge deeper. Because one Kubernetes cluster is never enough, why not sprinkle them everywhere? On-premises, in the cloud, under your desk—there&amp;rsquo;s a K8s solution for all that.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Amazon EKS Anywhere&lt;/strong&gt; – AWS container but in your own data center&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Amazon ECS Anywhere&lt;/strong&gt; – Like EKS Anywhere but ECS&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Azure Arc&lt;/strong&gt; – Hybrid cloud for masochists&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Google Anthos&lt;/strong&gt; – Hybrid cloud something something&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AWS Outposts&lt;/strong&gt; – AWS, now with hardware shipping delays&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Azure Red Hat OpenShift&lt;/strong&gt; – Azure but with a fedora&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;VMware Tanzu&lt;/strong&gt; – Because VMware heard you like Kubernetes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Red Hat OpenShift&lt;/strong&gt; – Fedoras on AWS&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IBM Cloud Kubernetes Service&lt;/strong&gt; – Kubernetes with a touch of enterprise nostalgia&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Oracle Container Engine for Kubernetes (OKE)&lt;/strong&gt; – For the brave souls trusting Oracle&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alibaba Cloud Elastic Container Service (ACK)&lt;/strong&gt; – K8s with Chinese characteristics&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DigitalOcean Kubernetes (DOKS)&lt;/strong&gt; – K8s in the ocean, hopefully no sharks&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Linode Kubernetes Engine (LKE)&lt;/strong&gt; – Lightweight K8s for the minimalist&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vultr Kubernetes Engine (VKE)&lt;/strong&gt; – Yet another place to run your pods&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scaleway Kapsule&lt;/strong&gt; – French elegance meets Kubernetes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hetzner Cloud Managed Kubernetes&lt;/strong&gt; – German engineering for your clusters&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nutanix Karbon&lt;/strong&gt; - Because regular Kubernetes wasn&amp;rsquo;t converged enough&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Metal Stack Cloud Kubernetes&lt;/strong&gt; - For when virtual machines are too virtual&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="note note-tip"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-lightbulb-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;
&lt;p&gt;&lt;strong&gt;You might also like:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.pulumi.com/blog/unified-programmatic-approach-infrastructure-management-bmw-using-pulumi/"&gt;
Unified and Programmatic Approach to Infrastructure Management at BMW Using Pulumi
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.pulumi.com/blog/docker-build/"&gt;
Introducing The New Docker Build Provider
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.pulumi.com/blog/ecs-anywhere-launch/"&gt;
How to Use ECS Anywhere with Pulumi [Step-by-Step Guide]
&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="middle-of-the-iceberg--serverless-and-function-containers--now-you-see-me-now-you-dont"&gt;Middle of the iceberg – Serverless and function containers – Now you see me, now you don&amp;rsquo;t&lt;/h2&gt;
&lt;p&gt;As we descend to the depths, we find containers with commitment issues. They appear, do their job, and disappear.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AWS Batch&lt;/strong&gt; – Containers that only work the night shift&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AWS Lambda&lt;/strong&gt; – Lambda&amp;rsquo;s containers for when a 250 MB zip file wasn&amp;rsquo;t enough baggage.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Azure Functions&lt;/strong&gt; – Roll your own functions in containers and hope for the best.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Google Cloud Functions&lt;/strong&gt; – Containers pretending to be functions, but now on Google Cloud.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alibaba Function Compute&lt;/strong&gt; – You get the idea.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="bottom-of-the-iceberg--specialized-and-hybrid-platforms--the-swiss-army-knife-of-container-hosting"&gt;Bottom of the iceberg – Specialized and hybrid platforms – The swiss army knife of container hosting&lt;/h2&gt;
&lt;p&gt;Down here, containers run in ways that make people question your life choices. These platforms let you host containers in environments many fear to tread.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AWS Proton&lt;/strong&gt; – Automate your deployments and run a container&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Azure Virtual Machines with Docker&lt;/strong&gt; – When you miss managing servers but love containers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IBM Cloud Code Engine&lt;/strong&gt; – Run anything, anywhere, if you can figure it out&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alibaba Elastic Container Instance (ECI)&lt;/strong&gt; – Containers that disappear faster than you can say &amp;ldquo;Jack Ma&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="below-the-iceberg--containers-on-the-edge-of-reason"&gt;Below the iceberg – Containers on the edge of reason&lt;/h2&gt;
&lt;p&gt;In these lightless depths, you want your containers to live life on the edge—literally. Because nothing says &amp;ldquo;enterprise-ready&amp;rdquo; like running production workloads on a smart toaster.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AWS IoT Greengrass&lt;/strong&gt; – Run AWS Lambda and Docker containers on everything from factory equipment to smart fridges. Nothing says &amp;ldquo;digital transformation&amp;rdquo; like your smart fridge running machine learning models.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Azure IoT Edge&lt;/strong&gt; – Like Greengrass, but only when your toaster prefers Windows updates.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AWS Snowball Edge&lt;/strong&gt; – For when &amp;lsquo;cloud migration&amp;rsquo; means paying FedEx to move your containers. Yes, it&amp;rsquo;s literally a box they mail you that can run containers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AWS Snowcone&lt;/strong&gt; – An even smaller box that runs just enough containers to make you appreciate your data center. Perfect for when you need AWS in places where AWS doesn&amp;rsquo;t want to build a region.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BalenaCloud IoT Device Management&lt;/strong&gt; – A platform that lets you remotely deploy and manage containers on fleets of Raspberry Pis. Because nothing says &amp;ldquo;enterprise IoT&amp;rdquo; like running your production stack on $35 computers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fly.io&lt;/strong&gt; – Global Edge Container Deployment, because apparently just picking one region was too simple.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Edge Node Service (Tencent Cloud)&lt;/strong&gt; – Edge deployment behind great firewall.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;StackPath Edge Computing&lt;/strong&gt; – More edge containers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Koyeb&lt;/strong&gt; – Serverless Edge containers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Platform.sh&lt;/strong&gt; – Multi-Cloud Edge containers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;StarlingX&lt;/strong&gt; - Edge computing platform for when 5G and IoT buzzwords aren&amp;rsquo;t enough&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ZStack Edge&lt;/strong&gt; - Edge computing with Chinese characteristics, now at the edge of the network, too&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="the-abyss--where-containers-fear-to-tread"&gt;The abyss – Where containers fear to tread&lt;/h2&gt;
&lt;p&gt;And now we reach the darkest depths. For the bravest souls who look at conventional container platforms and think, &amp;ldquo;Not edgy enough.&amp;rdquo; Here lie the most unusual ways to run containers in production.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Amazon GameLift&lt;/strong&gt; – Run your containerized services disguised as game servers. High scores may apply.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AWS Device Farm&lt;/strong&gt; – Deploy containers on hundreds of actual phones in AWS racks. Perfect for when &amp;ldquo;mobile-first&amp;rdquo; goes too far.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AWS AppStream 2.0&lt;/strong&gt; – Stream Windows to run Docker to stream containers. It&amp;rsquo;s virtualization all the way down.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Google Cloud Dataflow&lt;/strong&gt; – Hide containerized services in pipeline steps named &amp;ldquo;daily_data_normalize_37&amp;rdquo;. Hope your SRE enjoys debugging ETL.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Google Cloud Composer&lt;/strong&gt; – Your containerized microservice masquerades as an Airflow DAG. Because nothing says reliability like cron.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Google Colab &amp;amp; Jupyter Notebooks&lt;/strong&gt; – Run containers in &lt;code&gt;data_preprocessing_step.ipynb&lt;/code&gt;. Watch ML teams debug production.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Azure Quantum&lt;/strong&gt; – Package quantum containers with &lt;code&gt;az quantum job submit&lt;/code&gt;. Now, your uptime is truly uncertain.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Azure Cognitive Services in Containers&lt;/strong&gt; – Offline AI containers that promise not to become self-aware. Terms and conditions may apply.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Azure DevTest Labs&lt;/strong&gt; – Run containerized test environments that self-destruct like Mission Impossible tapes. Perfect for when temporary becomes permanent.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="beyond-the-abyss"&gt;Beyond the abyss&lt;/h2&gt;
&lt;p&gt;There you have it, the container iceberg. And I didn&amp;rsquo;t even have room to add the extra cursed ways you can run containers. Here is some of what didn&amp;rsquo;t make it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cloud IDEs and Workspaces&lt;/strong&gt; – Want to turn your development environment into a sneaky container platform? Take your pick: GitHub Codespaces, GitPod for the GitLab enthusiasts, or Eclipse Che for the masochists who miss Eclipse crashing locally. JetBrains Space or Replit? All this lets you run a container until you close your browser tab.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;GitHub Actions et al.&lt;/strong&gt; – Ever wanted to run a production service disguised as a CI job? Just label your production API start-up script as &amp;ldquo;integration_test_step_42&amp;rdquo;, add a &lt;code&gt;sleep 21600&lt;/code&gt; and a call to retrigger itself, and hope nobody notices your &amp;ldquo;test&amp;rdquo; has been running for months. Apply this same trick to AWS CodeBuild, GitLab CI/CD, CircleCI, Azure Pipelines, Travis CI, Bitbucket Pipelines, Drone CI, Jenkins Cloud, TeamCity Cloud, and Google Cloud Build for Bonus points.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="managing-your-container-zoo-with-pulumi"&gt;Managing your container zoo with pulumi&lt;/h2&gt;
&lt;p&gt;Whether you&amp;rsquo;re deploying containers to mainstream cloud providers or attempting to run them on quantum computers (really, please let us know how that goes), Pulumi helps you manage it all with real code instead of config file soup. Our &lt;a href="https://www.pulumi.com/registry/packages/kubernetes/"&gt;Kubernetes support&lt;/a&gt; means you can wrangle your containers wherever they might roam – from AWS to Azure, GCP to Kubernetes, or even that smart toaster you&amp;rsquo;re eyeing for production deployment.&lt;/p&gt;
&lt;p&gt;With Pulumi, you get:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/registry/"&gt;Cloud-native container orchestration&lt;/a&gt; across all major platforms and Kubernetes distributions&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/iac/concepts/testing/"&gt;Infrastructure testing&lt;/a&gt; to ensure your containers deploy correctly (even in the weird places)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/pulumi-go-boilerplate-v2/"&gt;Provider templates&lt;/a&gt; for when you invent yet another way to run containers&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/concepts/stack/#stackreferences"&gt;Stack references&lt;/a&gt; for managing multiple environments without losing your mind&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/automation-api/"&gt;Automation API&lt;/a&gt; for programmatically herding your container fleets&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ready to bring some sanity to your container deployment strategy? Get started with Pulumi:&lt;/p&gt;
&lt;a
href="https://www.pulumi.com/docs/get-started/"
class="btn btn-primary"
&gt;
Try Pulumi for Free
&lt;/a&gt;
&lt;h2 id="the-final-word"&gt;The final word&lt;/h2&gt;
&lt;p&gt;At this point, &lt;strong&gt;105&lt;/strong&gt; options in, you might wonder: &amp;ldquo;Has cloud container hosting gone too far?&amp;rdquo; Well, I&amp;rsquo;ve a solution to propose:&lt;/p&gt;
&lt;p&gt;At Pulumi, we&amp;rsquo;ve got providers for &lt;a href="https://www.pulumi.com/registry/packages/aws/api-docs/provider/"&gt;all&lt;/a&gt; &lt;a href="https://www.pulumi.com/registry/packages/azure-native/"&gt;the&lt;/a&gt; &lt;a href="https://www.pulumi.com/registry/packages/gcp/"&gt;usual&lt;/a&gt; &lt;a href="https://www.pulumi.com/registry/packages/kubernetes/"&gt;suspects&lt;/a&gt; and, surprisingly, many more in this container circus. And if you&amp;rsquo;ve figured out how to run containers on a quantum computer inside a data pipeline in New Zealand, you can write &lt;a href="https://github.com/pulumi/pulumi-provider-boilerplate/"&gt;a provider&lt;/a&gt; for that, too.&lt;/p&gt;
&lt;p&gt;So, use Pulumi and we will figure out how &lt;em&gt;RedHat-Tanzu-Greengrass-Icecore-Device-Farm&lt;/em&gt; works and provide you a sane interface to it. You&amp;rsquo;ve got other things to worry about.&lt;/p&gt;</description><author>Adam Gordon Bell</author><category>kubernetes</category><category>containers</category><category>cloud-native</category><category>docker</category></item><item><title>Pulumi + uv: Fast Python Package and Project Management</title><link>https://www.pulumi.com/blog/python-uv-toolchain/</link><pubDate>Wed, 27 Nov 2024 12:43:45 +0100</pubDate><guid>https://www.pulumi.com/blog/python-uv-toolchain/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/python-uv-toolchain/index.png" /&gt;
&lt;p&gt;Continuing our work to bring &lt;a href="https://www.pulumi.com/blog/pulumi-loves-python/"&gt;the best of modern Python to Infrastructure as Code&lt;/a&gt;, we are excited to announce built-in support for &lt;a href="https://docs.astral.sh/uv/"&gt;uv&lt;/a&gt; in Pulumi. uv is an extremely fast Python package manager that can install dependencies up to 100x faster than traditional tools, providing one of the fastest ways to manage your Python dependencies and virtual environments.&lt;/p&gt;
&lt;h2 id="why-uv"&gt;Why uv?&lt;/h2&gt;
&lt;p&gt;Listen in on this discussion between &lt;a href="https://www.pulumi.com/blog/author/adam-gordon-bell/"&gt;Adam Gordon Bell&lt;/a&gt; and &lt;a href="https://www.pulumi.com/blog/author/julien-poissonnier/"&gt;Julien Poissonnier&lt;/a&gt; as they discuss Pulumi + uv and why uv is so fast!
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/hxi7ZL9H0IU?rel=0?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;uv brings several key advantages to your Python development workflow:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Blazing Fast Performance&lt;/strong&gt;: Written in Rust, uv can install packages 10-100x faster than pip&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reliable Dependency Resolution&lt;/strong&gt;: Advanced resolution algorithm ensures consistent environments&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Drop-in Replacement&lt;/strong&gt;: Works seamlessly with existing requirements.txt and pyproject.toml files&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Built-in Virtual Environment Management&lt;/strong&gt;: Simplified environment handling out of the box&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="using-uv-in-pulumi"&gt;Using uv in Pulumi&lt;/h2&gt;
&lt;p&gt;To use uv to manage your Python virtual environment and dependencies, set the &lt;code&gt;toolchain&lt;/code&gt; option to &lt;code&gt;uv&lt;/code&gt; in your &lt;code&gt;Pulumi.yaml&lt;/code&gt; file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;uv-goes-brrrr&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;python&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;toolchain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;uv&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;virtualenv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;.venv&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you have uv installed, you can run &lt;code&gt;pulumi new python&lt;/code&gt; and select uv as the toolchain to use for installing dependencies and running the program.&lt;/p&gt;
&lt;p&gt;You can find more information on how to use uv in Pulumi in our &lt;a href="https://www.pulumi.com/docs/iac/languages-sdks/python/#uv"&gt;Python documentation&lt;/a&gt;.&lt;/p&gt;</description><author>Adam Gordon Bell</author><author>Julien Poissonnier</author><category>python</category><category>infrastructure-as-code</category></item><item><title>Fargate vs EC2</title><link>https://www.pulumi.com/blog/fargate-vs-ec2/</link><pubDate>Mon, 18 Nov 2024 08:42:34 -0500</pubDate><guid>https://www.pulumi.com/blog/fargate-vs-ec2/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/fargate-vs-ec2/index.png" /&gt;
&lt;p&gt;Building an EKS cluster requires choosing how your containers will actually run - either on EC2 instances you manage or through AWS Fargate&amp;rsquo;s pod-by-pod approach. The differences can be pretty dramatic in practice. I&amp;rsquo;m setting up a demo cluster right now using Pulumi, so let me show you what I mean.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/fargate-vs-ec2/#bin-packing"&gt;Bin Packing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/fargate-vs-ec2/#pros-and-cons"&gt;Pros and Cons&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/fargate-vs-ec2/#workload-example-static-analysis"&gt;Workload Example: Static Analysis&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/fargate-vs-ec2/#example-go-services-for-e-commerce"&gt;Example: Go Services for E-commerce&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/fargate-vs-ec2/#fargate-vs-ec2-pricing"&gt;Fargate vs EC2 Pricing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/fargate-vs-ec2/#misconceptions-about-fargate"&gt;Misconceptions About Fargate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/fargate-vs-ec2/#managing-container-orchestration-with-pulumi"&gt;Managing Container Orchestration with Pulumi&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/fargate-vs-ec2/#why-not-both"&gt;Why Not Both&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here is my Fargate cluster:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pulumi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pulumi_awsx&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;awsx&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pulumi_eks&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;eks&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pulumi_kubernetes&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;k8s&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Create VPC with default CIDR&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;eks_vpc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;awsx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Vpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;eks-vpc&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;enable_dns_hostnames&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Create minimal EKS cluster&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;eks_cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;eks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;eks-cluster&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;vpc_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;eks_vpc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vpc_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;private_subnet_ids&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;eks_vpc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;private_subnet_ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;fargate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;kubeconfig&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eks_cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kubeconfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And when I look in k9s I see this:&lt;/p&gt;
&lt;p&gt;&lt;img src="k9s-fargate.gif" alt="k9s fargate demo"&gt;&lt;/p&gt;
&lt;p&gt;You can see that each pod is on its own node. Even the core-dns pods in the system namespace are each on their own nodes. That is a lot of nodes, and it&amp;rsquo;s going to keep growing with my cluster.&lt;/p&gt;
&lt;p&gt;If I change my cluster to be backed by EC2 nodes, I get something different.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;eks_cluster = eks.Cluster(&amp;#34;eks-cluster&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; vpc_id=eks_vpc.vpc_id,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; private_subnet_ids=eks_vpc.private_subnet_ids,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- fargate=True,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ instance_type=&amp;#34;t3.medium&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ desired_capacity=2,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ min_size=1,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ max_size=2,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, there are only two nodes, with the pods distributed among them. Those are my two &lt;code&gt;t3.medium&lt;/code&gt;s.&lt;/p&gt;
&lt;p&gt;&lt;img src="k9s-eks.gif" alt="k9s eks demo"&gt;&lt;/p&gt;
&lt;p&gt;So when should you use Fargate, and when EC2 for EKS? Well, it really all relates to what we are seeing here with the number and size of nodes.&lt;/p&gt;
&lt;p&gt;Let me explain.&lt;/p&gt;
&lt;h2 id="bin-packing"&gt;Bin Packing&lt;/h2&gt;
&lt;p&gt;Normally, the Kubernetes scheduler has to solve a bin-packing problem—fitting M pods across N nodes based on available resources. This is a straight-up resource optimization problem of balancing CPU, memory, and other resource demands across the cluster.&lt;/p&gt;
&lt;p&gt;This can be surprisingly complex in practice. Teams must balance selecting the right instance types for different workload needs while managing multiple node groups. Adding to this complexity are scheduling rules and resource quotas that need careful configuration. Perhaps most challenging is striking the right balance between high resource utilization and maintaining enough headroom for spikes in demand.&lt;/p&gt;
&lt;p&gt;&lt;img src="ec2-pods.png" alt="Diagram showing multiple Kubernetes pods running on shared EC2 nodes labeled Node1 and Node2."&gt;&lt;/p&gt;
&lt;p&gt;With Fargate, AWS sidesteps this challenge by providing a correctly sized bin for each pod. Each Fargate pod runs on its own dynamically provisioned, right-sized mini-environment, where the &amp;ldquo;bin&amp;rdquo; (the Fargate instance) is sized to match the pod&amp;rsquo;s requested resources. This means there&amp;rsquo;s no need for Kubernetes to optimize resource usage across a pool of shared nodes, as each pod effectively has its own &amp;ldquo;container&amp;rdquo; provided by Fargate that fits it&amp;rsquo;s needs precisely.&lt;/p&gt;
&lt;p&gt;In other words, AWS effectively pushes this bin-packing responsibility to itself, so you don&amp;rsquo;t have to worry ( as much ) about it.&lt;/p&gt;
&lt;p&gt;&lt;img src="faregate-pods.png" alt="Diagram showing AWS Fargate with one Kubernetes pod per isolated node, illustrating 1:1 pod-to-node mapping."&gt;&lt;/p&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;While Fargate abstracts away node management, it&amp;rsquo;s important to understand that Fargate pods &lt;a href="https://justingarrison.com/blog/2024-02-08-fargate-is-not-firecracker/"&gt;still run on EC2 instances&lt;/a&gt; behind the scenes. And each Fargate pod gets its own ENI (Elastic Network Interface) that can sometimes limit your scaling because pods aren&amp;rsquo;t sharing a network namespace.&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="pros-and-cons"&gt;Pros and Cons&lt;/h2&gt;
&lt;p&gt;So yeah, Fargate abstracts away the complexity of managing nodes and scaling clusters by shifting bin-packing to AWS. With its extensive infrastructure, AWS is arguably better positioned to optimize placements at scale. They can monitor their entire cloud landscape, placing Fargate instances dynamically across available resources. This allows them to potentially leverage underutilized resources in ways that would be challenging or impractical at an individual account&amp;rsquo;s scale.&lt;/p&gt;
&lt;div&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;th&gt;EC2&lt;/th&gt;
&lt;th&gt;Fargate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Instance Types&lt;/td&gt;
&lt;td&gt;You pick size and provision nodes&lt;/td&gt;
&lt;td&gt;AWS provisions per pod request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pod Isolation&lt;/td&gt;
&lt;td&gt;You prevent noisy neighbors&lt;/td&gt;
&lt;td&gt;AWS isolates each pod&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resource Allocation&lt;/td&gt;
&lt;td&gt;You find space on nodes&lt;/td&gt;
&lt;td&gt;AWS creates dedicated space&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;span style="text-align:center"&gt;
&lt;figcaption&gt;
&lt;i&gt;Customer vs AWS Responsibility EC2 vs Fargate&lt;/i&gt;
&lt;/figcaption&gt;
&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;Ideally, this frees you to focus on the services rather than on the underlying infrastructure, but in practice, it&amp;rsquo;s a bit more complex.&lt;/p&gt;
&lt;p&gt;Since each pod is treated as a standalone instance, you lose out on the cost efficiencies of co-locating multiple pods on a single node. Even though AWS is &amp;ldquo;solving&amp;rdquo; the bin-packing problem globally, to do so, they need to introduce hard barriers, and while noisy neighbors, therefore, aren&amp;rsquo;t a problem, sharing resources is now impossible. In a way, you are throwing away the run time benefit of containers, the shared kernel virtualization, for something more like VMs. And just like VMs, Fargate pods take longer to start than pods on existing EC2 nodes (typically 30-60 seconds vs near-immediate starts).&lt;/p&gt;
&lt;h3 id="a-note-on-scaling"&gt;A Note on Scaling&lt;/h3&gt;
&lt;p&gt;Both EC2 and Fargate work with Kubernetes&amp;rsquo; Horizontal Pod Autoscaling (HPA), which adds or removes pods based on metrics like CPU usage. The difference? With EC2, you need enough nodes available to handle new pods. With Fargate, AWS automatically provisions resources for each new pod - though you&amp;rsquo;ll face that 30-60 second startup delay and potential VPC limits since each pod needs its own network interface.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Fargate&lt;/th&gt;
&lt;th&gt;EC2&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;Higher per pod&lt;/td&gt;
&lt;td&gt;Lower cost per pod&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scalability&lt;/td&gt;
&lt;td&gt;Scales on-demand, isolated resources&lt;/td&gt;
&lt;td&gt;Requires manual node scaling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resource Sharing&lt;/td&gt;
&lt;td&gt;Not possible&lt;/td&gt;
&lt;td&gt;Efficient resource sharing among pods&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Startup Time&lt;/td&gt;
&lt;td&gt;30-60 seconds&lt;/td&gt;
&lt;td&gt;Near instant on existing nodes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use Cases&lt;/td&gt;
&lt;td&gt;Burst workloads, isolated pods&lt;/td&gt;
&lt;td&gt;Cost-sensitive workloads, Resource sharing workloads&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Maybe this will make more sense with an example.&lt;/p&gt;
&lt;h2 id="workload-example-static-analysis"&gt;Workload Example: Static Analysis&lt;/h2&gt;
&lt;img src="noisy.webp" alt="sketch of sleeping while noisy neighbor" style="width: 50%; float: left; margin-right: 10px;"&gt;
&lt;p&gt;Have you ever had to run CPU-intensive batch jobs alongside lighter web services?&lt;/p&gt;
&lt;p&gt;In a previous role, when my team and I were tasked with putting our static container image analyzer into production, we went the typical Kubernetes route, throwing it into an EC2-backed K8s cluster. The analyzer was built in Scala, running on the JVM, and was designed to take in work from a Kafka topic. Each analysis ultimately led to the results being sent to a database. We also had scheduled daily rescans of existing images whenever vulnerability lists were updated.&lt;/p&gt;
&lt;p&gt;Then, a web app connected to this database and showed all your results. It was stateless and did far less work per request, mainly just answering an HTTP request by talking to a database.&lt;/p&gt;
&lt;p&gt;The hard part though is that each analysis job involved uncompressing and analyzing Docker layers from S3 and could use a lot of CPU. Memory needs were around 2 GB per instance, which kept the JVM happy, but long sustained bursts in CPU usage were common, especially during intensive parts of the analysis.&lt;/p&gt;
&lt;p&gt;In our K8s setup, the biggest pain point was handling this analysis workload&amp;rsquo;s burstiness and keeping a bunch of stateless web apps up and answering requests. We ran multiple instances of the static analyzer. Still, the analyzer was the worst type of noisy neighbor to other services in the cluster, so we&amp;rsquo;d end up over-provisioning to ensure we had enough headroom, which led to wasted resources and extra cost. The setup worked, but was not great.&lt;/p&gt;
&lt;p&gt;Thinking back, I can see how Fargate could&amp;rsquo;ve streamlined things here. With Fargate, each static analyzer instance would run in its own isolated environment, so there&amp;rsquo;d be no resource contention or need for pre-provisioned node capacity. Each pod would be fully provisioned with the exact CPU and memory needed, and we could do some sort of on-demand scaling to deal with backlogs of analysis work in the Kafka topic.&lt;/p&gt;
&lt;p&gt;Fargate on EKS could be a great solution for this type of chunky, resource-heavy macro-services. There are probably other ways to handle this situation, and we worked on decomposing the analysis into smaller chunks, but Fargate might have been a good solution.&lt;/p&gt;
&lt;p&gt;But there are lots of situations where Fargate is less of a fit.&lt;/p&gt;
&lt;div class="note note-tip"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-lightbulb-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;
&lt;p&gt;&lt;strong&gt;You might also like:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.pulumi.com/blog/pulumi-for-aws-automate-secure-manage/"&gt;
Pulumi for AWS: Automate, Secure, and Manage Your Cloud
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.pulumi.com/blog/easy-ai-apps-with-langserve-and-pulumi/"&gt;
Deploy LangServe Apps with Pulumi on AWS (RAG &amp;amp; Chatbot)
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.pulumi.com/blog/advanced-aws-networking-part-2/"&gt;
Advanced AWS Networking: Part 2
&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="example-go-services-for-e-commerce"&gt;Example: Go Services for E-commerce&lt;/h2&gt;
&lt;img src="cash-register.webp" alt="sketch of fog clearing" style="width: 30%; float: right; margin-left: 10px;"&gt;
&lt;p&gt;Ok, another workload that comes to mind is this one from a friend - He worked on an e-commerce backend with many lightweight Go services, each handling specific tasks—user profiles, catalog browsing, order processing, and notifications. These services were network-heavy, handling gRPC calls and database requests, but they didn&amp;rsquo;t need much CPU or memory per instance.&lt;/p&gt;
&lt;p&gt;Fargate wouldn&amp;rsquo;t have made sense here—it would have isolated each pod, preventing resource sharing and driving up costs without real benefit. For lightweight services like these, EC2-backed Kubernetes is perfect, keeping costs low and maximizing resources by sharing nodes across small, efficient services.&lt;/p&gt;
&lt;p&gt;So, for lightweight micro-services that can share resources, EC2 nodes with many pods each are probably the way to go.&lt;/p&gt;
&lt;h3 id="other-fargate-cases"&gt;Other Fargate Cases&lt;/h3&gt;
&lt;p&gt;Fargate can excel with long-running event-driven and bursty workloads, where you need compute resources briefly or unpredictably. Think file processing jobs that trigger on uploads, or batch processing that runs sporadically. When jobs are intermittent, Fargate&amp;rsquo;s ability to scale from zero and its isolated resources can outweigh its higher per-pod cost and startup delay. Just ensure your workload can tolerate the 30-60 second cold start.&lt;/p&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;p&gt;Fargate has some hard limitations that might rule it out for your use case:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No DaemonSets&lt;/li&gt;
&lt;li&gt;Maximum of 4 vCPU and 30GB memory per pod&lt;/li&gt;
&lt;li&gt;Storage limitations: Only ephemeral storage up to 20GB per pod, no persistent volumes&lt;/li&gt;
&lt;li&gt;No GPU support&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="fargate-vs-ec2-pricing"&gt;Fargate vs EC2 Pricing&lt;/h2&gt;
&lt;p&gt;Ok, EC2 costs less than Fargate on a pure cost-of-compute basis. If my t3.medium ($0.0416/hr) can typically run 6-8 pods reliably, and this works out to under $0.01/hr per pod. Comparable Fargate pods (0.5 vCPU/1GB) cost about $0.025/hr each, so 2.5x.&lt;/p&gt;
&lt;p&gt;These calculations can change based on how much headroom you need on EC2 and how many containers you can run on a given EC2 instance. Still, Fargate is just more expensive unless all your containers fit the exact computing sizes offered by Fargate. And don&amp;rsquo;t forget, AWS is likely incurring costs maintaining the warm compute capacity for launching Fargate pods, so it shouldn&amp;rsquo;t be surprising that Fargate is more expensive.&lt;/p&gt;
&lt;p&gt;But really, pure compute cost calculations are an insufficient metric for making this decision - you need to factor in your team&amp;rsquo;s operational capacity, scaling patterns, and isolation requirements.&lt;/p&gt;
&lt;p&gt;( Also, if Fargate makes sense because of your workload but the cost is a concern, then look into Fargate Spot instances, which can be up to 70% cheaper, but be aware that AWS can terminate them at any point to reclaim capacity. )&lt;/p&gt;
&lt;h2 id="misconceptions-about-fargate"&gt;Misconceptions About Fargate&lt;/h2&gt;
&lt;img src="insight.webp" alt="sketch of fog clearing" style="width: 50%; float: right; margin-left: 10px;"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fargate saves you from managing servers&lt;/strong&gt;
With containerized workloads and immutable infrastructure, your EC2 nodes are minimal, standardized, and automatically replaced. You&amp;rsquo;re not logging in, installing packages, or managing configuration - everything runs in containers. When a node needs updates, you replace it entirely.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fargate handles all scaling challenges&lt;/strong&gt;
While it handles node scaling, you still face ENI limits, longer pod startup times (30-60s), and potential networking bottlenecks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It&amp;rsquo;s simpler and more reliable&lt;/strong&gt;
It may be, but it may not be. The complexity shifts to networking, pod configuration, and working within Fargate&amp;rsquo;s constraints. You trade node management for new operational challenges like slower startups and possible networking complexity.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="managing-container-orchestration-with-pulumi"&gt;Managing Container Orchestration with Pulumi&lt;/h2&gt;
&lt;p&gt;Whether you choose EC2 or Fargate, Pulumi makes it easy to manage in your favorite programming languages. With its rich AWS support and infrastructure as code capabilities, you can define, deploy, and manage your entire Kubernetes infrastructure using familiar tools and practices. Pulumi&amp;rsquo;s &lt;a href="https://www.pulumi.com/docs/iac/clouds/aws/"&gt;AWS provider&lt;/a&gt; helps you implement the right solution for your needs.&lt;/p&gt;
&lt;p&gt;With Pulumi, you get:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/iac/clouds/aws/guides/eks/"&gt;Complete control over EKS cluster configuration&lt;/a&gt;, including node types, scaling policies, and networking setup&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/iac/clouds/kubernetes/guides/worker-nodes/"&gt;Built-in support for both EC2 node groups and Fargate profiles&lt;/a&gt; within the same cluster&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/iac/concepts/testing/"&gt;Infrastructure testing capabilities&lt;/a&gt; to validate your cluster configuration before deployment&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/automation-api/"&gt;Automation APIs&lt;/a&gt; for programmatic infrastructure management and custom tooling&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ready to streamline your EKS infrastructure management?&lt;/p&gt;
&lt;a
href="https://www.pulumi.com/docs/get-started/"
class="btn btn-primary"
&gt;
Try Pulumi for Free
&lt;/a&gt;
&lt;h2 id="why-not-both"&gt;Why Not Both&lt;/h2&gt;
&lt;p&gt;I hope my examples point to a pragmatic approach: use EC2 as your foundation for efficient microservices that can share resources, like our Go e-commerce setup. Add Fargate when you need isolation or flexible scaling - like for the static analysis workload. Better yet, combine both approaches in the same cluster, for cost-efficiency and isolation where needed.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re interested in using Pulumi to manage EKS infra, here&amp;rsquo;s a &lt;a href="https://www.pulumi.com/docs/iac/clouds/aws/guides/eks/"&gt;practical EKS setup guide&lt;/a&gt; guide to get your started.&lt;/p&gt;
&lt;p&gt;Now, back to getting this demo cluster up and working!&lt;/p&gt;</description><author>Adam Gordon Bell</author><category>aws</category><category>eks</category><category>ec2</category><category>kubernetes</category></item><item><title>When to Use Cosmos DB</title><link>https://www.pulumi.com/blog/when-to-use-azure-cosmos-db/</link><pubDate>Mon, 11 Nov 2024 17:39:54 -0500</pubDate><guid>https://www.pulumi.com/blog/when-to-use-azure-cosmos-db/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/when-to-use-azure-cosmos-db/index.png" /&gt;
&lt;p&gt;When should you use Cosmos DB on Azure? It&amp;rsquo;s a hard question to answer.&lt;/p&gt;
&lt;p&gt;Azure Cosmos DB tries to do it all - which makes it both powerful and, honestly, kind of confusing. The marketing doesn&amp;rsquo;t help either. Yeah, it&amp;rsquo;s a flexible database, but before you jump in, you need to understand how it actually works under the hood, what it&amp;rsquo;ll cost you, and what trade-offs you&amp;rsquo;re making by choosing it.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m going to break all that down. Here&amp;rsquo;s a roadmap:&lt;/p&gt;
&lt;p&gt;&lt;img src="roadmap.png" alt="Roadmap depicting stages when to use Azure Cosmos DB in comnparison to relational, Mongo, Cassandra, and Dynamo databases, in addition to recommendations"&gt;&lt;/p&gt;
&lt;h2 id="tldr"&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;The short version is: &lt;strong&gt;you should use it on Azure when you&amp;rsquo;d use DynamoDB on AWS.&lt;/strong&gt; That&amp;rsquo;s to say, you should use it when you&amp;rsquo;re OK with being cloud-specific, when you want hands-off scalability, and when you know the query pattern and potential cost ramifications ahead of time.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/XcB1Prpx-OE?rel=0?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;But let&amp;rsquo;s start at the beginning.&lt;/p&gt;
&lt;h2 id="what-is-cosmos-db-features--architecture-explained"&gt;What is Cosmos DB? Features &amp;amp; Architecture Explained&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://azure.microsoft.com/en-us/products/cosmos-db"&gt;Cosmos DB&lt;/a&gt; is Microsoft high availability distributed database. First released as DocumentDB in 2014 and rebranded to Cosmos DB in 2017, its used internally at MS for Bing and Office 365 and available on Azure in all regions.&lt;/p&gt;
&lt;h2 id="types-of-databases"&gt;Types Of Databases&lt;/h2&gt;
&lt;p&gt;Different databases are good at different things. Relational databases focus on strong consistency and complex relationships. Key-value stores are optimized for speed and simplicity. Document databases trade schema rigidity for flexibility. NewSQL systems attempt to combine consistency and scale.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Relational&lt;/td&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;td&gt;Traditional business applications with complex transactions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Key-Value&lt;/td&gt;
&lt;td&gt;Redis&lt;/td&gt;
&lt;td&gt;Caching and session management&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Document&lt;/td&gt;
&lt;td&gt;MongoDB&lt;/td&gt;
&lt;td&gt;Applications with evolving data structures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NewSQL&lt;/td&gt;
&lt;td&gt;Spanner, CockroachDB&lt;/td&gt;
&lt;td&gt;Global applications requiring both scale and consistency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dynamo-Inspired&lt;/td&gt;
&lt;td&gt;DynamoDB, Cassandra&lt;/td&gt;
&lt;td&gt;High write throughput, eventually consistent applications&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Cosmos DB can look like each of those categories. This is because it&amp;rsquo;s a multi-model DB, with a number of APIs that correspond to each model type. Despite it having a SQL interface (often recognized as the most used and recommended interface), and despite it purporting to be multi-model, at its lowest layer, it is actually backed by a document-based storage layer. It is a powerful document database, one with tunable consistency, distribution by logical and physical partition, and more, but it&amp;rsquo;s helpful to know how the storage layer works to understand everything else. I&amp;rsquo;m going to show some examples, but first let&amp;rsquo;s clear up what Cosmos DB is not.&lt;/p&gt;
&lt;p&gt;&lt;img src="apis.png" alt="Diagram showing Azure Cosmos DB’s multi-model architecture. SQL, MQL, and CQL interact via NoSQL, MongoDB, and Cassandra APIs, converting data into JSON for storage in DocumentDB’s JSON-based ARS format."&gt;&lt;/p&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;If you are going to use Cosmos DB, you should probably be using the Core API, also called the &amp;ldquo;API for NoSQL&amp;rdquo;. It&amp;rsquo;s the quickest to get new features. Confusingly, despite being called a NoSQL API, data is queried and added using SQL.&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="cosmos-db-vs-sql-server--azure-sql-database--or-postgres"&gt;Cosmos DB Vs. SQL Server ( Azure SQL Database ) or Postgres&lt;/h2&gt;
&lt;p&gt;This is a personal opinion, but if you have a use case that is well served by a traditional relational database, you should choose that. Postgres and SQL Server are both great and both available on Azure ( Postgres as Azure Database for PostgreSQL ). They both have great query planners; they have great tooling for migration and backups, as well as schema management. The SQL API from Cosmos should &lt;strong&gt;not&lt;/strong&gt; be considered an alternative to a relational database unless you have specific needs because it&amp;rsquo;s a different type of beast designed for different use cases. But let me show you.&lt;/p&gt;
&lt;p&gt;If you grab our &lt;a href="https://www.pulumi.com/registry/packages/azure-native/how-to-guides/azure-cs-cosmosdb-logicapp/"&gt;CosmoDB how to guide&lt;/a&gt; and &lt;code&gt;pulumi up&lt;/code&gt;, you&amp;rsquo;ll see from that, the first hint of its &lt;a href="https://github.com/pulumi/examples/blob/master/azure-cs-cosmosdb-logicapp/MyStack.cs#L6"&gt;document roots&lt;/a&gt; in that the pulumi namespace is &lt;code&gt;Pulumi.AzureNative.DocumentDB&lt;/code&gt;, but it goes deeper because although I can insert and select with sql in Cosmos DB, there is no schema.&lt;/p&gt;
&lt;h3 id="how-to-insert--query-data-in-cosmos-db-nosql"&gt;How to Insert &amp;amp; Query Data in Cosmos DB NoSQL&lt;/h3&gt;
&lt;p&gt;If I insert this customer:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;CosmosDatabase&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;CreateDatabaseIfNotExistsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;databaseId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;CosmosContainer&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;CreateContainerIfNotExistsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;containerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;/customerId&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Insert a customer document&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Customer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewGuid&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;CustomerId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;12345&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Cosmos DB Partition key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;John Doe&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Email&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;john.doe@example.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;InsertCustomer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I can do selects against it like &lt;code&gt;SELECT * FROM c WHERE c.customerId = '12345&amp;quot;&lt;/code&gt; but when I look in that container in Azure Data Explorer, I see something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;1d4e26b3-7a02-4e97-8a9d-eccc0b2fd537&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s"&gt;&amp;#34;customerId&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;12345&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;John Doe&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s"&gt;&amp;#34;email&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;john.doe@example.com&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s"&gt;&amp;#34;_rid&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;&amp;lt;resource id&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s"&gt;&amp;#34;_self&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;&amp;lt;document URI&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s"&gt;&amp;#34;_etag&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;\&amp;#34;00000000-0000-0000-0000-000000000000\&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s"&gt;&amp;#34;_attachments&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;attachments/&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s"&gt;&amp;#34;_ts&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1609459200&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As with any document database, rather than a schema, it&amp;rsquo;s just a JSON document. That&amp;rsquo;s all good, but because there is no schema, if I needed to do something like migrate &lt;code&gt;name&lt;/code&gt; to &lt;code&gt;fullname&lt;/code&gt;, how do I do it? Well, there are a bunch of strategies, but they all involve writing some application code to go record by record and move the value from &lt;code&gt;name&lt;/code&gt; to &lt;code&gt;full name&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So don&amp;rsquo;t be fooled by the SQL usage in the API. Cosmos DB is not a relational database; before you use a document database, you should be clear of the trade-offs.&lt;/p&gt;
&lt;h2 id="document-database--nosql-trade-offs"&gt;Document Database / NoSQL Trade-Offs&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Joins and Relationships&lt;/strong&gt;: Cosmos DB lacks support for complex joins and foreign key constraints. Cosmos DB typically requires denormalization or application-level management of relationships.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ACID Transactions and Consistency&lt;/strong&gt;: Cosmos DB supports ACID transactions but only within single partitions (more on partitions soon). Cross-partition operations in Cosmos DB may require manual consistency handling.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Schema Evolution and Migrations&lt;/strong&gt;: Relational databases enforce a schema and support migration tools. Cosmos DB&amp;rsquo;s schemaless design offers flexibility but requires custom logic for data migrations, which can increase operational overhead with frequent schema changes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Query Optimization and Indexing&lt;/strong&gt;: Postgres, SQL Server, and any relational database will have a query optimization layer with a sophisticated planner and manual indexing control. Cosmos DB auto-indexes all fields, simplifying queries but potentially increasing costs. Custom indexing policies offer less control than you would see in SQL Server or Postgres.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cost Structure&lt;/strong&gt;: Relational database costs are tied to compute and storage resources. Scaling might be a challenge past some point, but costs are fixed. Cosmos DB uses Request Units (RUs) for billing and charging per read, write, and query. This can lead to unpredictable costs if not carefully tuned.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Operational Maturity&lt;/strong&gt;: Relational databases are mature with mature solutions for backup, replication, and monitoring. Cosmos DB is fully managed but has fewer third-party tools. Postgres or another relational DB may be easier to integrate into existing workflows requiring mature operational support.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If your use case aligns well with relational databases, consider using PostgreSQL or SQL Server. You have multiple Azure options, including Azure Database for PostgreSQL and Azure SQL Database. There&amp;rsquo;s even Azure Cosmos DB for PostgreSQL, which confusingly uses native PostgreSQL combined with the Citus extension for distributed capabilities. It&amp;rsquo;s &lt;strong&gt;not&lt;/strong&gt; based on Cosmos DB&amp;rsquo;s NoSQL DocumentDB storage layer and is a Postgres database.&lt;/p&gt;
&lt;p&gt;Cosmos DB is best for other use cases, so on we go to &amp;lsquo;NoSQL&amp;rsquo; document databases.&lt;/p&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;Azure offers various services under the &amp;ldquo;Cosmos DB&amp;rdquo; brand like Azure Cosmos DB for PostgreSQL, Azure Cosmos DB for MongoDB (VCore), and a managed instance for Cassandra. These are &lt;em&gt;not&lt;/em&gt; based on the core Cosmos DB engine, they are not an API into Cosmos, they are a whole different things - hosted instances of specific database types.&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="cosmos-db-vs-mongodb"&gt;Cosmos DB Vs MongoDB&lt;/h2&gt;
&lt;p&gt;So, let&amp;rsquo;s talk MongoDB then. Cosmos DB is multi-model in a specific way. It has a single storage layer, the DocumentDB, and then various APIs. One of these is a MongoDB interface. This Mongo interface, on top of Cosmos DB, might be a great choice if you are used to Mongo, because you can often just change your query string on your existing mongodb code and start using cosmos.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;MongoDB.Driver&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;MongoDB.Bson&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Connect to Cosmos DB using MongoDB API&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;MongoClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;mongodb://&amp;lt;account&amp;gt;:&amp;lt;key&amp;gt;@&amp;lt;account&amp;gt;.mongo.cosmos.azure.com:10255/?ssl=true&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;customersdb&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetCollection&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BsonDocument&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;customers&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;docs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;BsonDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;customerId&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;12345&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That will totally work. You can query using MQL, assuming you avoid Mongo&amp;rsquo;s advanced features. Still, you will quickly need to understand Cosmos-specific things, like RUs and partitions, or else you might end up with unexpected performance or costs.&lt;/p&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;Reddit Azure users report some challenges with using the Mongo API, and Microsoft also recommends using the SQL-based &amp;ldquo;API for NoSQL&amp;rdquo; if you can. So Mongo fans may want to consider not choosing Cosmos&amp;rsquo; Mongo API.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;However, the big advantage of Cosmos over Mongo is distribution and scaling up. But to understand that, we need to explain partitions.&lt;/p&gt;
&lt;p&gt;With MongoDB, you&amp;rsquo;re setting up shards, configuring keys, and managing replica sets. Sure, you get control, but you&amp;rsquo;re also the one staying up late managing infrastructure as your data grows.&lt;/p&gt;
&lt;p&gt;In Cosmos DB, scaling and partitioning are more hands-off – you just have to enable &amp;lsquo;automatic scaling&amp;rsquo; – but you need the correct partition key from the start. So you need to understand your access patterns and be aware that &amp;lsquo;automatic scaling&amp;rsquo; does what it says, but costs will increase as you scale.&lt;/p&gt;
&lt;h2 id="how-cosmos-db-partitions--scales-data-efficiently"&gt;How Cosmos DB Partitions &amp;amp; Scales Data Efficiently&lt;/h2&gt;
&lt;p&gt;&lt;img src="shards.png" alt="Diagram comparing MongoDB sharding and Cosmos DB partitioning. MongoDB uses config servers and a Mongos router to distribute data across shards with replica sets, while Cosmos DB partitions data into logical partitions mapped to physical partitions via its managed service."&gt;&lt;/p&gt;
&lt;p&gt;In MongoDB, a shard is a collection of data stored on a specific server, used to distribute data across multiple servers for horizontal scaling. Partitioning is really an improvement on this idea. Cosmos separates partitions into logical and physical partitions. You choose the logical partitions only and leave the DB to decide the best way to group these onto specific physical servers. The physical groupings are physical partitions, and the system manages scaling by handling data distribution across physical partitions without user intervention.&lt;/p&gt;
&lt;h3 id="cosmos-db-partitioning"&gt;Cosmos DB Partitioning&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Logical Partitions:&lt;/strong&gt; Like MongoDB&amp;rsquo;s shards, logical partitions group documents with the same partition key. However, each logical partition can store up to 20 GB of data and is automatically managed by Cosmos DB.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Physical Partitions:&lt;/strong&gt; Cosmos DB hides the complexity of managing the physical servers. Physical partitions store and serve one or more logical partitions, and Cosmos DB handles how data is distributed across physical partitions for you. This allows Cosmos DB to automatically scale and balance the load as needed.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="comparison-to-mongodb"&gt;Comparison To MongoDB&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Feature&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;MongoDB Sharding&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Cosmos DB Partitioning&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data Distribution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual sharding with shard key and replica sets&lt;/td&gt;
&lt;td&gt;Automatic partitioning with partition key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scaling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual scaling (add shards)&lt;/td&gt;
&lt;td&gt;Automatic scaling (Azure-managed)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Operational Complexity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Requires manual setup and maintenance&lt;/td&gt;
&lt;td&gt;Minimal operational overhead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Global Distribution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Requires manual setup of replica sets&lt;/td&gt;
&lt;td&gt;Built-in global distribution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Consistency Models&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Eventual (with strong for replica sets)&lt;/td&gt;
&lt;td&gt;5 consistency models, including strong&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Query Performance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Requires index management, can result in scatter-gather queries&lt;/td&gt;
&lt;td&gt;Auto-indexing, but cross-partition queries can be costly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost Model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Based on instance, storage, and IO usage&lt;/td&gt;
&lt;td&gt;Request Units (RUs) per operation, with potential for high costs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="mongodb-vs-cosmos-db-cost-comparison"&gt;MongoDB Vs. Cosmos DB Cost Comparison&lt;/h2&gt;
&lt;p&gt;Your exact numbers may vary, but by my back-of-the-napkin math, Cosmos DB is cheaper more than MongoDB Atlas for similar workloads. It also has SLAs on read speed and scaling is much easier.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Service&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Cost (500 reads/sec, 50 writes/sec, 10 KB document)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Pricing Model&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MongoDB Atlas&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~$387.62/month&lt;/td&gt;
&lt;td&gt;Based on instance size (M30), fixed pricing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cosmos DB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~$227.28/month&lt;/td&gt;
&lt;td&gt;Request Units (RUs) based, scales with document size and operations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;See also &lt;a href="https://www.pulumi.com/what-is/cosmos-db-vs-mongodb-know-the-differences/"&gt;Cosmos DB vs MongoDB, Know The Differences&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="cosmos-db-vs-cassandra"&gt;Cosmos DB Vs Cassandra&lt;/h2&gt;
&lt;figure&gt;&lt;img src="https://www.pulumi.com/blog/when-to-use-azure-cosmos-db/cassandra.png" width="75%"&gt;
&lt;/figure&gt;
&lt;p&gt;After learning about partitioning and seeing the Cassandra API, you might think Cosmos DB is basically managed by Cassandra. And there are some key similarities. In many ways, Cosmos DB is Cassandra-inspired, but there are key differences.&lt;/p&gt;
&lt;div class="note note-tip"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-lightbulb-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;
&lt;p&gt;&lt;strong&gt;You might also like:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.pulumi.com/blog/azure-deployment-environments/"&gt;
Pulumi &amp;#43; Azure Deployment Environments: Better Together for Enterprise Developers
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.pulumi.com/blog/introducing-azure-native-v2/"&gt;
Azure Native Provider 2.0: Streamlined, Expanded, and More Powerful than Ever
&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 id="how-cosmos-db-and-cassandra-relate"&gt;How Cosmos DB and Cassandra Relate&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Partitioning&lt;/strong&gt;: Both use logical partition keys for scalability to distribute data across nodes. Cassandra requires manual management, while Cosmos DB automates partitioning and scaling, reducing operational burdens, though at the cost of flexibility.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tunable Consistency&lt;/strong&gt;: Like Cassandra, Cosmos DB offers tunable consistency but expands on it with five levels: Strong, Bounded Staleness, Session, Consistent Prefix, and Eventual—allowing finer control over consistency vs. performance.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="key-differences"&gt;Key Differences&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cassandra API&lt;/strong&gt;: Though Cosmos DB offers a Cassandra API, it&amp;rsquo;s not running Cassandra under the hood. So, while the API is familiar, the system behaves differently, especially in terms of performance.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Operational Complexity&lt;/strong&gt;: Cassandra gives complete control over nodes and replication but requires hands-on management. Cosmos DB automates these tasks, offering hands-off operations while maintaining high availability, making it easier to manage at scale.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="api-for-apache-cassandra"&gt;API for Apache Cassandra&lt;/h3&gt;
&lt;p&gt;If you have existing Apache Cassandra usage, in theory, the API for Apache Cassandra lets you use CosmosDB equivalently by just changing the query string. As with the Mongo API above, if possible you are probably better to use Core NoSQL API, but that will required rewriting dataaccess code.&lt;/p&gt;
&lt;h3 id="cosmos-db-cql-querying"&gt;Cosmos DB CQL Querying&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Cassandra&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Connect to Cosmos DB&amp;#39;s Cassandra API&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithCredentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;lt;username&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;&amp;lt;password&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// From Azure Portal&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithPort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10350&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddContactPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;lt;account&amp;gt;.cassandra.cosmos.azure.com&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithSSL&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConnectAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cql&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;SELECT * FROM customersdb.customers WHERE customer_id = ?&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;12345&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$&amp;#34;Customer: {row[&amp;#34;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;]}&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="when-to-use-cassandra-vs-cosmos-db"&gt;When to Use Cassandra Vs Cosmos DB&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cassandra&lt;/strong&gt;: Use when you need control and can manage partitioning and scaling manually.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cosmos DB&lt;/strong&gt;: Use when you want hands-off scalability and a fully managed service with the option to use Cassandra-like APIs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So use Cassandra when you need complete control over partitioning and scaling and are prepared to manage infrastructure manually. Opt for Cosmos DB when you want a hands-off managed service that handles the operational complexity for you, even if you&amp;rsquo;re using the familiar Cassandra API.&lt;/p&gt;
&lt;h2 id="dynamo-db-vs-cosmos-db"&gt;Dynamo DB Vs Cosmos DB&lt;/h2&gt;
&lt;p&gt;After comparing Cosmos DB to Cassandra, it&amp;rsquo;s natural to think of DynamoDB next. Like Cosmos DB, DynamoDB is fully managed and Dynamo-inspired, but being part of AWS, it operates differently from both Cassandra and Cosmos DB, especially in terms of operational simplicity and pricing.&lt;/p&gt;
&lt;h3 id="managed-operations"&gt;Managed Operations&lt;/h3&gt;
&lt;p&gt;DynamoDB and Cosmos DB remove the ops overhead you&amp;rsquo;d get with Cassandra. If Cassandra gives you full control over nodes and partitioning, DynamoDB and Cosmos DB automate much of the complexity behind the scenes. With DynamoDB, you don&amp;rsquo;t have to manually manage shards or replication—AWS handles that for you, just like Cosmos DB on Azure.&lt;/p&gt;
&lt;p&gt;Both databases also rely heavily on partition keys for data distribution. In DynamoDB, choosing the right partition key is critical to avoid hot spots, just like you need a well-thought-out partition key in Cosmos DB to balance the load across physical partitions. But where DynamoDB allows you to toggle between on-demand and provisioned capacity to manage scaling, Cosmos DB simply auto-scales as your traffic grows, offering a more seamless experience if you&amp;rsquo;re willing to pay for it.&lt;/p&gt;
&lt;h3 id="cosmos-db-consistency-levels"&gt;Cosmos DB Consistency Levels&lt;/h3&gt;
&lt;p&gt;While Cassandra offers tunable consistency, DynamoDB simplifies this down to just two options: eventual consistency (default) or strong consistency. It&amp;rsquo;s enough for many use cases, but Cosmos DB follows Cassandra in covering many levels of consistency ranging from strong to eventual. One level, Session level consistency, seems both unique to Cosmos and valuable. It ensures you are always able to read your writes in the same session without impacting performance as much as higher consistency levels can. This makes Cosmos DB pretty unique if you to fine-tune consistency for globally distributed applications.&lt;/p&gt;
&lt;h3 id="cosmos-db-vs-dynamo-db-costs"&gt;Cosmos DB vs Dynamo DB Costs&lt;/h3&gt;
&lt;p&gt;Again, using some back-of-the-napkin math, DynamoDB in provisioned Mode is more cost-effective than Cosmos DB for the same workload with 10 kb document sizes, but the numbers could swing the other way based on your use case. This is with estimating 500 reads / second ( 400 point reads or get-time, 100 more complex queries) and 50 writes.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Service&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Workload&lt;/strong&gt; (500 reads/sec, 50 writes/sec)&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;3 KB Document Size&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;10 KB Document Size&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Key Difference&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DynamoDB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;500 reads/sec, 50 writes/sec&lt;/td&gt;
&lt;td&gt;$137.67/month&lt;/td&gt;
&lt;td&gt;$54.84/month&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cosmos DB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;500 reads/sec, 50 writes/sec&lt;/td&gt;
&lt;td&gt;$227.28/month&lt;/td&gt;
&lt;td&gt;$212.92/month&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;See also &lt;a href="https://www.pulumi.com/what-is/database-comparison-cosmos-db-vs-dynamodb/"&gt;Cosmos DB vs DynamoDB, Know The Differences&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="tangent-azure-table-storage-vs-cosmos-db"&gt;Tangent: Azure Table Storage vs. Cosmos DB&lt;/h2&gt;
&lt;p&gt;Cosmos DB also has a Azure Table API and marketing material saying it&amp;rsquo;s a better choice than Azure Table Storage. My take is Azure Table and Cosmos DB&amp;rsquo;s Table API both provide key-value storage with distinct trade-offs. Azure Table Storage is cost-effective and ideal for simpler, predictable workloads, requiring minimal complexity. Cosmos DB&amp;rsquo;s Table API adds flexibility, optional global distribution, and tunable consistency, but also brings more complexity and cost.&lt;/p&gt;
&lt;p&gt;While both offer similar functionality, Cosmos DB&amp;rsquo;s advanced features will cost you. If you know you aren&amp;rsquo;t going to need scale read and writes or need multi-region writes or other features, Azure Table Storage&amp;rsquo;s simpler, predictable pricing is the way to go.&lt;/p&gt;
&lt;div class="rounded-lg bg-violet-50 p-6 my-8"&gt;
&lt;p class="heading-4 m-0 mb-3 flex items-center gap-1.5"&gt;Provision Azure databases as code&lt;/p&gt;
&lt;div class="body-base m-0 text-gray-950"&gt;Whether you land on Cosmos DB, Azure SQL, or PostgreSQL, Pulumi lets you define and manage your entire Azure data layer in the language you already use.&lt;/div&gt;
&lt;a href="https://app.pulumi.com/signup" data-track="blog-body-cta" class="btn btn-primary mt-4"&gt;
Get started
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--regular size-4" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-arrow-right-regular"/&gt;&lt;/svg&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id="when-cosmos-db-makes-sense"&gt;When Cosmos DB Makes Sense&lt;/h2&gt;
&lt;p&gt;&lt;img src="pro-con.png" alt="Diagram outlining the pros and cons of Cosmos DB. Pros include scale, flexibility, and consistency, while cons include cost, tooling limitations, and vendor lock-in."&gt;&lt;/p&gt;
&lt;p&gt;OK, I think all these comparisons give us a solid grounding to talk about trade-offs. So, with all this in mind, when should you use Cosmos DB?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Document Database with Global Distribution&lt;/strong&gt;: Cosmos DB is a strong choice if you need a document database that supports global distribution with multiple consistency models. Its automatic scaling and geo-replication mean you can reach a global audience and a high level of throughput with minimal operational overhead.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hands-Off Scalability&lt;/strong&gt;: This came up again and again. When you want to focus more on development and less on database administration, Cosmos DB offers hands-off scalability. It automatically handles partitioning, indexing, and replication, which is great for teams that prefer highly managed services.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Schema Flexibility&lt;/strong&gt;: When your data structure is not fixed and might evolve over time, Cosmos DB&amp;rsquo;s schema-less nature is an advantage. This flexibility is helpful in cases where your application data needs to adapt frequently without the constraints of a rigid schema. Also, there is a taste aspect to this. While I like schemas and structure, some teams have extensive experience working with document-based NoSQL solutions, and they will find CosmosDB a great fit.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="when-to-avoid-cosmos-db"&gt;When to Avoid Cosmos DB&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cost Sensitivity&lt;/strong&gt;: Cosmos DB can be expensive, when you start working multi-region or start writing a lot of data per partition. If cost is a primary concern, especially for smaller projects, it might be better to consider alternatives.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Complex Joins and Transactions&lt;/strong&gt;: If your application requires complex joins, foreign key relationships, or cross-partition transactions, Cosmos DB might not be the best fit. A relational database like PostgreSQL or SQL Server would serve these requirements more effectively.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Predictable Workloads with Lower Complexity&lt;/strong&gt;: For applications that have predictable workloads and do not need the complexity of multi-region consistency or scaling to varying demand levels, a simpler solution like a traditional relational database might be more cost-effective and easier to manage.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Need for Strong Schema Enforcement&lt;/strong&gt;: Long-term, there is a lot to be said for having a scheme and an easy way to evolve your data. If your application benefits from strict schema enforcement to ensure data integrity and reduce developer errors, a relational database like PostgreSQL or SQL Server will be a better choice than Cosmos DB. Cosmos DB&amp;rsquo;s schema-less nature can introduce challenges if your data model needs strict consistency and structure.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Avoiding Vendor Lock-In&lt;/strong&gt;: If you want to maintain portability and avoid vendor lock-in, Cosmos DB might not be ideal for you. A highly managed database service, whether CosmosDB, Dynamo, or Spanner, will be hard to replace on another cloud vendor. A more portable database like PostgreSQL or Cassandra might be preferable for multi-cloud or cloud-agnostic strategies.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.21047dcd83825f0caafb78a6fd28628a694219e6b6824f6b3e24ee3147bac331.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;&lt;h2 id="more-reasons-for-cosmos-db--update-"&gt;More Reasons for Cosmos DB ( Update )&lt;/h2&gt;
&lt;p&gt;Since this article came out, I&amp;rsquo;ve had a chance to talk to several Cosmos DB users inside and outside Microsoft. I spoke with Mark Brown on the Cosmos DB team, and he highlighted two additional reasons why teams choose Cosmos DB, that I had originally under emphasized:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;High Availability:&lt;/strong&gt; Cosmos DB is designed for mission-critical applications requiring always-on availability. It&amp;rsquo;s the only Azure database with five 9s availability and active-active multi-region writes. This high availability is why services like Azure Active Directory and Teams use it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Guaranteed Low Latency:&lt;/strong&gt; Cosmos DB offers SLAs on read and write latency (&amp;lt;10ms P99), a guarantee few other databases can match. This speed is achieved by co-locating compute and storage on the same cluster, minimizing data access times.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="managing-database-infrastructure-with-pulumi"&gt;Managing Database Infrastructure with Pulumi&lt;/h2&gt;
&lt;p&gt;Whether you choose Cosmos DB, PostgreSQL, or Azure Table Storage Pulumi simplifies the process of provisioning and managing database infrastructure on Azure. With infrastructure as code in your favorite programming languages, you can define, deploy, and maintain your entire database infrastructure using familiar development practices. Pulumi&amp;rsquo;s &lt;a href="https://www.pulumi.com/docs/iac/clouds/aws/"&gt;Azure Native provider&lt;/a&gt; helps you implement the right database solution for your specific needs.&lt;/p&gt;
&lt;p&gt;With Pulumi, you get:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/iac/clouds/azure/"&gt;Rich Azure database provisioning&lt;/a&gt; capabilities for Cosmos DB, PostgreSQL, and Azure SQL&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/iac/concepts/config/"&gt;Automated configuration management&lt;/a&gt; to handle connection strings and secrets securely&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/iac/concepts/testing/"&gt;Infrastructure testing capabilities&lt;/a&gt; to validate your database configuration before deployment&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/concepts/stack/#stackreferences"&gt;Stack references&lt;/a&gt; for managing multiple database environments&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/concepts/resources/components/"&gt;Custom components&lt;/a&gt; for creating reusable database infrastructure patterns&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ready to streamline your database infrastructure management?&lt;/p&gt;
&lt;a
href="https://www.pulumi.com/docs/get-started/"
class="btn btn-primary"
&gt;
Try Pulumi for Free
&lt;/a&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;If you need something that is document-based, scales globally, and handles different consistency levels well, Cosmos DB could be a great choice - especially if you&amp;rsquo;re already using Azure. Azure takes it seriously - it&amp;rsquo;s in every data center they have, and they treat it as mission-critical. But here&amp;rsquo;s the thing - while it&amp;rsquo;s easier to manage than the alternatives we looked at, you&amp;rsquo;ll pay more for that convenience.&lt;/p&gt;
&lt;p&gt;Many times, that cost is not the variable being optimized for. Data is paramount, and Cosmos DB works well for many. James Thurley, Co-founder of Canopy Simulations, found it a perfect fit for their needs:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We&amp;rsquo;ve been using Cosmos DB as a document database since it was called DocumentDB, and it&amp;rsquo;s been fast, reliable, and straightforward for our needs. We store large, arbitrarily structured JSON documents, and Cosmos DB&amp;rsquo;s full indexing has handled that effortlessly. For us, the simplicity works—we don&amp;rsquo;t need to scale to millions of users, and with auto-scaling, each customer&amp;rsquo;s database runs smoothly without any partitioning.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For help provisioning Cosmos DB, check out our &lt;a href="https://www.pulumi.com/registry/packages/azure-native/how-to-guides/azure-cs-cosmosdb-logicapp/"&gt;Azure Cosmos DB How to guide&lt;/a&gt; and if you decide to go with Azure Tables instead, our &lt;a href="https://www.pulumi.com/registry/packages/azure-native/"&gt;Azure Native provider&lt;/a&gt; is there to help you all the same.&lt;/p&gt;
&lt;p&gt;And if you have a Cosmos DB experience to share ( or I got something wrong ): &lt;a href="https://www.pulumi.com/blog/author/adam-gordon-bell/"&gt;reach out&lt;/a&gt;. And, if you haven&amp;rsquo;t already, give Pulumi IaC a try:&lt;/p&gt;
&lt;section class="my-16"&gt;
&lt;div class="container mx-auto max-w-5xl md:flex my-8 align-top justify-center text-center"&gt;
&lt;div class="md:w3-/12 mr-4"&gt;
&lt;h2 class="no-anchor"&gt;Get Started with Pulumi&lt;/h2&gt;
&lt;p class="text-gray-700 dark:text-gray-300 text-sm"&gt;Use Pulumi's open-source SDK to create, deploy, and manage infrastructure on any cloud.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="mx-auto max-w-4xl"&gt;
&lt;div class="tiles flex-wrap mt-4"&gt;
&lt;div class="pb-4 md:pr-4 md:w-1/2"&gt;
&lt;a class="tile p-8" href="https://www.pulumi.com/docs/iac/get-started/aws/"&gt;
&lt;img class="h-10 mx-auto" src="https://www.pulumi.com/logos/tech/aws.svg" alt="AWS" /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;div class="pb-4 md:w-1/2"&gt;
&lt;a class="tile p-8" href="https://www.pulumi.com/docs/iac/get-started/azure/"&gt;
&lt;img class="h-10 mx-auto" src="https://www.pulumi.com/logos/tech/azure.svg" alt="Azure" /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;div class="pb-4 md:pr-4 md:w-1/2"&gt;
&lt;a class="tile p-8" href="https://www.pulumi.com/docs/iac/get-started/gcp/"&gt;
&lt;img class="h-10 mx-auto" src="https://www.pulumi.com/logos/tech/gcp.svg" alt="Google Cloud" /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;div class="pb-4 md:w-1/2"&gt;
&lt;a class="tile p-8" href="https://www.pulumi.com/docs/iac/get-started/kubernetes/"&gt;
&lt;img class="h-10 mx-auto" src="https://www.pulumi.com/logos/tech/k8s.svg" alt="Kubernetes" /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;</description><author>Adam Gordon Bell</author><category>azure</category></item></channel></rss>