<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Michael Zion</title>
    <description>The latest articles on DEV Community by Michael Zion (@michaelzion).</description>
    <link>https://dev.to/michaelzion</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1190304%2F718d0fd6-e684-47a6-8457-813673cdb806.jpeg</url>
      <title>DEV Community: Michael Zion</title>
      <link>https://dev.to/michaelzion</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/michaelzion"/>
    <language>en</language>
    <item>
      <title>Kubernetes HPA: How It Works and How to Stop Flapping</title>
      <dc:creator>Michael Zion</dc:creator>
      <pubDate>Mon, 02 Nov 2026 07:30:09 +0000</pubDate>
      <link>https://dev.to/meteorops/kubernetes-hpa-how-it-works-and-how-to-stop-flapping-1cmm</link>
      <guid>https://dev.to/meteorops/kubernetes-hpa-how-it-works-and-how-to-stop-flapping-1cmm</guid>
      <description>&lt;p&gt;&lt;em&gt;This guide was written by &lt;a href="https://meteorops.com/team/arthur-azrieli" rel="noopener noreferrer"&gt;Arthur Azrieli&lt;/a&gt;, CTO at MeteorOps, and first published on the &lt;a href="https://meteorops.com/blog/how-to-configure-kubernetes-hpa-without-causing-scaling-flaps" rel="noopener noreferrer"&gt;MeteorOps blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;The Kubernetes Horizontal Pod Autoscaler (HPA) adds or removes pod replicas for a Deployment, StatefulSet or other scalable workload based on a metric such as CPU utilization. Every 15 seconds it compares the current metric to your target, computes &lt;code&gt;ceil(currentReplicas × current / target)&lt;/code&gt;, and scales within your &lt;code&gt;minReplicas&lt;/code&gt; and &lt;code&gt;maxReplicas&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is the whole idea. Most of the trouble comes later, when replica counts start bouncing up and down, targets show &lt;code&gt;&amp;amp;lt;unknown&amp;amp;gt;&lt;/code&gt;, or a GitOps tool keeps resetting the replica count. This guide covers how the algorithm really behaves (including the parts most tutorials skip), copy-paste manifests for the common cases, the &lt;code&gt;behavior&lt;/code&gt; settings that stop scaling flaps, how HPA fits with VPA, KEDA, Karpenter and Argo Rollouts, and a troubleshooting table built from the actual controller error messages.&lt;/p&gt;

&lt;p&gt;Versions referenced: Kubernetes 1.37 (the current release in October 2026), metrics-server v0.9.0 and KEDA v2.21. The &lt;code&gt;autoscaling/v2&lt;/code&gt; API used throughout has been stable since Kubernetes 1.23, so everything here works on any supported cluster unless a section says otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kubernetes autoscalers compared: HPA, VPA, KEDA and node autoscalers
&lt;/h2&gt;

&lt;p&gt;"Kubernetes autoscaling" means four different things, and mixing them up causes most bad designs. The HPA only changes the number of pods. It never changes pod size and never adds nodes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Autoscaler&lt;/th&gt;
&lt;th&gt;What it changes&lt;/th&gt;
&lt;th&gt;Typical signal&lt;/th&gt;
&lt;th&gt;Use it when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Horizontal Pod Autoscaler (HPA)&lt;/td&gt;
&lt;td&gt;Replica count of a workload&lt;/td&gt;
&lt;td&gt;CPU or memory utilization, custom or external metrics&lt;/td&gt;
&lt;td&gt;Stateless services where more pods means more capacity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vertical Pod Autoscaler (VPA)&lt;/td&gt;
&lt;td&gt;CPU and memory requests of each pod&lt;/td&gt;
&lt;td&gt;Historical usage&lt;/td&gt;
&lt;td&gt;Right-sizing requests, or workloads that cannot scale out&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KEDA&lt;/td&gt;
&lt;td&gt;Replica count, including to and from zero (it creates and manages an HPA for you)&lt;/td&gt;
&lt;td&gt;Event sources: queue length, Kafka lag, cron, Prometheus queries, cloud services&lt;/td&gt;
&lt;td&gt;Queue consumers and event-driven workers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cluster Autoscaler or Karpenter&lt;/td&gt;
&lt;td&gt;Number and type of nodes&lt;/td&gt;
&lt;td&gt;Pending pods that cannot be scheduled&lt;/td&gt;
&lt;td&gt;Always, if the HPA can create more pods than your nodes can hold&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The usual production setup is HPA (or KEDA) for pods plus Cluster Autoscaler or Karpenter for nodes. If the node layer is missing, the HPA happily creates pods that sit in &lt;code&gt;Pending&lt;/code&gt;, and you get the cost of a scale-up decision without the capacity. Our guide on &lt;a href="https://meteorops.com/blog/how-to-set-up-kubernetes-autoscaling-without-creating-cost-surprises" rel="noopener noreferrer"&gt;setting up Kubernetes autoscaling without cost surprises&lt;/a&gt; covers the node side.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Horizontal Pod Autoscaler algorithm works
&lt;/h2&gt;

&lt;p&gt;The HPA is a control loop inside &lt;code&gt;kube-controller-manager&lt;/code&gt;. It does not react to events. It wakes up on an interval (&lt;code&gt;--horizontal-pod-autoscaler-sync-period&lt;/code&gt;, 15 seconds by default), reads metrics, and computes a desired replica count:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;desiredReplicas = ceil( currentReplicas × currentMetricValue / desiredMetricValue )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example: 4 replicas averaging 90% CPU utilization against a 60% target gives &lt;code&gt;ceil(4 × 90 / 60) = ceil(6.0) = 6&lt;/code&gt; replicas. At 45% utilization it gives &lt;code&gt;ceil(4 × 45 / 60) = 3&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For resource metrics, "utilization" is usage divided by the container's &lt;strong&gt;request&lt;/strong&gt;, not its limit and not node capacity. A pod requesting 250m CPU and using 200m is at 80%. This single fact explains a large share of HPA surprises: change the request and you change the scaling math, even though nothing about the traffic changed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwkecicmf5fpgsgf16n53.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwkecicmf5fpgsgf16n53.webp" alt="Flow chart of the HPA control loop: every 15 seconds it reads metrics (utilization is usage divided by request), computes ceil(4 x 90 / 60) = 6, does nothing if the ratio is within 10% of target (54% to 66% for a 60% target), and otherwise scales to 6 replicas within minReplicas and maxReplicas." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The HPA recomputes desired replicas on a fixed interval and skips any change that falls inside the 10% tolerance.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  The details that shape real behavior
&lt;/h3&gt;

&lt;p&gt;The formula is the easy part. These rules, all from the Kubernetes documentation, decide what actually happens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tolerance.&lt;/strong&gt; If the ratio &lt;code&gt;current / target&lt;/code&gt; is within 10% of 1.0, the HPA does nothing. With a 60% CPU target, anything between 54% and 66% is a no-op. This dead band is your first protection against jitter.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Unready pods are set aside.&lt;/strong&gt; When scaling on CPU, pods that are not yet ready, or whose latest metric sample predates readiness, are excluded from the average. If the result would be a scale up, those pods are then assumed to use 0% of the target, which dampens the scale up.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Missing metrics are treated conservatively.&lt;/strong&gt; Pods with no metric are assumed to use 100% of the target when the HPA is considering a scale down, and 0% when considering a scale up. If that reverses the direction or lands inside the tolerance, the HPA skips the action.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Startup windows.&lt;/strong&gt; &lt;code&gt;--horizontal-pod-autoscaler-initial-readiness-delay&lt;/code&gt; (default 30 seconds) and &lt;code&gt;--horizontal-pod-autoscaler-cpu-initialization-period&lt;/code&gt; (default 5 minutes) control how the controller treats CPU samples from freshly started pods. These are control plane flags, so on EKS, GKE or AKS you cannot change them. Design the workload around the defaults instead.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Multiple metrics take the maximum.&lt;/strong&gt; With CPU and a requests-per-second metric, the HPA computes a desired count for each and uses the highest. If one metric cannot be fetched and the others suggest a scale down, the scale down is skipped. Scale up still works.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Status shows the raw value.&lt;/strong&gt; &lt;code&gt;kubectl get hpa&lt;/code&gt; reports the original average, not the adjusted one used for the decision. That is why the TARGETS column can look like it should trigger a scale and nothing happens.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scale down is smoothed by default.&lt;/strong&gt; The HPA keeps the highest recommendation from the last 5 minutes before scaling down. That default stabilization window is why replicas linger after a spike.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Prerequisites: metrics-server and resource requests
&lt;/h2&gt;

&lt;p&gt;Resource metrics (CPU and memory) come from the &lt;code&gt;metrics.k8s.io&lt;/code&gt; API, which metrics-server provides. Many managed clusters ship it, many do not. Check first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl get apiservice v1beta1.metrics.k8s.io
kubectl top pods -n production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;kubectl top&lt;/code&gt; fails, install metrics-server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then make sure every container in the target pods has a CPU request (and a memory request if you scale on memory). The HPA computes utilization per pod across all its containers, so a single sidecar without a request breaks CPU utilization scaling for the whole workload. If your requests are guesses, fix them before tuning the HPA. Our guide to &lt;a href="https://meteorops.com/blog/how-to-set-kubernetes-resource-requests-and-limits-without-throttling-apps" rel="noopener noreferrer"&gt;Kubernetes requests and limits&lt;/a&gt; shows how to size them from real usage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create your first HPA
&lt;/h2&gt;

&lt;p&gt;The fastest way is &lt;code&gt;kubectl autoscale&lt;/code&gt;. Recent kubectl versions use &lt;code&gt;--cpu&lt;/code&gt; with a percentage (the older &lt;code&gt;--cpu-percent&lt;/code&gt; flag is deprecated):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl autoscale deployment api -n production --min=3 --max=20 --cpu=60%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For anything you run in production, keep the HPA in version control as YAML instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api
  namespace: production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api
  minReplicas: 3
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Watch it work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl get hpa api -n production --watch
kubectl describe hpa api -n production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;describe&lt;/code&gt; is where the useful information lives: current metrics, the three status conditions (&lt;code&gt;AbleToScale&lt;/code&gt;, &lt;code&gt;ScalingActive&lt;/code&gt;, &lt;code&gt;ScalingLimited&lt;/code&gt;) and recent scaling events with reasons.&lt;/p&gt;

&lt;h3&gt;
  
  
  Remove spec.replicas when the HPA owns the count
&lt;/h3&gt;

&lt;p&gt;The Kubernetes docs recommend removing &lt;code&gt;spec.replicas&lt;/code&gt; from the Deployment once an HPA manages it. Otherwise every &lt;code&gt;kubectl apply&lt;/code&gt;, Helm upgrade or Argo CD sync resets the replica count to the value in Git, the HPA scales it back, and you get a sawtooth that looks exactly like flapping. In Helm charts, the common pattern is to render &lt;code&gt;replicas&lt;/code&gt; only when autoscaling is disabled. In Argo CD, you can also add &lt;code&gt;/spec/replicas&lt;/code&gt; to &lt;code&gt;ignoreDifferences&lt;/code&gt; for the Deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  HPA metric types: resource, container, pods, object and external
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;autoscaling/v2&lt;/code&gt; API supports five metric source types. Pick the one that measures the real bottleneck, not the one that is easiest to wire up.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What it measures&lt;/th&gt;
&lt;th&gt;Needs&lt;/th&gt;
&lt;th&gt;Good for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Resource&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CPU or memory across the whole pod, as utilization or average value&lt;/td&gt;
&lt;td&gt;metrics-server&lt;/td&gt;
&lt;td&gt;CPU-bound web services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ContainerResource&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CPU or memory of one named container&lt;/td&gt;
&lt;td&gt;metrics-server, Kubernetes 1.30 or newer (stable)&lt;/td&gt;
&lt;td&gt;Pods with sidecars (service mesh proxies, log shippers) that distort the pod average&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Pods&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A custom per-pod metric averaged across pods&lt;/td&gt;
&lt;td&gt;A custom metrics adapter such as Prometheus Adapter&lt;/td&gt;
&lt;td&gt;Requests per second or in-flight requests per pod&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Object&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A metric describing another Kubernetes object&lt;/td&gt;
&lt;td&gt;A custom metrics adapter&lt;/td&gt;
&lt;td&gt;Requests per second on an Ingress&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;External&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A metric from outside the cluster&lt;/td&gt;
&lt;td&gt;An external metrics adapter (KEDA provides one)&lt;/td&gt;
&lt;td&gt;Queue depth, Pub/Sub backlog, Kafka lag&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Scaling only the application container when a sidecar is present:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;metrics:
  - type: ContainerResource
    containerResource:
      name: cpu
      container: app
      target:
        type: Utilization
        averageUtilization: 60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scaling on a per-pod custom metric (for example, exposed by Prometheus Adapter as &lt;code&gt;http_requests_per_second&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;metrics:
  - type: Pods
    pods:
      metric:
        name: http_requests_per_second
      target:
        type: AverageValue
        averageValue: "50"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few rules of thumb from running these in production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CPU&lt;/strong&gt; is a good signal when traffic turns directly into CPU work: serialization, compression, TLS, rendering.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Memory&lt;/strong&gt; is a poor scaling signal for most runtimes. The JVM, Go, Node.js and Python rarely give memory back quickly after load drops, so a memory-based HPA scales up easily and then refuses to scale down. Use it only when memory per pod clearly falls as you add pods.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Requests per second or in-flight requests&lt;/strong&gt; beat CPU for I/O-bound APIs that spend their time waiting on databases or other services.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Queue depth or lag&lt;/strong&gt; is the right signal for workers. A worker blocked on a slow dependency shows low CPU while the queue grows, so CPU scaling never fires. Use KEDA here.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why HPAs flap, and how to stop it
&lt;/h2&gt;

&lt;p&gt;Flapping means the replica count oscillates: up on a spike, down a minute later, up again. Users feel it as latency spikes during scale ups, connection churn during scale downs, and a cluster that never settles. The causes are almost always one of these:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Requests set too low.&lt;/strong&gt; A pod that normally uses 300m with a 100m request sits at 300% utilization at idle. Small traffic changes swing the ratio wildly. Fix the request first.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A noisy metric.&lt;/strong&gt; Per-second request rates, garbage collection pauses and batchy traffic produce sharp short peaks. The HPA sees them every 15 seconds and reacts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Startup CPU spikes.&lt;/strong&gt; JIT compilation, cache warm-up and connection pool creation make new pods look overloaded, which triggers more scale up, which starts more pods.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scale down that is too fast.&lt;/strong&gt; Removing many pods at once pushes the survivors over the target, and the HPA adds them back.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A target with no headroom.&lt;/strong&gt; A 90% CPU target leaves nothing for the minute or two it takes a new pod to become ready.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Something else is changing replicas.&lt;/strong&gt; A GitOps sync resetting &lt;code&gt;spec.replicas&lt;/code&gt;, two HPAs pointing at the same Deployment, or KEDA and a hand-written HPA on the same target.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Causes 1, 5 and 6 are configuration errors you fix in the workload. Causes 2, 3 and 4 are what the &lt;code&gt;behavior&lt;/code&gt; field exists for.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc0qp1n3bidnothge08g2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc0qp1n3bidnothge08g2.webp" alt="Two panels splitting six causes of HPA flapping: fix in the workload (requests set too low, a target with no headroom, something else changing replicas) and tame with the behavior field (a noisy metric, startup CPU spikes, scale down that is too fast)." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Half of flapping is a configuration error in the workload; the other half is what scaling behavior settings are for.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  HPA behavior: stabilization windows and scaling policies
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;behavior&lt;/code&gt; field lets you control scale up and scale down separately. Each direction has a stabilization window, one or more rate-limiting policies, and a &lt;code&gt;selectPolicy&lt;/code&gt; that decides which policy wins. Fields you do not set are merged with these defaults:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;behavior:
  scaleDown:
    stabilizationWindowSeconds: 300
    policies:
      - type: Percent
        value: 100
        periodSeconds: 15
  scaleUp:
    stabilizationWindowSeconds: 0
    policies:
      - type: Percent
        value: 100
        periodSeconds: 15
      - type: Pods
        value: 4
        periodSeconds: 15
    selectPolicy: Max
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In words: by default the HPA can double the replica count (or add 4 pods, whichever is more) every 15 seconds with no delay, and can remove every pod above &lt;code&gt;minReplicas&lt;/code&gt; in one step once the 5-minute scale-down window has passed. That is aggressive in both directions.&lt;/p&gt;

&lt;h3&gt;
  
  
  What each field does
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;stabilizationWindowSeconds&lt;/code&gt;&lt;/strong&gt;: the HPA looks at all recommendations made during this window and uses the most conservative one. For scale down that is the highest recommendation, so a brief dip does not remove pods. For scale up it is the lowest, so a brief spike does not add pods.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;policies&lt;/code&gt;&lt;/strong&gt;: rate limits. &lt;code&gt;type: Pods, value: 4, periodSeconds: 60&lt;/code&gt; means at most 4 pods per minute. &lt;code&gt;type: Percent, value: 10, periodSeconds: 60&lt;/code&gt; means at most 10% of current replicas per minute (fractions are rounded up, so 10% of 72 replicas allows 8).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;selectPolicy&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;Max&lt;/code&gt; picks the policy allowing the biggest change, &lt;code&gt;Min&lt;/code&gt; the smallest, and &lt;code&gt;Disabled&lt;/code&gt; turns off scaling in that direction entirely.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;tolerance&lt;/code&gt;&lt;/strong&gt; (per direction): overrides the cluster-wide 10% dead band for this HPA. It is beta in Kubernetes 1.35 and 1.36 and generally available from 1.37, so check your cluster version before relying on it.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjvhuoanhnjctdbeinopl.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjvhuoanhnjctdbeinopl.webp" alt="Two bar charts of recommendations over a stabilization window: for scale down the highest recommendation wins so a brief dip removes no pods, and for scale up the lowest wins so a brief spike adds no pods." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A stabilization window ignores short dips and spikes by acting on the most conservative recent recommendation.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Anti-flap profiles you can copy
&lt;/h3&gt;

&lt;p&gt;Starting points, not final answers. Tune them with the load test in the next section.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workload&lt;/th&gt;
&lt;th&gt;Scale up&lt;/th&gt;
&lt;th&gt;Scale down&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Latency-sensitive API, fast startup (under 30 seconds)&lt;/td&gt;
&lt;td&gt;No window, max of 100% or 4 pods per 60 seconds&lt;/td&gt;
&lt;td&gt;300 second window, 20% per 60 seconds&lt;/td&gt;
&lt;td&gt;React fast to real load, shed capacity slowly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slow-starting service (JVM, large caches)&lt;/td&gt;
&lt;td&gt;60 second window, 50% per 60 seconds&lt;/td&gt;
&lt;td&gt;600 second window, 10% per 60 seconds&lt;/td&gt;
&lt;td&gt;Stop startup spikes from compounding; keep warm pods&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spiky, bursty traffic&lt;/td&gt;
&lt;td&gt;No window, 100% per 30 seconds&lt;/td&gt;
&lt;td&gt;900 second window, 1 pod per 120 seconds&lt;/td&gt;
&lt;td&gt;Bursts return; keeping capacity is cheaper than churn&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Batch or queue worker on CPU&lt;/td&gt;
&lt;td&gt;No window, 4 pods per 60 seconds&lt;/td&gt;
&lt;td&gt;300 second window, 25% per 60 seconds&lt;/td&gt;
&lt;td&gt;Usually better on KEDA with queue depth&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The latency-sensitive API profile as YAML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api
  namespace: production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api
  minReplicas: 3
  maxReplicas: 30
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 60
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 0
      selectPolicy: Max
      policies:
        - type: Percent
          value: 100
          periodSeconds: 60
        - type: Pods
          value: 4
          periodSeconds: 60
    scaleDown:
      stabilizationWindowSeconds: 300
      selectPolicy: Min
      policies:
        - type: Percent
          value: 20
          periodSeconds: 60
        - type: Pods
          value: 2
          periodSeconds: 60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;selectPolicy: Min&lt;/code&gt; on scale down, the HPA removes whichever is smaller, 20% or 2 pods, per minute. At 30 replicas that is 2 pods a minute, so once the 5-minute window has passed it takes about 13 more minutes to return to 3. That is deliberate: slow scale down is the cheapest insurance against flapping.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foz74aqv46k6xc60vs7qn.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foz74aqv46k6xc60vs7qn.webp" alt="Line chart of replicas after load drops: the count holds at 30 during the 300 second window, then steps down 2 pods per minute for about 13 minutes until it reaches 3, because selectPolicy Min picks 2 pods over 20%." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Holding capacity for five minutes and then shedding 2 pods a minute is cheap insurance against flapping.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  How to pick the target utilization
&lt;/h3&gt;

&lt;p&gt;The target is really a headroom setting. Ask how much traffic can grow in the time it takes a new pod to become ready and serve. If traffic can rise 50% in the two minutes it takes to start a pod, existing pods need room to absorb that, so a target near 100 / 1.5, about 65% of the point where latency degrades, is the ceiling. If your service degrades at 80% CPU, that gives roughly 50% to 55%.&lt;/p&gt;

&lt;p&gt;Most CPU-bound services land between 50% and 70%. Below 50% you pay for a lot of idle capacity. Above 75% you are betting that scale ups always finish before the next spike.&lt;/p&gt;
&lt;h2&gt;
  
  
  Test HPA behavior before production traffic does
&lt;/h2&gt;

&lt;p&gt;Do not trust an HPA you have not watched under load. In a staging environment with realistic requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# terminal 1: decisions and events
kubectl get hpa api -n staging --watch
kubectl get events -n staging --field-selector involvedObject.name=api --watch

# terminal 2: pods coming and going
kubectl get pods -n staging -l app=api --watch

# terminal 3: load from outside the cluster for 10 minutes, then stop
hey -z 10m -c 100 https://staging.example.com/api/health-heavy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Record five things: time to first scale up, replicas added per minute, whether new pods were ready before the next decision, whether latency recovered, and how replicas stepped down after the load stopped. If replicas climb past what the load needs, your scale up is outrunning readiness. If they drop and come straight back, slow the scale down.&lt;/p&gt;

&lt;h2&gt;
  
  
  HPA with VPA, KEDA, Karpenter and Argo Rollouts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  HPA and VPA
&lt;/h3&gt;

&lt;p&gt;Do not let both act on the same resource metric. If the HPA scales on CPU utilization and VPA raises CPU requests, utilization drops, the HPA removes pods, utilization rises, and the two loops chase each other. Safe combinations are VPA in recommendation-only mode (&lt;code&gt;updateMode: "Off"&lt;/code&gt;) to inform your requests, or HPA on a custom metric such as requests per second while VPA manages CPU and memory.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpftklrrboibgh43vm9pb.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpftklrrboibgh43vm9pb.webp" alt="Loop diagram: VPA raises CPU requests, utilization drops, the HPA removes pods, utilization rises, and the cycle repeats. Safe combinations: VPA in recommendation mode with updateMode Off, or HPA on requests per second with VPA on CPU and memory." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Give the HPA and VPA different signals, or keep VPA in recommendation mode, so they never fight over one metric.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  HPA and KEDA
&lt;/h3&gt;

&lt;p&gt;KEDA is not a competitor to the HPA. A KEDA &lt;code&gt;ScaledObject&lt;/code&gt; creates and manages an HPA for you and feeds it external metrics from its 70+ built-in scalers, plus it handles activation from zero replicas. Never point your own HPA and a KEDA ScaledObject at the same Deployment. Choose KEDA when the signal lives outside the pod (queues, streams, schedules) or when you need scale to zero.&lt;/p&gt;

&lt;h3&gt;
  
  
  HPA and node autoscaling
&lt;/h3&gt;

&lt;p&gt;When the HPA adds pods that do not fit, they stay &lt;code&gt;Pending&lt;/code&gt; until Cluster Autoscaler or Karpenter adds a node, which commonly takes one to several minutes. Your scale-up speed is therefore the slower of pod startup and node provisioning. If that is too slow for your traffic pattern, raise &lt;code&gt;minReplicas&lt;/code&gt;, lower the target, or keep a little spare node capacity with low-priority placeholder pods that real workloads can preempt (see our guide to &lt;a href="https://meteorops.com/blog/how-to-configure-kubernetes-priorityclasses-without-starving-workloads" rel="noopener noreferrer"&gt;Kubernetes PriorityClasses&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  HPA during rollouts
&lt;/h3&gt;

&lt;p&gt;During a rolling update the HPA scales the Deployment, and the Deployment controller spreads replicas across old and new ReplicaSets. Startup CPU from new pods can trigger scale ups mid-rollout, so the slow-starting profile above helps here too. If you use progressive delivery, point &lt;code&gt;scaleTargetRef&lt;/code&gt; at the Rollout instead of a Deployment (&lt;code&gt;apiVersion: argoproj.io/v1alpha1&lt;/code&gt;, &lt;code&gt;kind: Rollout&lt;/code&gt;). Our &lt;a href="https://meteorops.com/blog/how-to-deploy-zero-downtime-kubernetes-releases-with-helm-and-argo-rollouts" rel="noopener noreferrer"&gt;Argo Rollouts guide&lt;/a&gt; covers how canary and blue-green steps interact with the HPA.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can the HPA scale to zero?
&lt;/h2&gt;

&lt;p&gt;Only for object and external metrics, and only with the &lt;code&gt;HPAScaleToZero&lt;/code&gt; feature gate. That gate was alpha (off by default) from Kubernetes 1.16 to 1.36 and became beta, on by default, in 1.37. CPU and memory cannot scale to zero because they can only be measured on running pods. On older clusters, or for anything event driven, use KEDA, which has supported scale to zero for years.&lt;/p&gt;

&lt;h2&gt;
  
  
  HPA troubleshooting: error messages and fixes
&lt;/h2&gt;

&lt;p&gt;These are the messages the HPA controller actually emits, as seen in &lt;code&gt;kubectl describe hpa&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom or message&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TARGETS shows &lt;code&gt;&amp;amp;lt;unknown&amp;amp;gt;/60%&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;No metrics yet, metrics-server missing, or a container without a request&lt;/td&gt;
&lt;td&gt;Wait one or two minutes after creation; run &lt;code&gt;kubectl top pods&lt;/code&gt;; add requests to every container&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;failed to get cpu utilization: missing request for cpu in container X of Pod Y&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A container (often a sidecar or init-injected proxy) has no CPU request&lt;/td&gt;
&lt;td&gt;Add a CPU request to every container, or switch to &lt;code&gt;ContainerResource&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;unable to get metrics for resource cpu: ...&lt;/code&gt; with reason &lt;code&gt;FailedGetResourceMetric&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;metrics.k8s.io&lt;/code&gt; API is unavailable&lt;/td&gt;
&lt;td&gt;Check &lt;code&gt;kubectl get apiservice v1beta1.metrics.k8s.io&lt;/code&gt; and the metrics-server pod logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;did not receive metrics for targeted pods (pods might be unready)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pods are crash looping or never become ready&lt;/td&gt;
&lt;td&gt;Fix readiness first; see our guide to &lt;a href="https://meteorops.com/blog/how-to-configure-kubernetes-liveness-probes-without-causing-restart-loops" rel="noopener noreferrer"&gt;liveness probes without restart loops&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ScalingLimited True TooManyReplicas&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The desired count hit &lt;code&gt;maxReplicas&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Raise &lt;code&gt;maxReplicas&lt;/code&gt; only if downstream systems (database, cache, APIs) can take it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ScaleDownStabilized&lt;/code&gt; while load is low&lt;/td&gt;
&lt;td&gt;The scale-down window is holding the highest recent recommendation&lt;/td&gt;
&lt;td&gt;Expected. Shorten the window only if idle cost matters more than churn&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Replicas jump back to a fixed number after every deploy&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;spec.replicas&lt;/code&gt; in the manifest overrides the HPA on each apply or sync&lt;/td&gt;
&lt;td&gt;Remove &lt;code&gt;spec.replicas&lt;/code&gt;, or ignore it in Argo CD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory HPA never scales down&lt;/td&gt;
&lt;td&gt;The runtime keeps memory after load drops&lt;/td&gt;
&lt;td&gt;Scale on CPU or a work metric instead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;More pods, same latency&lt;/td&gt;
&lt;td&gt;The bottleneck is a database, lock or external API&lt;/td&gt;
&lt;td&gt;Cap &lt;code&gt;maxReplicas&lt;/code&gt; at downstream capacity, add pooling and backpressure&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Production HPA checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Every container in the target has a CPU request based on measured usage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;spec.replicas&lt;/code&gt; is removed from the Deployment manifest, or ignored by your GitOps tool.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;minReplicas&lt;/code&gt; is at least 2 (3 for multi-zone services) and covers normal traffic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;maxReplicas&lt;/code&gt; is set from downstream capacity and node budget, not from a round number.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The metric matches the bottleneck: CPU for compute, requests per pod for I/O-bound APIs, queue depth (via KEDA) for workers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scale up is faster than scale down, and scale down has a window of at least 300 seconds.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Readiness probes reflect real readiness, and startup time is known.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A node autoscaler is in place and its provisioning time is part of your scale-up budget.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;VPA is not changing the same resource the HPA scales on.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You have watched the HPA through a load test and a scale down.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Kubernetes HPA FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is HPA useful for Kubernetes?
&lt;/h3&gt;

&lt;p&gt;Yes, for stateless workloads where more pods means more capacity. It lets you run fewer replicas at quiet times and absorb peaks without manual intervention. It is the wrong tool for stateful systems that cannot simply add replicas, and for workloads whose bottleneck is outside the pod.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a good HPA CPU threshold?
&lt;/h3&gt;

&lt;p&gt;Most CPU-bound services work well between 50% and 70% of the CPU request. Use the headroom calculation above: the slower your pods and nodes start, and the faster your traffic grows, the lower the target should be.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between HPA and VPA?
&lt;/h3&gt;

&lt;p&gt;The HPA changes how many pods run. VPA changes how much CPU and memory each pod requests. They can work together if they act on different signals, for example HPA on requests per second and VPA on resources, or VPA in recommendation mode only.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between KEDA and HPA?
&lt;/h3&gt;

&lt;p&gt;KEDA builds on the HPA. It creates an HPA for you, feeds it metrics from event sources like queues and streams, and adds scale to zero. Use plain HPA for CPU or custom metrics on services; use KEDA for event-driven workers.&lt;/p&gt;

&lt;h3&gt;
  
  
  How often does the HPA check metrics?
&lt;/h3&gt;

&lt;p&gt;Every 15 seconds by default. Metrics-server itself scrapes kubelets on its own interval, so the data the HPA sees can be somewhat older than that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does my HPA show unknown targets?
&lt;/h3&gt;

&lt;p&gt;Either metrics are not available yet (normal for a minute or two after creation), metrics-server is missing or broken, or at least one container in the pods has no request for the resource you scale on. &lt;code&gt;kubectl describe hpa&lt;/code&gt; shows which one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is my HPA not scaling down?
&lt;/h3&gt;

&lt;p&gt;The 5-minute default scale-down stabilization window, a metric still above the target minus the 10% tolerance, a memory metric that never drops, or a second metric that keeps the maximum recommendation high. Check the conditions and events in &lt;code&gt;kubectl describe hpa&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use HPA and a fixed replica count in Helm?
&lt;/h3&gt;

&lt;p&gt;Not on the same object. Render &lt;code&gt;replicas&lt;/code&gt; in the Deployment only when autoscaling is disabled, so a Helm upgrade does not reset the HPA's decision.&lt;/p&gt;

&lt;p&gt;If your autoscaling still behaves unpredictably after this, the cause is usually in the workload or the node layer rather than the HPA itself. MeteorOps engineers tune this for startups as part of &lt;a href="https://meteorops.com/technologies/kubernetes" rel="noopener noreferrer"&gt;Kubernetes consulting&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>performance</category>
      <category>sre</category>
    </item>
    <item>
      <title>SRE vs DevOps: Key Differences and Which Your Startup Needs</title>
      <dc:creator>Michael Zion</dc:creator>
      <pubDate>Sun, 01 Nov 2026 07:30:04 +0000</pubDate>
      <link>https://dev.to/meteorops/sre-vs-devops-key-differences-and-which-your-startup-needs-58ec</link>
      <guid>https://dev.to/meteorops/sre-vs-devops-key-differences-and-which-your-startup-needs-58ec</guid>
      <description>&lt;p&gt;&lt;em&gt;This guide was written by &lt;a href="https://meteorops.com/team/michael-zion" rel="noopener noreferrer"&gt;Michael Zion&lt;/a&gt;, CEO at MeteorOps, and first published on the &lt;a href="https://meteorops.com/blog/sre-vs-devops" rel="noopener noreferrer"&gt;MeteorOps blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;SRE vs DevOps in one paragraph:&lt;/strong&gt; DevOps is a culture and set of practices for shipping software faster by removing the wall between development and operations. Site reliability engineering (SRE) is a specific, opinionated way to run production that came out of Google: it sets reliability targets (SLOs), spends an error budget against them, and treats operations as a software problem. Google sums it up as "class SRE implements interface DevOps". DevOps optimizes the path to production. SRE optimizes what happens once you are there.&lt;/p&gt;

&lt;p&gt;Below is how the two differ in goals, daily work, metrics, skills and pay, how they fit together, and which one a startup actually needs at each stage.&lt;/p&gt;

&lt;h2&gt;
  
  
  SRE vs DevOps at a glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;DevOps&lt;/th&gt;
&lt;th&gt;SRE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What it is&lt;/td&gt;
&lt;td&gt;A culture and set of practices across the whole delivery lifecycle&lt;/td&gt;
&lt;td&gt;A job role and an engineering discipline for running production&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Origin&lt;/td&gt;
&lt;td&gt;A movement among practitioners at many companies, around 2009&lt;/td&gt;
&lt;td&gt;Google, from 2003, written up in the &lt;a href="https://sre.google/sre-book/table-of-contents/" rel="noopener noreferrer"&gt;Site Reliability Engineering&lt;/a&gt; book (2016)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Main question&lt;/td&gt;
&lt;td&gt;How do we ship changes faster and safer?&lt;/td&gt;
&lt;td&gt;How reliable must this service be, and are we meeting that?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Core practices&lt;/td&gt;
&lt;td&gt;CI/CD, infrastructure as code, automation, shared ownership, fast feedback&lt;/td&gt;
&lt;td&gt;SLIs and SLOs, error budgets, toil reduction, incident response, blameless postmortems, capacity planning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical metrics&lt;/td&gt;
&lt;td&gt;The five &lt;a href="https://dora.dev/guides/dora-metrics/" rel="noopener noreferrer"&gt;DORA metrics&lt;/a&gt;: change lead time, deployment frequency, failed deployment recovery time, change fail rate, deployment rework rate&lt;/td&gt;
&lt;td&gt;SLO attainment, error budget burn rate, toil percentage, incident count and duration, pages per shift&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prescriptiveness&lt;/td&gt;
&lt;td&gt;Principles; little detail on how to run services&lt;/td&gt;
&lt;td&gt;Concrete rules, such as caps on toil and on-call time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where the work sits&lt;/td&gt;
&lt;td&gt;Pipelines, environments, platforms, developer workflow&lt;/td&gt;
&lt;td&gt;Production systems, observability, on-call, reliability of user-facing services&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1hfeaii3w1j7hh0bixxj.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1hfeaii3w1j7hh0bixxj.webp" alt="Diagram of a flow from Commit to CI/CD to Production: DevOps covers the path to production (pipelines, environments, platforms), while SRE covers production once you are there (observability, on-call, reliability)." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;DevOps owns how changes reach production; SRE owns how production behaves once they land.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What DevOps actually means
&lt;/h2&gt;

&lt;p&gt;DevOps started as a reaction to a familiar split: developers were measured on shipping features, operations on keeping things stable, and every release became a fight. DevOps says one team should own the whole path from commit to production, with automation doing the repetitive work.&lt;/p&gt;

&lt;p&gt;A common way to describe it is &lt;strong&gt;CALMS&lt;/strong&gt;: Culture, Automation, Lean, Measurement and Sharing. In practice that means CI/CD pipelines, infrastructure as code, small and frequent changes, environments anyone can recreate, and developers who can see and act on how their code behaves in production.&lt;/p&gt;

&lt;p&gt;"DevOps engineer" is the job title that grew out of this. In most companies a DevOps engineer builds and runs the delivery platform: cloud infrastructure, CI/CD, containers and Kubernetes, secrets, and the tooling that lets developers ship on their own.&lt;/p&gt;

&lt;h2&gt;
  
  
  What SRE actually means
&lt;/h2&gt;

&lt;p&gt;SRE is what you get when you ask software engineers to run operations. Its central idea is that 100% reliability is the wrong target. Users cannot tell 99.99% from 100%, and chasing the last fraction slows every release. So SRE makes reliability an explicit, measured number:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;SLI (service level indicator):&lt;/strong&gt; what you measure, such as the share of requests that succeed in under 300 ms.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;SLO (service level objective):&lt;/strong&gt; the target for that indicator over a window, such as 99.9% over 30 days.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Error budget:&lt;/strong&gt; the failure you are allowed. A 99.9% SLO over 30 days allows about 43 minutes of full downtime, or the equivalent in failed requests.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While budget remains, teams ship freely. When it runs out, the priority shifts to reliability work until the service is healthy again. That one rule turns the old dev vs ops argument into a shared, data-driven decision.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg74bkccyc4379sssjlub.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg74bkccyc4379sssjlub.webp" alt="Flow from SLI (what you measure) to SLO (99.9% over 30 days) to an error budget of about 43 minutes, which splits into two outcomes: budget remains, ship freely; budget runs out, reliability work first." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The error budget turns a reliability target into a clear rule for when to ship and when to fix.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;SRE also sets hard limits on operational load. Google's SRE teams aim to keep toil, the manual, repetitive, automatable work that "scales linearly as a service grows", &lt;a href="https://sre.google/sre-book/eliminating-toil/" rel="noopener noreferrer"&gt;below 50% of each engineer's time&lt;/a&gt;. Of the remaining operational time, &lt;a href="https://sre.google/sre-book/being-on-call/" rel="noopener noreferrer"&gt;no more than 25% should be on-call&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much downtime does each SLO allow?
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;SLO&lt;/th&gt;
&lt;th&gt;Allowed downtime per 30 days&lt;/th&gt;
&lt;th&gt;Per year&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;99%&lt;/td&gt;
&lt;td&gt;7 hours 12 minutes&lt;/td&gt;
&lt;td&gt;about 3.65 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;99.5%&lt;/td&gt;
&lt;td&gt;3 hours 36 minutes&lt;/td&gt;
&lt;td&gt;about 1.8 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;99.9%&lt;/td&gt;
&lt;td&gt;43.2 minutes&lt;/td&gt;
&lt;td&gt;about 8.8 hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;99.95%&lt;/td&gt;
&lt;td&gt;21.6 minutes&lt;/td&gt;
&lt;td&gt;about 4.4 hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;99.99%&lt;/td&gt;
&lt;td&gt;4.3 minutes&lt;/td&gt;
&lt;td&gt;about 53 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  "class SRE implements interface DevOps"
&lt;/h2&gt;

&lt;p&gt;Google's own &lt;a href="https://sre.google/workbook/how-sre-relates/" rel="noopener noreferrer"&gt;SRE workbook&lt;/a&gt; frames the relationship this way. DevOps is the interface: a set of principles. SRE is one concrete implementation of those principles, with specific practices for each of them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reduce organizational silos:&lt;/strong&gt; SREs share ownership of production with developers and use the same tools.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Accept failure as normal:&lt;/strong&gt; error budgets and blameless postmortems.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Implement gradual change:&lt;/strong&gt; small changes, canary releases, fast rollback.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tooling and culture are interrelated:&lt;/strong&gt; SREs use the same tools as developers, and the toil cap forces automation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Measure everything:&lt;/strong&gt; SLOs and error budgets drive decisions.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2o4p9qxv8h6fzaqdfy9z.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2o4p9qxv8h6fzaqdfy9z.webp" alt="Mapping of five DevOps principles to SRE practices: reduce organizational silos to shared ownership of production; accept failure as normal to error budgets and blameless postmortems; implement gradual change to canary releases and fast rollback; tooling and culture are interrelated to a toil cap that forces automation; measure everything to SLOs and error budgets driving decisions." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Each DevOps principle has a concrete SRE practice behind it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The workbook also names the differences: DevOps is broader and targets silos across the whole organization, while SRE is narrower, more prescriptive, and focused on services and their users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Responsibilities side by side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;DevOps engineer&lt;/th&gt;
&lt;th&gt;Site reliability engineer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CI/CD&lt;/td&gt;
&lt;td&gt;Builds and owns pipelines, build caching, release automation&lt;/td&gt;
&lt;td&gt;Adds safety: canaries, automated rollback, release gates tied to SLOs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Infrastructure&lt;/td&gt;
&lt;td&gt;Provisions cloud, networks, clusters with Terraform or similar&lt;/td&gt;
&lt;td&gt;Capacity planning, load testing, failure-mode design, disaster recovery drills&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observability&lt;/td&gt;
&lt;td&gt;Installs and maintains the monitoring and logging stack&lt;/td&gt;
&lt;td&gt;Defines SLIs and SLOs, alerting on symptoms and burn rate, dashboards for incidents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Incidents&lt;/td&gt;
&lt;td&gt;Often on the rotation, fixes infrastructure causes&lt;/td&gt;
&lt;td&gt;Leads incident response, runs postmortems, tracks follow-ups to completion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Developer experience&lt;/td&gt;
&lt;td&gt;Self-service environments, templates, internal platform&lt;/td&gt;
&lt;td&gt;Production readiness reviews, reliability guidance for service owners&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code&lt;/td&gt;
&lt;td&gt;Infrastructure code, scripts, pipeline config&lt;/td&gt;
&lt;td&gt;Automation and tooling, sometimes changes to application code for reliability&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In small companies these roles overlap heavily, and one senior engineer often does both. The split becomes real once you have enough services and traffic that reliability needs a dedicated owner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Skills: where they overlap and where they don't
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Shared core:&lt;/strong&gt; Linux, networking, a major cloud provider, containers and Kubernetes, infrastructure as code, CI/CD, a scripting or programming language, and monitoring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Leans DevOps:&lt;/strong&gt; pipeline design, build systems, Terraform module design, developer tooling, platform engineering, cost-aware infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Leans SRE:&lt;/strong&gt; distributed systems failure modes, SLO design, statistics for latency and error rates, performance and capacity analysis, incident command, writing production-grade software in Go, Python or Java.&lt;/p&gt;

&lt;h2&gt;
  
  
  SRE vs DevOps salary
&lt;/h2&gt;

&lt;p&gt;The two pay similarly, with SRE slightly ahead in most surveys. In the &lt;a href="https://survey.stackoverflow.co/2024/work" rel="noopener noreferrer"&gt;2024 Stack Overflow Developer Survey&lt;/a&gt;, median yearly salaries were:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;United States median&lt;/th&gt;
&lt;th&gt;Global median&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SRE&lt;/td&gt;
&lt;td&gt;$166,500&lt;/td&gt;
&lt;td&gt;$99,099&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DevOps specialist&lt;/td&gt;
&lt;td&gt;$145,000&lt;/td&gt;
&lt;td&gt;$72,611&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloud infrastructure engineer&lt;/td&gt;
&lt;td&gt;$165,000&lt;/td&gt;
&lt;td&gt;$96,666&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;a href="https://survey.stackoverflow.co/2025/work" rel="noopener noreferrer"&gt;2025 survey&lt;/a&gt; no longer breaks out SRE, but reports a US median of $165,000 for "DevOps engineer or professional". Seniority, location and company size move pay far more than the title does. Treat the gap between the two roles as small.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which does your startup need?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;What you need&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pre-product or MVP, 1 to 10 engineers&lt;/td&gt;
&lt;td&gt;DevOps practices, owned by developers, with outside help for the foundation&lt;/td&gt;
&lt;td&gt;You need CI/CD, infrastructure as code and basic monitoring. Reliability targets would be guesses.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Early customers, 10 to 30 engineers&lt;/td&gt;
&lt;td&gt;A first DevOps or platform engineer, plus simple SLOs on your one or two critical user journeys&lt;/td&gt;
&lt;td&gt;Delivery speed is the bottleneck. A light SLO keeps reliability from being argued by gut feel.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Growth, 30 to 100 engineers&lt;/td&gt;
&lt;td&gt;A platform team and your first SRE, or SRE practices embedded in the platform team&lt;/td&gt;
&lt;td&gt;Incidents, on-call load and enterprise customers' uptime expectations make reliability a full-time job.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scale, 100+ engineers or strict SLAs&lt;/td&gt;
&lt;td&gt;A dedicated SRE function alongside platform engineering&lt;/td&gt;
&lt;td&gt;Error budgets, capacity planning and incident management across many teams need clear ownership.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two practical rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hire for the bottleneck.&lt;/strong&gt; If releases are slow and painful, you need DevOps capability. If releases are fine but outages, pages and customer complaints are the problem, you need SRE capability.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Don't build a 24/7 rotation too early.&lt;/strong&gt; Google's guidance puts the minimum for a single-site 24/7 rotation at eight engineers. Most startups get further with business-hours paging, good runbooks and clear severity rules. Our guide to &lt;a href="https://meteorops.com/blog/how-to-design-on-call-before-you-hire-sre" rel="noopener noreferrer"&gt;designing an on-call rotation for a small team&lt;/a&gt; covers the math.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu7padyhi4ood9i6hls62.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu7padyhi4ood9i6hls62.webp" alt="Decision tree: if releases are slow and painful, you need DevOps capability; if releases are fine but outages and pages are the problem, you need SRE capability." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Let the problem you feel most decide which capability to hire first.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How DevOps and SRE work together
&lt;/h2&gt;

&lt;p&gt;In a healthy setup, the platform or DevOps team builds the paved road (pipelines, environments, infrastructure modules), and SRE makes sure what runs on that road meets its reliability targets. Concretely:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Product teams own their services and their SLOs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The platform team makes the right thing the easy thing: standard deploys, built-in metrics, safe rollbacks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SREs set the SLO framework, review production readiness, lead major incidents and push systemic fixes back into the platform.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Error budget policy decides, in advance, what happens when a service runs out of budget.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A practical first step for any team is cleaning up alerting so that pages map to user impact. Our article on &lt;a href="https://meteorops.com/blog/how-to-handle-alert-fatigue" rel="noopener noreferrer"&gt;handling alert fatigue&lt;/a&gt; shows how, and the &lt;a href="https://meteorops.com/blog/how-to-build-a-startup-observability-stack" rel="noopener noreferrer"&gt;observability stack guide&lt;/a&gt; covers what to run at each team size.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Renaming the ops team "SRE".&lt;/strong&gt; Without SLOs, error budgets and a toil cap, it is the same ops team with a new title.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hiring a "DevOps engineer" to be the only person who can deploy.&lt;/strong&gt; That rebuilds the silo DevOps was meant to remove.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Setting SLOs at 99.99% by default.&lt;/strong&gt; Each extra nine costs a lot. Start from what users notice and what your dependencies can deliver.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Measuring only uptime.&lt;/strong&gt; Latency and correctness matter to users as much as availability.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Treating it as either/or.&lt;/strong&gt; Most teams need DevOps practices first and SRE practices as they grow, not one instead of the other.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is SRE better than DevOps?
&lt;/h3&gt;

&lt;p&gt;Neither is better; they solve different problems. DevOps improves how you build and ship software. SRE improves how reliably it runs. SRE is often described as one specific implementation of DevOps principles, so a mature organization usually uses both.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is DevOps a part of SRE, or SRE a part of DevOps?
&lt;/h3&gt;

&lt;p&gt;SRE is generally seen as an implementation of DevOps. Google's phrase is "class SRE implements interface DevOps": DevOps defines the principles, and SRE is a concrete, prescriptive way to apply them to running production services.&lt;/p&gt;

&lt;h3&gt;
  
  
  Who earns more, SRE or DevOps?
&lt;/h3&gt;

&lt;p&gt;SREs usually earn slightly more. In the 2024 Stack Overflow Developer Survey, the US median was $166,500 for SREs and $145,000 for DevOps specialists. Seniority, location and employer matter more than the title.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can a DevOps engineer become an SRE?
&lt;/h3&gt;

&lt;p&gt;Yes, it is one of the most common paths. Add stronger programming skills, learn SLO and error budget design, get comfortable leading incidents and postmortems, and go deeper on distributed systems failure modes and capacity planning.&lt;/p&gt;

&lt;h3&gt;
  
  
  Will AI replace SRE jobs?
&lt;/h3&gt;

&lt;p&gt;AI tools are taking over parts of the work, such as summarizing incidents, correlating alerts and drafting runbooks and postmortems. The judgment parts remain: choosing reliability targets, deciding trade-offs during incidents and designing systems that fail safely. The role is shifting toward that work, not disappearing.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between SRE and platform engineering?
&lt;/h3&gt;

&lt;p&gt;Platform engineering builds the internal products developers use to ship: templates, pipelines, environments, an internal developer platform. SRE focuses on the reliability of the services running in production. Many startups combine them in one team until they grow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does a startup need an SRE?
&lt;/h3&gt;

&lt;p&gt;Usually not as a first infrastructure hire. Most startups need DevOps capability first, with simple SLOs on critical user journeys. A dedicated SRE starts to pay off when incidents, on-call load or customer uptime commitments take a meaningful share of engineering time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short answer
&lt;/h2&gt;

&lt;p&gt;DevOps gets your changes to production quickly and safely. SRE keeps production as reliable as your users need, and no more. Start with DevOps practices, add SLOs early and cheaply, and bring in dedicated SRE when reliability becomes a job of its own.&lt;/p&gt;

&lt;p&gt;Need senior help with either? MeteorOps provides &lt;a href="https://meteorops.com/services/devops-consulting" rel="noopener noreferrer"&gt;DevOps consulting&lt;/a&gt; and &lt;a href="https://meteorops.com/services/sre-on-call-support" rel="noopener noreferrer"&gt;SRE and on-call support&lt;/a&gt; for startups, from first pipelines to SLOs and incident response.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>sre</category>
      <category>startup</category>
      <category>management</category>
    </item>
    <item>
      <title>How to Choose a Staff Augmentation Company: A CTO's Guide</title>
      <dc:creator>Michael Zion</dc:creator>
      <pubDate>Fri, 30 Oct 2026 15:32:25 +0000</pubDate>
      <link>https://dev.to/meteorops/how-to-choose-a-staff-augmentation-company-a-ctos-guide-1825</link>
      <guid>https://dev.to/meteorops/how-to-choose-a-staff-augmentation-company-a-ctos-guide-1825</guid>
      <description>&lt;p&gt;&lt;em&gt;This guide was written by &lt;a href="https://meteorops.com/team/michael-zion" rel="noopener noreferrer"&gt;Michael Zion&lt;/a&gt;, CEO at MeteorOps, and first published on the &lt;a href="https://meteorops.com/blog/how-to-choose-a-staff-augmentation-company" rel="noopener noreferrer"&gt;MeteorOps blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;To choose a staff augmentation company, judge the engineers, not the sales deck. Insist on interviewing the exact person who will join your team, ask how the vendor screens for your stack, and get clear answers on ramp-up, access, replacement terms, rate structure and how the engagement ends. A good provider answers all of that specifically and in writing before you sign. A weak one talks about its "talent pool", sends a polished CV and asks for a long commitment.&lt;/p&gt;

&lt;p&gt;Staff augmentation is the right model when you know what needs doing, you have someone internal who can direct the work, and you are short on senior hands. It is the wrong model when nobody owns the problem: then you need a consultancy or a managed service that takes responsibility for the outcome, not extra capacity that waits for instructions.&lt;/p&gt;

&lt;p&gt;This guide is written for CTOs, VPs of Engineering and engineering managers at startups and scale-ups who are buying engineering capacity, with a focus on DevOps, platform and cloud work, where a bad hire can touch production on day three. It covers how staff augmentation compares with managed services and consulting, the questions to ask (with what good and bad answers sound like), how pricing works and how to compare quotes, the contract clauses that matter, red flags, a scorecard, and a first-30-days plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a staff augmentation company actually sells
&lt;/h2&gt;

&lt;p&gt;A staff augmentation company supplies engineers who work inside your team, under your direction, in your tools and your processes. You decide what they work on. They attend your standups, open pull requests in your repositories and follow your review rules. The vendor handles sourcing, employment, payroll, benefits and replacement if the person leaves or is not a fit.&lt;/p&gt;

&lt;p&gt;That is the whole product: vetted people plus the commercial wrapper around them. The quality difference between providers comes almost entirely from three things: how good their engineers really are, how honestly they match a person to your problem, and what happens when something goes wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Staff augmentation vs managed services vs consulting
&lt;/h3&gt;

&lt;p&gt;Most buyers searching for a staff augmentation company are also deciding between this model and two others. The difference is who owns the outcome.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Staff augmentation&lt;/th&gt;
&lt;th&gt;Managed services&lt;/th&gt;
&lt;th&gt;Consulting / project delivery&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Who directs the work&lt;/td&gt;
&lt;td&gt;You&lt;/td&gt;
&lt;td&gt;The provider, against an agreed scope or service levels&lt;/td&gt;
&lt;td&gt;The provider, against a defined project goal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who owns the outcome&lt;/td&gt;
&lt;td&gt;You&lt;/td&gt;
&lt;td&gt;The provider&lt;/td&gt;
&lt;td&gt;Shared; the provider owns delivery of the agreed scope&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical billing&lt;/td&gt;
&lt;td&gt;Hourly or monthly per engineer&lt;/td&gt;
&lt;td&gt;Monthly fee tied to service levels&lt;/td&gt;
&lt;td&gt;Fixed price or time and materials per project&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;A known backlog and a gap in senior capacity&lt;/td&gt;
&lt;td&gt;Ongoing operations you do not want to run yourself&lt;/td&gt;
&lt;td&gt;A specific change you lack the expertise to design&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Your management effort&lt;/td&gt;
&lt;td&gt;High: priorities, reviews, onboarding&lt;/td&gt;
&lt;td&gt;Low day to day, high at contract time&lt;/td&gt;
&lt;td&gt;Medium: decisions and acceptance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Knowledge stays with&lt;/td&gt;
&lt;td&gt;Your team, if you insist on documentation&lt;/td&gt;
&lt;td&gt;Mostly the provider&lt;/td&gt;
&lt;td&gt;Your team, if handover is in the contract&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Main risk&lt;/td&gt;
&lt;td&gt;Paying for capacity nobody directs&lt;/td&gt;
&lt;td&gt;Lock-in and slow change requests&lt;/td&gt;
&lt;td&gt;A design your team cannot operate after handover&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In practice the lines blur. Many DevOps providers offer an engineer who is embedded in your team and also backed by an architect who reviews their work. That hybrid is often the best fit for a startup without a senior platform lead, because you get capacity plus a second opinion on design decisions. If you want a deeper comparison of provider types, see our breakdown of &lt;a href="https://meteorops.com/blog/devops-agency-vs-devops-consultancy-vs-devops-services-company" rel="noopener noreferrer"&gt;DevOps agencies, consultancies and services companies&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When staff augmentation is the right call (and when it is not)
&lt;/h2&gt;

&lt;p&gt;Use staff augmentation when most of these are true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You can name the work: a Kubernetes upgrade, a Terraform cleanup, CI/CD (continuous integration and continuous delivery) that takes 40 minutes, an observability gap, a cloud migration.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Someone on your side can set priorities, review pull requests and accept the work, even if only for a few hours a week.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You need senior skills faster than you can hire. A senior DevOps or platform hire commonly takes months from opening the role to a productive first week.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The need is real but its long-term size is uncertain, so a permanent headcount is premature.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You want the knowledge to stay in your team, not in a provider's runbooks.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Choose something else when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Nobody internal owns the area.&lt;/strong&gt; An augmented engineer with no direction drifts toward whatever ticket is loudest. Buy an assessment or a consulting engagement that produces a plan first.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;You want to stop operating something entirely.&lt;/strong&gt; If the goal is "we never want to think about our database backups again", that is a managed service with service levels, not a person.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The real problem is organisational.&lt;/strong&gt; Unclear product priorities, no agreed architecture owner or a team that does not review code will not be fixed by adding people. Extra capacity will expose those problems faster.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;You need one decision, not ongoing work.&lt;/strong&gt; A few hours with a senior engineer to review an architecture or choose between two approaches is cheaper and faster than a monthly seat.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flkxlwz6r23otu6oj68pe.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flkxlwz6r23otu6oj68pe.webp" alt="Decision flow chart: if nobody internal owns the area, start with an assessment or consulting; if you want to stop operating it, choose a managed service; if you need one decision, book a few hours with a senior engineer; if you can name ongoing work and someone directs it, staff augmentation fits." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Staff augmentation pays off only when someone owns the work and the work is known and ongoing.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pros and cons of staff augmentation
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Senior capacity in days or weeks instead of months&lt;/td&gt;
&lt;td&gt;You still have to manage, onboard and review the work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scale up or down without hiring and layoffs&lt;/td&gt;
&lt;td&gt;Higher hourly cost than an equivalent employee's salary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Access to specialists you only need for a while (Kubernetes, security, data platforms)&lt;/td&gt;
&lt;td&gt;Knowledge leaves with the engineer unless documentation is enforced&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full control over priorities and technical direction&lt;/td&gt;
&lt;td&gt;Quality varies hugely between providers and between engineers at the same provider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The vendor carries recruitment, payroll and replacement risk&lt;/td&gt;
&lt;td&gt;Weak vendors rotate people, which resets your onboarding investment&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Write a one-page brief before you contact any vendor
&lt;/h2&gt;

&lt;p&gt;The fastest way to separate good providers from weak ones is to send them all the same short brief and compare what comes back. It also forces you to decide what you are buying. Keep it to one page:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The problem in one paragraph.&lt;/strong&gt; "Releases need a senior engineer to babysit them, and our EKS clusters are two minor versions behind" beats "we need a DevOps engineer".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The stack.&lt;/strong&gt; Cloud provider, orchestration, IaC (infrastructure as code) tooling, CI/CD system, observability tools, languages of the services being deployed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The first three outcomes&lt;/strong&gt; you would be happy to see in 60 to 90 days.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Who directs the work&lt;/strong&gt; on your side and how much time they really have.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Working pattern.&lt;/strong&gt; Required time-zone overlap, on-call expectations, meeting cadence.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Expected volume.&lt;/strong&gt; A few hours a week, half-time or full-time, and for roughly how long.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Constraints.&lt;/strong&gt; Compliance (SOC 2, ISO 27001, HIPAA), data residency, background checks, device policy.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you are unsure how much DevOps capacity you need in the first place, size it before talking to vendors: our guide to &lt;a href="https://meteorops.com/blog/calculating-your-companys-required-devops-capacity" rel="noopener noreferrer"&gt;calculating your company's required DevOps capacity&lt;/a&gt; walks through the inputs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Questions to ask a staff augmentation company, and what good answers sound like
&lt;/h2&gt;

&lt;p&gt;These questions are grouped by the risk they test. For each one, the good answer is specific and checkable. The bad answer is vague, deflects to process or promises something nobody can promise.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft504qvjhc7unjbcl6rfq.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft504qvjhc7unjbcl6rfq.webp" alt="Side-by-side comparison of good vendor answers and red flags: interviewing the exact engineer vs a team lead representing them, hands-on production exercises vs certifications as the main filter, a named architect reviewing the work vs the engineer working alone, individual read-only accounts vs shared admin credentials, and month to month terms vs annual contracts with auto-renewal." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Good answers are specific and checkable; red flags are vague or ask you to trust the vendor.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  About the engineers
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. "Can I interview the exact engineer who will work with us?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Good answer: yes, before you commit, with a technical conversation led by your own engineer, and the person you interview is the person who starts. Red flag: "our team lead will represent the engineer", "we will assign the best available resource after signing", or a different name on day one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. "How do you screen engineers for this specific stack?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Good answer: a description of a real technical evaluation (hands-on exercises, production debugging scenarios, reviews by senior engineers in that discipline) and how often people fail it. Red flag: certifications and years of experience as the main filter. Certifications show someone passed an exam, not that they can safely upgrade a production cluster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. "Is this person an employee or a subcontractor of yours?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Good answer: a straight answer, either way, plus how they handle confidentiality and IP (intellectual property) assignment for both cases. Red flag: hesitation, or discovering later that your "vendor engineer" was sourced through a second agency, which adds margin and removes accountability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. "What has this engineer actually run in production?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Good answer: concrete examples in their own words during the interview. "I migrated 30 services from ECS (Elastic Container Service) to EKS (Elastic Kubernetes Service) and here is what broke" is worth more than any CV bullet. Ask what they would do differently. Engineers who have operated real systems always have an answer.&lt;/p&gt;

&lt;h3&gt;
  
  
  About fit and ramp-up
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;5. "What will the first two weeks look like?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Good answer: an access plan, a system walkthrough, a review of your repositories and pipelines, and one or two small, low-risk changes shipped in week one. Red flag: "two weeks of discovery" with no output, or the opposite, a plan to rebuild your platform before they have read it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. "Who reviews this engineer's work besides us?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Good answer: a named senior or architect at the provider who is available for design reviews and hard problems, at no extra cost or at a clearly stated one. This matters most when your own team cannot review infrastructure changes in depth. Red flag: the engineer works alone and the vendor's involvement ends at invoicing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. "How do you handle time-zone overlap and communication?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Good answer: a stated number of overlapping working hours, response expectations in your chat tool, and written updates. For DevOps work, a minimum of three to four hours of overlap is a reasonable bar if the engineer touches anything that can page someone. Red flag: "we are flexible" with no numbers.&lt;/p&gt;

&lt;h3&gt;
  
  
  About security and access
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;8. "What access will the engineer need, and how do you handle it?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Good answer: they expect to use your identity provider, your SSO (single sign-on) and individual named accounts, start with read-only or non-production access, and get production access through your normal approval path. They should also mention offboarding: what gets revoked on the last day. Red flag: asking for shared admin credentials or long-lived cloud access keys "to move faster".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9. "What do you require from engineers on security?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Good answer: device security requirements, a signed NDA (non-disclosure agreement) and IP assignment that flows down to the individual, willingness to follow your policies and complete your security training, and support for background checks if you need them. If you are SOC 2 audited, ask whether they have supported customer audits before.&lt;/p&gt;

&lt;h3&gt;
  
  
  About commercials and exit
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;10. "What happens if the engineer is not a fit?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Good answer: a defined replacement window (for example, a replacement candidate within one to two weeks), no charge for the overlap or ramp-up of the replacement, and a short notice period. Red flag: replacement only after a formal performance process, or a long minimum term with no fit clause.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;11. "What is the minimum commitment and notice period?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Good answer: month to month or a short initial term, with notice measured in weeks. Long lock-ins are a sign the provider is pricing in churn risk. Red flag: annual contracts with auto-renewal for a first engagement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;12. "If we want to hire this engineer, what does it cost?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Good answer: a clear conversion fee or a period after which conversion is free. Some providers prohibit conversion entirely, which is their right, but you should know before you sign. Red flag: the clause is only discovered when you ask to make an offer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;13. "How will we know this is working?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Good answer: they propose measures tied to your brief, such as deployment lead time for a named service, manual release steps removed, alert volume, or a migration checklist completed, plus time logs that show where hours went. Red flag: hours logged and tickets closed as the only proof of value.&lt;/p&gt;

&lt;h2&gt;
  
  
  How staff augmentation pricing works
&lt;/h2&gt;

&lt;p&gt;Almost every staff augmentation company bills by time, but the structure around that time varies a lot. The headline rate is rarely the number that decides what you actually pay.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pricing model&lt;/th&gt;
&lt;th&gt;How it works&lt;/th&gt;
&lt;th&gt;Good for&lt;/th&gt;
&lt;th&gt;Watch out for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hourly, billed monthly&lt;/td&gt;
&lt;td&gt;You pay for hours worked, with no minimum or a small one&lt;/td&gt;
&lt;td&gt;Uneven or uncertain demand, advisory work&lt;/td&gt;
&lt;td&gt;Availability: a part-time engineer may have other clients when you need them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hours bank&lt;/td&gt;
&lt;td&gt;You prepay a block of hours and draw it down&lt;/td&gt;
&lt;td&gt;A defined project with an uneven pace&lt;/td&gt;
&lt;td&gt;Expiry dates on unused hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monthly capacity&lt;/td&gt;
&lt;td&gt;A committed monthly volume (for example, full-time) at a rate tied to that volume&lt;/td&gt;
&lt;td&gt;Ongoing embedded work and ownership&lt;/td&gt;
&lt;td&gt;Minimum terms and what happens when the engineer is on leave&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fixed monthly fee per seat&lt;/td&gt;
&lt;td&gt;One price per engineer per month regardless of hours&lt;/td&gt;
&lt;td&gt;Predictable budgets&lt;/td&gt;
&lt;td&gt;Unclear hours: you may be getting less than full time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost plus markup&lt;/td&gt;
&lt;td&gt;The engineer's cost plus a disclosed margin&lt;/td&gt;
&lt;td&gt;Large, long engagements where transparency matters&lt;/td&gt;
&lt;td&gt;Rare in practice; verify what "cost" includes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  How to compare quotes from different providers
&lt;/h3&gt;

&lt;p&gt;Rates for DevOps and platform engineers vary several times over between regions, seniority levels and provider types, so comparing headline numbers tells you little. Compare the cost of useful, senior output instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Seniority you actually get.&lt;/strong&gt; A cheaper engineer who needs a week to do what a senior does in a day is more expensive. Ask what the engineer has done before, not what level they are labelled.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ramp-up cost.&lt;/strong&gt; Who pays for the first week of onboarding, and for the ramp-up of a replacement?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Included support.&lt;/strong&gt; Is architect review, specialist backup or holiday cover included or billed separately?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Minimums and lock-in.&lt;/strong&gt; A lower rate with a six-month minimum can cost more than a higher rate you can stop after a month.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Your management time.&lt;/strong&gt; A provider that needs you to write detailed tickets for every task consumes your most expensive hours.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simple way to compare: estimate the total cost of the first three months (rate times expected hours, plus any fees and minimums), then divide it by the outcomes you expect in that period. Two quotes that differ by 30% per hour often end up close, or reversed, on that measure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5kww5kgkednezxgqbvn0.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5kww5kgkednezxgqbvn0.webp" alt="Equation diagram: rate times expected hours, plus fees, plus minimums gives the total cost of the first three months; divide it by the outcomes you expect to get cost per outcome. Quotes 30% apart per hour often end up close, or reversed." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The cheaper hourly rate is not always the cheaper quote once fees, minimums and outcomes are counted.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Also compare against hiring. An employee's salary is not their full cost: benefits, payroll taxes, equipment, recruiting fees and management time add a substantial amount on top, and an open senior role costs you months of the work not being done. Augmentation usually costs more per hour than an employee, and less than an empty seat.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contract clauses to check before you sign
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Clause&lt;/th&gt;
&lt;th&gt;What you want&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Named engineer&lt;/td&gt;
&lt;td&gt;The engineer you interviewed is named, and substitution requires your approval&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IP assignment&lt;/td&gt;
&lt;td&gt;All work product belongs to you, and the assignment flows down to individual engineers and any subcontractors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Confidentiality&lt;/td&gt;
&lt;td&gt;An NDA that covers the vendor and each individual, with a defined survival period&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Replacement&lt;/td&gt;
&lt;td&gt;A replacement timeline and no charge for the replacement's ramp-up&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Term and notice&lt;/td&gt;
&lt;td&gt;Short initial term, notice in weeks, no silent auto-renewal into a long term&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rate changes&lt;/td&gt;
&lt;td&gt;Rates fixed for a stated period, with notice before any increase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conversion&lt;/td&gt;
&lt;td&gt;A stated fee or free conversion after a period, if you may want to hire&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-solicitation&lt;/td&gt;
&lt;td&gt;Reasonable scope and duration, mutual, and not blocking conversion you have paid for&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security and compliance&lt;/td&gt;
&lt;td&gt;Your policies apply, access is individual and revocable, and the vendor supports audits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offboarding&lt;/td&gt;
&lt;td&gt;Handover of documentation and credentials, and access revoked on the last day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Liability and insurance&lt;/td&gt;
&lt;td&gt;Professional liability coverage appropriate for engineers who touch production&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None of these are exotic. A provider that resists most of them is telling you how it expects the relationship to go.&lt;/p&gt;

&lt;h2&gt;
  
  
  Red flags that should end the conversation
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;You cannot interview the engineer.&lt;/strong&gt; This is the single most reliable warning sign.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bait and switch.&lt;/strong&gt; A strong engineer in the interview and a different, weaker one on day one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;"We have someone for every technology."&lt;/strong&gt; Breadth claims with no depth behind them usually mean the vendor matches keywords on CVs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No questions about your system.&lt;/strong&gt; A provider that quotes before asking about your stack, team and constraints is selling hours, not outcomes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pressure toward a long commitment&lt;/strong&gt; before any work has been delivered.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Requests for shared or permanent admin access.&lt;/strong&gt; Good engineers ask for the least access they need.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Vague answers about subcontracting&lt;/strong&gt; or about who actually employs the person.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No references from similar companies&lt;/strong&gt;, or references they will not let you speak to.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A scorecard to compare staff augmentation companies
&lt;/h2&gt;

&lt;p&gt;Score each provider from 1 to 5 on each criterion after the interviews, multiply by the weight and add up. Adjust the weights to your situation; these suit a startup buying senior DevOps capacity.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Weight&lt;/th&gt;
&lt;th&gt;What a 5 looks like&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Engineer's technical depth (from your own interview)&lt;/td&gt;
&lt;td&gt;30%&lt;/td&gt;
&lt;td&gt;Has run your stack in production and explains tradeoffs clearly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fit with your problem and team&lt;/td&gt;
&lt;td&gt;15%&lt;/td&gt;
&lt;td&gt;Asked good questions about your brief and proposed sensible first steps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Senior backup and review&lt;/td&gt;
&lt;td&gt;15%&lt;/td&gt;
&lt;td&gt;Named architect or senior available for design reviews&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Communication and overlap&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;td&gt;Clear overlap hours, written updates, fast responses during evaluation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security practices&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;td&gt;Individual access, least privilege, clean offboarding, audit support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Commercial flexibility&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;td&gt;Short notice, fit clause, transparent rates, clear conversion terms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;References&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;td&gt;Similar companies who confirm the engineers' quality, not just the sales experience&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The engineer's interview carries the most weight on purpose. A great provider with the wrong person for your problem is still the wrong choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first 30 days with an augmented engineer
&lt;/h2&gt;

&lt;p&gt;Most failed engagements fail in the first month, usually because access was slow, priorities were unclear or nobody reviewed the work. A simple plan prevents all three.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Before day one:&lt;/strong&gt; accounts in your identity provider, repository access, chat channels, read-only cloud and cluster access, and a short list of documents and dashboards to read. Name the internal owner who sets priorities.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Week 1:&lt;/strong&gt; system walkthrough with someone who knows the history, review of infrastructure code and pipelines, and one or two small changes shipped through your normal review path. Ask for a written list of the top risks they noticed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Week 2:&lt;/strong&gt; the first meaningful item from your brief, such as removing a manual release step, tuning noisy alerts or fixing a flaky pipeline stage. Production access granted through your normal approval process if the work needs it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Week 3:&lt;/strong&gt; a larger piece of work with a written plan and rollback path, reviewed by your owner and, ideally, the provider's architect.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Week 4:&lt;/strong&gt; a review meeting. Compare progress with the brief, read the time logs, agree on the next month's priorities and decide whether to continue, change scope or stop.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxag1zxyngd6rpyf2u0ef.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxag1zxyngd6rpyf2u0ef.webp" alt="Timeline of the first 30 days with an augmented engineer: before day one access is ready and an owner is named; week 1 walkthrough and small changes shipped; week 2 the first meaningful item from the brief; week 3 larger work with a rollback path; week 4 a review to continue, change scope or stop. A weekly written update covers what shipped, what is blocked, risks and what is next." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Most failed engagements fail in month one, so plan access, priorities and review before day one.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Keep a weekly written update: what shipped, what is blocked, what risks appeared and what is next. It takes the engineer fifteen minutes and gives leadership what it needs without extra meetings.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to measure whether staff augmentation is paying off
&lt;/h2&gt;

&lt;p&gt;Measure outcomes your team would care about even without a vendor. For DevOps and platform work, the &lt;a href="https://dora.dev/guides/dora-metrics/" rel="noopener noreferrer"&gt;DORA metrics&lt;/a&gt; are a good backbone: deployment frequency, change lead time, change fail rate, failed deployment recovery time and rework rate. Add measures specific to your brief:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Manual steps removed from the release process.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Share of infrastructure changes made through reviewed IaC rather than the console.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pages per week and the share that needed action.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Time for a new developer to get a working environment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Migration or upgrade checklist items completed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Documentation produced: runbooks, architecture decision records and README updates your team actually uses.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid measuring hours, tickets closed or meetings attended. They show activity, not whether your systems became safer or faster to change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notes specific to DevOps and platform staff augmentation
&lt;/h2&gt;

&lt;p&gt;DevOps work carries more risk than most augmented roles, because the engineer works on the systems everything else depends on. A few extra checks are worth it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Infrastructure changes go through code review.&lt;/strong&gt; Terraform plans, Helm changes and pipeline edits should be reviewed pull requests, never console changes, from the first week.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;On-call is explicit.&lt;/strong&gt; Decide whether the engineer joins your rotation, what the response expectations are, and how it is billed. Do not let it happen by accident after the first incident.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bus factor is tracked.&lt;/strong&gt; If only the augmented engineer understands the new deployment pipeline, you have built a dependency on a vendor. Pair them with an internal engineer on anything critical.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Breadth matters.&lt;/strong&gt; DevOps covers Kubernetes, cloud networking, IAM (identity and access management), CI/CD, observability and cost. One engineer will not be deep in all of it, which is why access to specialist backup at the provider is valuable. If you are still defining the role, read &lt;a href="https://meteorops.com/blog/how-to-build-a-devops-team" rel="noopener noreferrer"&gt;how to build a DevOps team&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is a staff augmentation company?
&lt;/h3&gt;

&lt;p&gt;A staff augmentation company provides engineers who join your team temporarily and work under your direction. The provider employs or contracts the engineers and handles sourcing, payroll and replacement, while you set priorities and own the outcome.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between staff augmentation and managed services?
&lt;/h3&gt;

&lt;p&gt;With staff augmentation you direct the work and own the result; the provider supplies people. With managed services the provider owns an agreed outcome or service level and decides how to deliver it. Augmentation gives you control and keeps knowledge in-house; managed services reduce your management effort at the cost of control.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much does staff augmentation cost?
&lt;/h3&gt;

&lt;p&gt;Most providers bill hourly or per month per engineer, and rates vary several times over with seniority, specialism and region. Compare total cost against expected outcomes over the first few months, including minimums, ramp-up and included support, rather than headline hourly rates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is staff augmentation better than hiring a full-time engineer?
&lt;/h3&gt;

&lt;p&gt;It is better when you need senior skills quickly, the long-term need is uncertain, or the work is specialised and temporary. Hiring is better when the need is permanent and central to your product. Many companies use augmentation to cover the gap while they hire, and some convert the augmented engineer.&lt;/p&gt;

&lt;h3&gt;
  
  
  How long does it take to onboard an augmented engineer?
&lt;/h3&gt;

&lt;p&gt;A senior engineer with good access and a clear brief should ship small changes in the first week and meaningful work by the second. If it takes longer, the cause is usually slow access approvals or unclear priorities on the client side.&lt;/p&gt;

&lt;h3&gt;
  
  
  What should a staff augmentation contract include?
&lt;/h3&gt;

&lt;p&gt;At minimum: the named engineer, IP assignment that flows down to individuals, confidentiality, replacement terms, term and notice period, rate change rules, conversion terms, security obligations and offboarding. Short terms and clear exit clauses protect both sides on a first engagement.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are the risks of staff augmentation?
&lt;/h3&gt;

&lt;p&gt;The main risks are paying for capacity nobody directs, knowledge leaving with the engineer, vendor rotation of people, and over-broad access on critical systems. Each one is managed by an internal owner, enforced documentation, a named-engineer clause and least-privilege access.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;Pick a staff augmentation company by the engineer you will actually get, not by the company's pitch. Write a one-page brief, send the same brief to each provider, interview the exact person, check the contract for replacement, notice and IP terms, and run the first month against a written plan. If the answers are specific and the first weeks produce visible work, the model works. If they are vague, keep looking, or reconsider whether you need capacity or someone to own the problem.&lt;/p&gt;

&lt;p&gt;If you are looking for senior DevOps, SRE or platform engineers who work inside your team with an architect behind them, see how our &lt;a href="https://meteorops.com/services/devops-staff-augmentation" rel="noopener noreferrer"&gt;DevOps staff augmentation&lt;/a&gt; works, or compare &lt;a href="https://meteorops.com/plans-pricing" rel="noopener noreferrer"&gt;buying options&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>management</category>
      <category>leadership</category>
      <category>startup</category>
    </item>
    <item>
      <title>The CTO DevOps Handbook: Simple Principles and Examples</title>
      <dc:creator>Michael Zion</dc:creator>
      <pubDate>Thu, 29 Oct 2026 15:32:16 +0000</pubDate>
      <link>https://dev.to/meteorops/the-cto-devops-handbook-simple-principles-and-examples-m7j</link>
      <guid>https://dev.to/meteorops/the-cto-devops-handbook-simple-principles-and-examples-m7j</guid>
      <description>&lt;p&gt;&lt;em&gt;This handbook was written by &lt;a href="https://meteorops.com/team/michael-zion" rel="noopener noreferrer"&gt;Michael Zion&lt;/a&gt;, CEO at MeteorOps, and first published on the &lt;a href="https://meteorops.com/blog/the-cto-devops-handbook-simple-principles-and-examples" rel="noopener noreferrer"&gt;MeteorOps blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Nail the DevOps part as your company's CTO.&lt;/p&gt;

&lt;p&gt;The goal of this handbook is to give you clarity on DevOps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Understand what’s DevOps (in simple words)&lt;/li&gt;
&lt;li&gt;Know what’s possible with DevOps (in simple goals)&lt;/li&gt;
&lt;li&gt;Get simple “when-to-do-what” DevOps guidelines&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I added a bonus at the bottom of the article.&lt;br&gt;&lt;br&gt;
It's a production-ready setup example you could take inspiration from.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who this article is for
&lt;/h2&gt;

&lt;p&gt;You might be a founder who wishes to get started with DevOps the right way.&lt;/p&gt;

&lt;p&gt;You might be a CTO of a 1,000 employees company who wishes to get simple principles.&lt;/p&gt;

&lt;p&gt;Or, maybe you’re a Software Engineer, and you want to understand if your company’s DevOps approach is good.&lt;/p&gt;

&lt;p&gt;If you’re looking for a simple DevOps playbook, this is it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understand the desired result
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Two things your company needs to be able to do&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Serve its product to customers&lt;/li&gt;
&lt;li&gt;Build and improve the product&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Abilities you need to build, improve, and serve software&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run experiments and test changes&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  DevOps has a simple meaning
&lt;/h2&gt;

&lt;p&gt;Developers and Operators have shared responsibility for building and improving the system.&lt;/p&gt;

&lt;p&gt;In practice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Developers are responsible to “Operate”&lt;/li&gt;
&lt;li&gt;DevOps Engineers are responsible to enable to “Operate” AND do some of it themselves&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Operate&lt;/strong&gt; = provision, monitor, secure, configure, deploy, scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose a balance: Enabler, Doer, or Automator
&lt;/h2&gt;

&lt;p&gt;The DevOps role will end up as a balance between:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enabler: Provides the tools and knowledge to fulfill the DevOps goals&lt;/li&gt;
&lt;li&gt;Doer: Does the tasks that fulfill the DevOps goals&lt;/li&gt;
&lt;li&gt;Automator: Automates any repeating operation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftfr35s1j76jlfeki2a1h.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftfr35s1j76jlfeki2a1h.webp" alt="Triangle diagram of the DevOps role balanced between three modes: Enabler, who provides the tools and knowledge to fulfill the DevOps goals; Doer, who does the tasks that fulfill them; and Automator, who automates any repeating operation." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The DevOps role ends up as a balance between Enabler, Doer and Automator. Operate = provision, monitor, secure, configure, deploy, scale.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Know what things you should enable, do, or automate
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Provision infrastructure&lt;/li&gt;
&lt;li&gt;Secure the system&lt;/li&gt;
&lt;li&gt;Deploy workloads&lt;/li&gt;
&lt;li&gt;Monitor the system&lt;/li&gt;
&lt;li&gt;Recover from issues&lt;/li&gt;
&lt;li&gt;Scale up or down&lt;/li&gt;
&lt;li&gt;Track &amp;amp; test changes&lt;/li&gt;
&lt;li&gt;Automate processes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Choose the right tools
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Has state management = Saves time automating state-aware processes (e.g., &lt;a href="https://meteorops.com/technologies/terraform" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Has a big community &amp;amp; good docs = Saves time dealing with common issues (e.g., &lt;a href="https://meteorops.com/technologies/kubernetes" rel="noopener noreferrer"&gt;Kubernetes&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Has multiple interface types: API, CLI, UI = Saves time integrating with the existing system (e.g., &lt;a href="https://meteorops.com/technologies/vault" rel="noopener noreferrer"&gt;Vault&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also read about choosing tools &lt;a href="https://meteorops.com/blog/one-click-environment-the-ultimate-devops-goal#tools-choosing-principles" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set useful goals
&lt;/h3&gt;

&lt;p&gt;There are DevOps goals that adopting them will focus you on the right direction:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://meteorops.com/blog/one-click-environment-the-ultimate-devops-goal" rel="noopener noreferrer"&gt;&lt;strong&gt;One-Click Environments&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;:&lt;/strong&gt; makes e2e tests easy and quick&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Atomic Commits:&lt;/strong&gt; provides confidence that a tested change will work in production&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separate the Shared &amp;amp; Env-Specific Parts:&lt;/strong&gt; enables e2e tests as the company scales up&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you want to learn about more useful DevOps goals, feel free to book a free consultation &lt;a href="https://meteorops.com/contact" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enablers: Choose the Tools-to-Knowledge Balance
&lt;/h2&gt;

&lt;p&gt;Developers can either have the knowledge or the tools to do something.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;More knowledge-reliance:&lt;/strong&gt; if you want the developers to contribute to the DevOps efforts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More tools-reliance:&lt;/strong&gt; if you want to abstract the operations from the developers&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the balance between the two is not intentional, it’s accidental.&lt;/p&gt;

&lt;h2&gt;
  
  
  Doers: Have a good reason to do it
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Is it a one-time task?&lt;/li&gt;
&lt;li&gt;Does it teach you how the developers work?&lt;/li&gt;
&lt;li&gt;Are you directly accountable for the results of the task?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you answered “no” to the above questions, enable or automate it instead.&lt;/p&gt;

&lt;p&gt;Doing more = Learning the system's use-cases&lt;/p&gt;

&lt;p&gt;Doing too much = Not scalable, too-much knowledge-reliance&lt;/p&gt;

&lt;h2&gt;
  
  
  Automators: Have a good reason to automate it
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Did it happen before?&lt;/li&gt;
&lt;li&gt;Is it likely to happen again?&lt;/li&gt;
&lt;li&gt;Will automating it take less time than doing it?&lt;/li&gt;
&lt;li&gt;Will automating it teach you an important company process?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you answered “yes” to 2 out of the 4 questions - automate it!&lt;/p&gt;

&lt;p&gt;More automations = Less reliance on knowledge to operate the system.&lt;/p&gt;

&lt;p&gt;Too much automations = No system awareness.&lt;/p&gt;

&lt;p&gt;P.S. - you can also enable developers to automate it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fldr3h0bnt60ijm2znqex.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fldr3h0bnt60ijm2znqex.webp" alt="Two checklists side by side. Doers: is it a one-time task, does it teach you how the developers work, are you directly accountable for the results; all no means enable or automate it instead. Automators: did it happen before, is it likely to happen again, will automating it take less time than doing it, will it teach you an important company process; yes to 2 out of 4 means automate it." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The two checklists on one page: do it only with a good reason, automate it when 2 of the 4 answers are yes.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Create available DevOps Capacity
&lt;/h2&gt;

&lt;p&gt;The DevOps needs of a company have spikes.&lt;/p&gt;

&lt;p&gt;One month you need 2 DevOps Engineers, and half of that the next month.&lt;/p&gt;

&lt;p&gt;Switchovers between big efforts and small tasks are common.&lt;/p&gt;

&lt;p&gt;This is true, especially for new companies.&lt;/p&gt;

&lt;p&gt;Break the assumption: “DevOps tasks must be done by a DevOps Engineer”.&lt;/p&gt;

&lt;h2&gt;
  
  
  There are 3 types of DevOps capacity
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Non-Flexible:&lt;/strong&gt; A full-time DevOps Engineer on the team&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semi-Flexible:&lt;/strong&gt; Key developers that can contribute to the DevOps goals&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fully-Flexible:&lt;/strong&gt; A flexible DevOps Services company or freelancer&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F148vjct0tc8mw0wydexb.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F148vjct0tc8mw0wydexb.webp" alt="Chart of DevOps engineers needed per month spiking between 1 and 2, above three stacked capacity bands: Non-Flexible, a full-time DevOps engineer on the team; Semi-Flexible, key developers that can contribute to the DevOps goals; Fully-Flexible, a flexible DevOps services company or freelancer." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;DevOps needs spike month to month. Non-flexible capacity covers the base, semi-flexible and fully-flexible capacity cover the spikes.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You can read more about calculating the DevOps capacity your company needs &lt;a href="https://meteorops.com/blog/calculating-your-companys-required-devops-capacity" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to focus on what: Common Dilemmas
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;When:&lt;/strong&gt; You work alone, and the system is simple&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Focus:&lt;/strong&gt; On simplifying the development - Dockerize your apps, Create a post-commit pipeline that runs tests&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When:&lt;/strong&gt; You need to be able to create new environments quickly (for development, or for clients)&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Focus:&lt;/strong&gt; On implementing “One-Click Environments”: Using IaC (e.g., Terraform) + Deployment tool (Depends on the platform).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When:&lt;/strong&gt; You want to e2e test every code modification, but there are many code modifications&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Focus:&lt;/strong&gt; On splitting the “One-Click Env” into a “base” with shared resources, and “env” with env-specific resources&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When:&lt;/strong&gt; You want to unify &amp;amp; standardize how you deploy, monitor, scale, configure, and secure your workloads&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Focus:&lt;/strong&gt; On implementing an orchestrator such as Kubernetes&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When:&lt;/strong&gt; You want you have many moving parts and wish to be certain a tested change will work&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Focus:&lt;/strong&gt; On implementing GitOps and consider a Monorepo (the sooner the better)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When:&lt;/strong&gt; You want the DevOps efforts to be done by the dev team&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Focus:&lt;/strong&gt; On using “actual” IaC tools (Pulumi Typescript/Python), Full “how to operate” (see above) documentation&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Never: -&lt;/strong&gt; Invest lots of time in new tech without a strong reason&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;ul&gt;
&lt;li&gt;Have your code in Git&lt;/li&gt;
&lt;li&gt;Monitor the basic stuff: CPU, Memory, Disk, Network, App Logs, &lt;a href="https://meteorops.com/glossary/cloud" rel="noopener noreferrer"&gt;Cloud&lt;/a&gt; Costs&lt;/li&gt;
&lt;li&gt;Architect for high-availability&lt;/li&gt;
&lt;li&gt;Test before you deploy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn5k7lzm0jxesxdqqca66.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn5k7lzm0jxesxdqqca66.webp" alt="Roadmap with six stops, each pairing a situation with a focus: working alone, Dockerize and a post-commit pipeline that runs tests; new environments quickly, one-click environments with IaC and a deployment tool; e2e testing every change, split into a base and an env; standardizing deploy, monitor, scale, configure and secure, an orchestrator such as Kubernetes; many moving parts, GitOps and a monorepo; DevOps done by the dev team, actual IaC tools such as Pulumi and how-to-operate documentation." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The common dilemmas in the order they usually arrive, with the one thing to focus on at each.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  BONUS: An example setup for a CTO approaching Production
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Falwc6zk6agtye69mc17t.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Falwc6zk6agtye69mc17t.jpeg" alt="Architecture diagram of an example production setup: two AWS accounts, a monorepo, base and apps infrastructure-as-code projects, and a GitHub Actions workflow." width="800" height="559"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2 AWS Accounts
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;One for development and staging&lt;/li&gt;
&lt;li&gt;Another for production&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Monorepo in Github
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Docker-Compose for local development&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2 Infrastructure-as-Code projects: 'base' &amp;amp; 'apps'
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;base = shared resources (e.g., VPC, RDS, ECS Cluster, EKS Cluster)&lt;/li&gt;
&lt;li&gt;apps = env-specific resources (e.g., Lambda Functions, ECS Services, Kubernetes Namespaces)&lt;/li&gt;
&lt;li&gt;config file per environment&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Github Actions Workflow: Development workflow
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Checkout branch and locally develop + test changes&lt;/li&gt;
&lt;li&gt;Create a Pull Request: Deploys a Pull-Request ‘apps’ environment on the ‘development’ environment ‘base’&lt;/li&gt;
&lt;li&gt;On merge to main: Deploys from the ‘main’ branch an ‘apps’ environment onto the ‘development’ environment ‘base’&lt;/li&gt;
&lt;li&gt;Manual: Deploy from the ‘main’ branch onto the ‘staging’ / ‘production’ environment ‘base’&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Setup Notes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Avoid mentioning an environmnent's name in the code for conditional resources deployment&lt;/li&gt;
&lt;li&gt;Use each environment’s config file to declare if a resource should be created&lt;/li&gt;
&lt;li&gt;Could be implemented using Terraform, Terragrunt, Pulumi, CDK, and other IaC tools&lt;/li&gt;
&lt;li&gt;Production should have 2-instances of every workload for high-availability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’d like to see this setup in your startup, &lt;a href="https://meteorops.com/devops-setup-for-production-free-consultation" rel="noopener noreferrer"&gt;click here to book a call&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;P.S. - I'll be updating this page occasionally, so you might want to visit again.&lt;/p&gt;

&lt;h3&gt;
  
  
  Another Bonus: DevOps Dictionary for Human Beings
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Term&lt;/th&gt;
&lt;th&gt;Definition&lt;/th&gt;
&lt;th&gt;Tools&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Environment&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A working instance of the entire system&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CI (Continuous Integration)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Enable developers to collaborate by agreeing on a single source-of-truth (master/main)&lt;/td&gt;
&lt;td&gt;Jenkins, Github Actions, GitlabCI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CD (Continuous Delivery)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Create an artifact that’s ready for production (tested, tagged)&lt;/td&gt;
&lt;td&gt;JFrog Artifactory, Nexus, AWS ECR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CD (Continuous Deployment)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Every available deliverable (artifact) gets deployed automatically&lt;/td&gt;
&lt;td&gt;ArgoCD, Jenkins, AWS CodeDeploy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monitoring / Observability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Collect metrics/traces/logs from apps and infrastructure, analyze them, and display them, and setup alerts&lt;/td&gt;
&lt;td&gt;Prometheus, Jaeger, Elasticsearch, Fluentd, OpenTelemetry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Infrastructure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The resources on which the workloads run, in which the data is stored, and through which the network flows&lt;/td&gt;
&lt;td&gt;Servers, Databases, Network Routers &amp;amp; Switches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cloud Infrastructure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Same as the above, but specifically in the cloud&lt;/td&gt;
&lt;td&gt;AWS EC2, AWS RDS, GCP Compute Engine, Azure Virtual Machines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cloud&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Computing &amp;amp; Data services served from remote locations for you to build your system&lt;/td&gt;
&lt;td&gt;AWS, Azure, GCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Containerization &amp;amp; Virtualization&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Technologies utilizing Kernel &amp;amp; OS features to create virtual machines, or isolate process (AKA run containers)&lt;/td&gt;
&lt;td&gt;Docker, vSphere, KVM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Secrets Management&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Storing and retrieving sensitive configurations (e.g., tokens, passwords)&lt;/td&gt;
&lt;td&gt;Hashicorp Vault, AWS Secrets Manager, SealedSecrets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Configuration Management&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Usually refers to preparing servers for workloads (e.g., creating directories &amp;amp; files, starting processes)&lt;/td&gt;
&lt;td&gt;Ansible, Chef, Puppet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Version Control&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Saving the code in a versioned way (Git)&lt;/td&gt;
&lt;td&gt;Github, Gitlab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitOps&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Making the system is the same as it’s described in Git&lt;/td&gt;
&lt;td&gt;Flux, ArgoCD, Jenkins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monorepo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;All of the company’s code is in one Git Repository&lt;/td&gt;
&lt;td&gt;NX, Turborepo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Polyrepo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Multiple Git repositories for different components&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;IaC (Infrastructure-as-Code)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Creating Cloud infrastructure with &lt;strong&gt;idempotent&lt;/strong&gt; code and state management&lt;/td&gt;
&lt;td&gt;Terraform, Pulumi, CDK, Crossplane&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deployment&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Execute, serve, or install the artifacts&lt;/td&gt;
&lt;td&gt;ArgoCD, Jenkins, AWS CodeDeploy, Scripts (Bash, Python, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Orchestrator&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Dynamically allocating workloads to a pool of nodes&lt;/td&gt;
&lt;td&gt;Kubernetes, Nomad, AWS ECS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Authentication &amp;amp; Authorization&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Making sure each person, workload, or resource, has access only to what’s necessary (other workloads and resources)&lt;/td&gt;
&lt;td&gt;AWS IAM, OpenID, OpenVPN, Twingate, Istio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Service Discovery&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Exposing available workloads using DNS&lt;/td&gt;
&lt;td&gt;Consul, CoreDNS&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>devops</category>
      <category>management</category>
      <category>leadership</category>
      <category>startup</category>
    </item>
    <item>
      <title>GitOps Benefits, Challenges, and When to Use It</title>
      <dc:creator>Michael Zion</dc:creator>
      <pubDate>Wed, 28 Oct 2026 15:31:43 +0000</pubDate>
      <link>https://dev.to/meteorops/gitops-benefits-challenges-and-when-to-use-it-4832</link>
      <guid>https://dev.to/meteorops/gitops-benefits-challenges-and-when-to-use-it-4832</guid>
      <description>&lt;p&gt;&lt;em&gt;This guide was written by &lt;a href="https://meteorops.com/team/michael-zion" rel="noopener noreferrer"&gt;Michael Zion&lt;/a&gt;, CEO at MeteorOps, and first published on the &lt;a href="https://meteorops.com/blog/why-when-to-use-gitops-use-cases-principles" rel="noopener noreferrer"&gt;MeteorOps blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;GitOps is a way of running deployments where the desired state of your systems lives in Git, and an agent inside your environment continuously pulls that state and makes reality match it. The real benefits are concrete: every production change is a reviewed commit, rollback is a &lt;code&gt;git revert&lt;/code&gt;, manual drift gets corrected automatically, your CI system no longer needs admin credentials to your clusters, and a lost cluster can be rebuilt from a repository.&lt;/p&gt;

&lt;p&gt;The real costs are just as concrete. You have to solve secrets, environment promotion and repository layout yourself; tools like the Horizontal Pod Autoscaler (HPA) will fight your sync controller until you configure around them; and debugging a failed sync adds a layer between a developer and their running pod. GitOps pays off for teams running several services on Kubernetes across more than one environment. It is usually not worth it for a single app on a managed platform, or for a team without anyone who will own the setup.&lt;/p&gt;

&lt;p&gt;This guide covers what GitOps actually is (using the OpenGitOps principles, not vendor definitions), each benefit and what it takes to get it, the challenges teams hit in practice, a decision table for when to use it and when not to, Argo CD vs Flux, a worked example, an adoption path, and how to measure whether it worked.&lt;/p&gt;

&lt;h2&gt;
  
  
  What GitOps is: the four OpenGitOps principles
&lt;/h2&gt;

&lt;p&gt;"GitOps" has been stretched by marketing to mean almost anything involving Git. The useful definition comes from &lt;a href="https://opengitops.dev/" rel="noopener noreferrer"&gt;OpenGitOps&lt;/a&gt;, a CNCF working group that published four principles (version 1.0.0):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Declarative.&lt;/strong&gt; "A system managed by GitOps must have its desired state expressed declaratively." You describe what should exist (Kubernetes manifests, Helm values, Kustomize overlays), not the commands to create it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Versioned and Immutable.&lt;/strong&gt; "Desired state is stored in a way that enforces immutability, versioning and retains a complete version history." In practice this is Git, though OCI registries also qualify.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pulled Automatically.&lt;/strong&gt; "Software agents automatically pull the desired state declarations from the source." The agent runs next to the target system and fetches changes; nothing outside pushes into it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Continuously Reconciled.&lt;/strong&gt; "Software agents continuously observe actual system state and attempt to apply the desired state." This is the part most "GitOps" pipelines skip.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The last two principles are what separate GitOps from "we keep our YAML in Git and CI runs &lt;code&gt;kubectl apply&lt;/code&gt;". A pipeline that pushes manifests on merge gives you versioning, but it applies changes once and then stops looking. If someone edits a Deployment by hand an hour later, nothing notices. A GitOps agent (Argo CD or Flux, in almost every real setup) notices and either reports the drift or reverts it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3uloyqc656gypk9921ra.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3uloyqc656gypk9921ra.webp" alt="Two timelines compared: a push pipeline merges and CI runs kubectl apply once, so a manual edit an hour later goes unnoticed; a GitOps agent continuously observes and applies, so a manual edit is reported or reverted as drift." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Pushing manifests on merge applies them once; only continuous reconciliation catches drift afterwards.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  GitOps vs DevOps vs CI/CD
&lt;/h3&gt;

&lt;p&gt;DevOps is a culture and set of practices for shared ownership of delivery and operations. CI/CD (continuous integration and continuous delivery) is the automation that builds, tests and ships code. GitOps is one specific way to implement the deployment half of CD: CI still builds and tests your code and pushes an image; GitOps takes over from "the new image exists" to "the cluster runs it". It does not replace your CI system and it is not an alternative to DevOps.&lt;/p&gt;
&lt;h2&gt;
  
  
  The real benefits of GitOps (and what each one requires)
&lt;/h2&gt;

&lt;p&gt;Most benefit lists are written as if you get everything for free once you install Argo CD. You do not. Each benefit below comes with the condition that makes it real.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. An audit trail you get for free
&lt;/h3&gt;

&lt;p&gt;Every change to production is a commit with an author, a timestamp, a pull request, reviewers and a diff. When someone asks "what changed before the incident at 14:05?", the answer is &lt;code&gt;git log&lt;/code&gt; on the environment path, not a search through CI logs and Slack. For teams going through SOC 2 or ISO 27001, this gives auditors change management evidence (who approved what, when) without a separate ticketing ritual.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it requires:&lt;/strong&gt; branch protection with required reviews on the environment repository or path, and no other way to change production. If engineers still have write access with &lt;code&gt;kubectl&lt;/code&gt;, your audit trail has a hole exactly where incidents happen.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Rollback is &lt;code&gt;git revert&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Because the cluster follows Git, reverting the commit that bumped an image tag or changed a config value rolls the environment back through the same reviewed, logged path as any deployment. There is no special rollback runbook to remember during an incident.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it requires:&lt;/strong&gt; deployments must be expressed as a single, self-contained change (one image tag bump, one values change). If a release is spread across several commits in different repositories, a revert is no longer one action. Also note that Argo CD's own UI rollback is disabled for Applications with automated sync; the &lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/auto_sync/" rel="noopener noreferrer"&gt;Argo CD docs&lt;/a&gt; state that "Rollback cannot be performed against an application with automated sync enabled." Git revert is the rollback. Database migrations are not rolled back by reverting a manifest, so design migrations to be backward compatible.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Drift detection and correction
&lt;/h3&gt;

&lt;p&gt;Drift is the gap between what you think is running and what is running: a hotfixed ConfigMap, a replica count someone scaled by hand, a resource deleted by mistake. A reconciling agent detects this continuously. With Argo CD's &lt;code&gt;selfHeal&lt;/code&gt; or Flux's default behaviour of re-applying on every interval, the agent reverts it. This is the single biggest operational difference between GitOps and push-based CD.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it requires:&lt;/strong&gt; deciding what the agent should own and what it should ignore (see the HPA challenge below). Drift correction that fights a legitimate controller is worse than no drift correction.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. A better security model: pull-based delivery
&lt;/h3&gt;

&lt;p&gt;In push-based CD, your CI system (GitHub Actions, GitLab CI, Jenkins) holds credentials that can modify production clusters. CI runners execute arbitrary code from pull requests and third-party actions, so they are a high-value target. In pull-based GitOps, the agent runs inside the cluster with in-cluster permissions, and CI only needs permission to push an image and, in some setups, to open a commit or pull request against the config repository. The cluster API server does not need to be reachable from your CI provider at all.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr8lrr9xlyveug5l0vfx1.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr8lrr9xlyveug5l0vfx1.webp" alt="Comparison of security models: in push-based CD the CI system holds cluster credentials and pushes into the cluster, making it a high-value target; in pull-based GitOps CI only pushes to the image registry and config repo with no cluster credentials, while the GitOps agent inside the cluster uses in-cluster permissions." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;With pull-based delivery, the credentials that can change production never leave the cluster.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it requires:&lt;/strong&gt; actually removing the old CI credentials and human admin access. Teams often add GitOps alongside their existing &lt;code&gt;kubectl&lt;/code&gt; pipeline "temporarily" and end up with two attack surfaces instead of one. Also remember that write access to the config repository now effectively equals deploy access, so protect it like production.&lt;/p&gt;
&lt;h3&gt;
  
  
  5. Self-service for developers, with guardrails
&lt;/h3&gt;

&lt;p&gt;Once the platform team has defined how apps are laid out, a developer ships a new service or changes a resource limit by opening a pull request against a known path. Review rules, policy checks in CI (schema validation, Open Policy Agent or Kyverno policies) and CODEOWNERS files decide what needs platform approval. This is how a small platform team supports many product engineers without becoming a ticket queue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it requires:&lt;/strong&gt; a repository structure and templates that developers can understand. If adding a service means touching six files across three directories, self-service will not happen.&lt;/p&gt;
&lt;h3&gt;
  
  
  6. Disaster recovery and cluster rebuilds
&lt;/h3&gt;

&lt;p&gt;If everything that runs in a cluster is declared in Git, rebuilding a cluster is: create the cluster, install the GitOps agent, point it at the repository, wait. The same mechanism lets you spin up a new region or a full preview environment, which is the foundation of the &lt;a href="https://meteorops.com/blog/one-click-environment-the-ultimate-devops-goal" rel="noopener noreferrer"&gt;one-click environment&lt;/a&gt; idea.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it requires:&lt;/strong&gt; really everything in Git, including cluster add-ons (ingress controller, cert-manager, external-dns, monitoring). Persistent data (databases, volumes) is not restored by GitOps; you still need backups. Test the rebuild before you need it, because the first attempt usually exposes a resource someone created by hand two years ago.&lt;/p&gt;
&lt;h3&gt;
  
  
  7. Faster, more frequent, less scary deployments
&lt;/h3&gt;

&lt;p&gt;When deploying is a small pull request and rollback is a revert, teams deploy smaller changes more often. That is the mechanism by which GitOps moves DORA metrics, discussed at the end of this guide. GitOps does not make your tests better or your services more reliable; it removes friction and fear from the deployment step.&lt;/p&gt;
&lt;h2&gt;
  
  
  GitOps challenges: what teams actually struggle with
&lt;/h2&gt;

&lt;p&gt;These are the problems that show up in the first three to six months of running GitOps in production. None of them is a reason to avoid GitOps, but each needs a deliberate decision.&lt;/p&gt;
&lt;h3&gt;
  
  
  Secrets management
&lt;/h3&gt;

&lt;p&gt;You cannot commit plaintext secrets to Git, yet GitOps says the desired state lives in Git. There are three common answers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reference, do not store:&lt;/strong&gt; External Secrets Operator or the Secrets Store CSI Driver. Git holds a pointer ("this secret comes from AWS Secrets Manager key X"); an in-cluster controller fetches the value. This is usually the best default.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Encrypt in Git:&lt;/strong&gt; Sealed Secrets, or SOPS with age or a cloud KMS key. Flux decrypts SOPS natively through the Kustomization &lt;code&gt;spec.decryption&lt;/code&gt; field.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Inject at render time&lt;/strong&gt; with a plugin such as argocd-vault-plugin. The &lt;a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/secret-management/" rel="noopener noreferrer"&gt;Argo CD secret management docs&lt;/a&gt; "strongly caution against this style", partly because "Argo CD stores generated manifests in plaintext in its Redis cache", and recommend managing secrets on the destination cluster instead.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Environment promotion
&lt;/h3&gt;

&lt;p&gt;GitOps tells you how to make an environment match Git. It does not tell you how a change moves from dev to staging to production. Common patterns are a directory per environment with Kustomize overlays (promotion is a pull request that copies a tag from one overlay to the next), a values file per environment for Helm, or automation that opens the promotion pull request after staging checks pass. Avoid a long-lived Git branch per environment: branches drift apart, merges carry unrelated changes, and nobody can easily answer "what is different between staging and prod?". Directories make that question a &lt;code&gt;diff&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Repository structure
&lt;/h3&gt;

&lt;p&gt;Monorepo or one config repo per team? App code and manifests together or separate? There is no universal answer, but two rules hold. First, keep deployable configuration separate from application source when CI writes back to it, otherwise image-bump commits trigger new CI builds and pollute history. Second, decide early how cluster add-ons, shared platform config and app config are separated, because moving hundreds of resources between paths later means carefully orchestrated pruning.&lt;/p&gt;
&lt;h3&gt;
  
  
  Drift vs controllers that legitimately change state (HPA and friends)
&lt;/h3&gt;

&lt;p&gt;The HPA changes &lt;code&gt;spec.replicas&lt;/code&gt; on your Deployment. If Git also sets &lt;code&gt;replicas: 3&lt;/code&gt; and self-heal is on, the GitOps agent and the HPA fight, and you get replica counts snapping back on every sync. The fix is to stop declaring replicas for autoscaled workloads, and tell the agent to ignore the field. In Argo CD that is &lt;code&gt;ignoreDifferences&lt;/code&gt; on &lt;code&gt;/spec/replicas&lt;/code&gt;, plus the &lt;code&gt;RespectIgnoreDifferences=true&lt;/code&gt; sync option so the ignored field is also left alone during sync, not only in the diff (both documented in the &lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/sync-options/" rel="noopener noreferrer"&gt;Argo CD sync options&lt;/a&gt;). The same applies to fields mutated by admission webhooks, operators and service meshes. We cover the autoscaling side in &lt;a href="https://meteorops.com/blog/how-to-configure-kubernetes-hpa-without-causing-scaling-flaps" rel="noopener noreferrer"&gt;how to configure Kubernetes HPA without causing scaling flaps&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7x93c9y97j9wtn93qqn2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7x93c9y97j9wtn93qqn2.webp" alt="Left: Git declares replicas: 3 with self-heal on while the HPA also writes spec.replicas, so the count snaps back on every sync. Right: the fix removes replicas from Git, sets ignoreDifferences on /spec/replicas with RespectIgnoreDifferences=true, and lets the HPA own replicas." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Stop declaring replicas for autoscaled workloads and tell the agent to ignore the field.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Debugging sync failures
&lt;/h3&gt;

&lt;p&gt;Before GitOps, a failed deploy showed up red in the CI job the developer was watching. After GitOps, CI is green (it pushed an image and a commit), and the failure happens asynchronously in the cluster: a schema error, a missing CRD, a failing health check, an immutable field change. Developers need to know where to look. Wire the agent's notifications (Argo CD Notifications or the Flux notification-controller) into the team's channel and into commit statuses, and give developers read access to the Argo CD UI or Flux status, otherwise every failed sync becomes a platform team ticket.&lt;/p&gt;
&lt;h3&gt;
  
  
  Separating CI from CD
&lt;/h3&gt;

&lt;p&gt;The clean split is: CI builds, tests, scans and pushes an image, then records the new tag in the config repository (directly or through a pull request). The GitOps agent deploys. Teams get into trouble when CI keeps doing deployment-shaped work (running &lt;code&gt;helm upgrade&lt;/code&gt; "just for this one service", running migrations with cluster credentials), because that reintroduces push access and splits the source of truth.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg57o8zjduxcsn9zxffh2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg57o8zjduxcsn9zxffh2.webp" alt="Flow diagram: CI builds, tests, scans, pushes an image and records the new tag in the config repo; the GitOps agent reads the config repo and deploys to the cluster; promotion from overlays/dev to overlays/production is a pull request." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;CI ends at a commit to the config repo; everything after that belongs to the agent.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Image update automation
&lt;/h3&gt;

&lt;p&gt;Someone has to write the new image tag into Git. Options, from simplest to most automated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;CI commits the tag (for example with &lt;code&gt;kustomize edit set image&lt;/code&gt; or &lt;code&gt;yq&lt;/code&gt;) to the dev overlay after a successful build. Simple, explicit, and works with any agent.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Flux image automation: &lt;code&gt;ImageRepository&lt;/code&gt;, &lt;code&gt;ImagePolicy&lt;/code&gt; and &lt;code&gt;ImageUpdateAutomation&lt;/code&gt; (API &lt;code&gt;image.toolkit.fluxcd.io/v1&lt;/code&gt;) scan a registry and commit tag updates to lines marked with a comment like &lt;code&gt;# {"$imagepolicy": "flux-system:podinfo"}&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Argo CD Image Updater, a separate project. Its &lt;a href="https://argocd-image-updater.readthedocs.io/en/stable/" rel="noopener noreferrer"&gt;docs&lt;/a&gt; describe it as under active development and invite users to test it on non-critical environments, so treat it accordingly for production.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Multi-cluster and fleet management
&lt;/h3&gt;

&lt;p&gt;One cluster is easy. Ten clusters across regions and environments raise new questions: one central Argo CD managing all clusters (a single pane of glass, but a single, highly privileged control plane) or an agent per cluster (more isolation, more to operate)? How do you template the same add-on across clusters with small differences? Argo CD answers the templating question with ApplicationSets; Flux with Kustomize overlays per cluster path and, increasingly, the Flux Operator's ResourceSets. Pick one model deliberately before cluster number three.&lt;/p&gt;
&lt;h3&gt;
  
  
  Ordering, CRDs and bootstrap
&lt;/h3&gt;

&lt;p&gt;A CRD must exist before resources that use it; a namespace before what lives in it; cert-manager before Certificates. Flux expresses this with &lt;code&gt;dependsOn&lt;/code&gt; between Kustomizations; Argo CD with sync waves and sync phases. Expect to spend real time on bootstrap ordering the first time you rebuild a cluster from scratch.&lt;/p&gt;
&lt;h2&gt;
  
  
  When to use GitOps and when not to
&lt;/h2&gt;

&lt;p&gt;GitOps is most valuable when there is a lot of state to keep consistent and more than a handful of people changing it. Use this table as a starting point.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Recommendation&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1 to 3 engineers, one app on a PaaS (Vercel, Heroku, Cloud Run, App Runner)&lt;/td&gt;
&lt;td&gt;Skip GitOps&lt;/td&gt;
&lt;td&gt;The platform already gives you declarative deploys and rollback. A Kubernetes GitOps stack would be overhead without a cluster to reconcile.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Small team, one Kubernetes cluster, a few services&lt;/td&gt;
&lt;td&gt;Adopt, but keep it minimal&lt;/td&gt;
&lt;td&gt;One agent, one config repo, plain manifests or Kustomize. You get audit, rollback and DR cheaply. Avoid ApplicationSets and fancy promotion until you need them.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5 to 50 engineers, multiple services, dev/staging/prod on Kubernetes&lt;/td&gt;
&lt;td&gt;Strong fit&lt;/td&gt;
&lt;td&gt;This is where drift, "who changed what" and slow manual deploys start to hurt. Self-service and promotion via pull requests pay back quickly.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiple clusters, regions or tenants; platform team serving product teams&lt;/td&gt;
&lt;td&gt;Strong fit, invest in fleet tooling&lt;/td&gt;
&lt;td&gt;Templated add-ons, cluster bootstrap from Git and centralized visibility become essential.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Regulated environment (SOC 2, ISO 27001, PCI)&lt;/td&gt;
&lt;td&gt;Strong fit&lt;/td&gt;
&lt;td&gt;Reviewed commits are change-management evidence, and removing CI cluster credentials shrinks the attack surface.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mostly serverless or managed services, little or no Kubernetes&lt;/td&gt;
&lt;td&gt;Use Infrastructure as Code with PR-based plans instead&lt;/td&gt;
&lt;td&gt;Terraform or OpenTofu with plan-on-PR and apply-on-merge gives most of the audit benefit. Continuous reconciliation is possible (for example via Crossplane) but is rarely the first problem to solve.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No one available to own the setup&lt;/td&gt;
&lt;td&gt;Wait&lt;/td&gt;
&lt;td&gt;A half-configured GitOps setup (secrets unsolved, drift fights, nobody reading sync errors) is worse than a simple, well-understood pipeline.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A useful smell test: if your team can't answer "what exactly is running in production right now, and who approved it?" in under a minute, GitOps will help. If you can, and deploys are not painful, it may not be your highest-leverage project. Our guide to &lt;a href="https://meteorops.com/blog/calculating-your-companys-required-devops-capacity" rel="noopener noreferrer"&gt;calculating your company's required DevOps capacity&lt;/a&gt; helps decide whether you have the people to own it.&lt;/p&gt;
&lt;h3&gt;
  
  
  Can GitOps manage cloud infrastructure, not just Kubernetes apps?
&lt;/h3&gt;

&lt;p&gt;Yes, if the infrastructure is expressed as Kubernetes resources that a controller reconciles: Crossplane, or cloud-specific controllers such as AWS Controllers for Kubernetes and Google Config Connector. See &lt;a href="https://meteorops.com/blog/deploy-aws-resources-using-crossplane-on-kubernetes" rel="noopener noreferrer"&gt;deploying AWS resources using Crossplane on Kubernetes&lt;/a&gt;. For most startups, Terraform with pull-request plans is the pragmatic choice for cloud resources, with GitOps handling what runs inside the clusters.&lt;/p&gt;
&lt;h2&gt;
  
  
  Argo CD vs Flux: which GitOps tool to choose
&lt;/h2&gt;

&lt;p&gt;Both are CNCF graduated projects (Flux graduated in November 2022, Argo in December 2022), both are production grade, and both implement the four principles. The difference is mostly in operating model and user experience.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Argo CD&lt;/th&gt;
&lt;th&gt;Flux&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Core model&lt;/td&gt;
&lt;td&gt;An &lt;code&gt;Application&lt;/code&gt; resource points at a source (Git, Helm repo or OCI) and a destination cluster and namespace.&lt;/td&gt;
&lt;td&gt;A set of controllers: sources (&lt;code&gt;GitRepository&lt;/code&gt;, &lt;code&gt;OCIRepository&lt;/code&gt;, &lt;code&gt;HelmRepository&lt;/code&gt;) plus appliers (&lt;code&gt;Kustomization&lt;/code&gt;, &lt;code&gt;HelmRelease&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI&lt;/td&gt;
&lt;td&gt;Rich built-in web UI showing resource trees, diffs, sync status and logs. A big reason teams pick it.&lt;/td&gt;
&lt;td&gt;No UI in core Flux. The Flux Operator ships a Flux Web UI (status dashboard with OIDC and RBAC); many teams use the CLI and notifications.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-cluster&lt;/td&gt;
&lt;td&gt;Commonly one central instance managing many clusters; ApplicationSets generate Applications per cluster, per directory or per pull request.&lt;/td&gt;
&lt;td&gt;Commonly one Flux per cluster, each bootstrapped from its own path in Git. Can also target remote clusters via kubeconfig.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-tenancy&lt;/td&gt;
&lt;td&gt;AppProjects restrict which repos, clusters, namespaces and resource kinds a team can deploy, plus SSO and Argo CD RBAC.&lt;/td&gt;
&lt;td&gt;Relies on Kubernetes RBAC: each Kustomization can impersonate a service account scoped to a tenant's namespaces.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Helm&lt;/td&gt;
&lt;td&gt;Renders charts with &lt;code&gt;helm template&lt;/code&gt; and applies the output; Helm release history is not used.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;HelmRelease&lt;/code&gt; performs real Helm installs and upgrades, with Helm hooks, tests and rollback remediation.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secrets&lt;/td&gt;
&lt;td&gt;Bring your own (External Secrets, Sealed Secrets, CSI driver).&lt;/td&gt;
&lt;td&gt;Native SOPS decryption in Kustomizations, or bring your own.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image automation&lt;/td&gt;
&lt;td&gt;Argo CD Image Updater (separate project, still maturing).&lt;/td&gt;
&lt;td&gt;Built-in image automation controllers.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Change detection&lt;/td&gt;
&lt;td&gt;Polls repos every 3 minutes by default (120s plus up to 60s jitter), plus webhooks.&lt;/td&gt;
&lt;td&gt;Per-object &lt;code&gt;interval&lt;/code&gt;, plus webhook receivers via notification-controller.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Progressive delivery&lt;/td&gt;
&lt;td&gt;Argo Rollouts (canary, blue-green).&lt;/td&gt;
&lt;td&gt;Flagger.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Our default advice:&lt;/strong&gt; if developers will interact with deployments and you want them to see what is happening without learning kubectl, pick Argo CD. If you want a lightweight, Kubernetes-native agent per cluster, heavy Helm usage with real Helm lifecycle, native SOPS and built-in image automation, pick Flux. Both are fine choices; the bigger risk is spending weeks choosing. For canary and blue-green on top of Argo CD, see &lt;a href="https://meteorops.com/blog/how-to-deploy-zero-downtime-kubernetes-releases-with-helm-and-argo-rollouts" rel="noopener noreferrer"&gt;zero-downtime Kubernetes releases with Helm and Argo Rollouts&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  A worked GitOps example: repo layout and manifests
&lt;/h2&gt;

&lt;p&gt;Here is a minimal, realistic setup for one service with dev and production environments, using Kustomize overlays. The same layout works with either tool.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;platform-config/
  apps/
    checkout-api/
      base/
        deployment.yaml
        service.yaml
        hpa.yaml
        kustomization.yaml
      overlays/
        dev/
          kustomization.yaml      # image tag for dev, smaller resources
        production/
          kustomization.yaml      # image tag for prod, prod resources
  clusters/
    dev/
      checkout-api.yaml           # Argo CD Application or Flux Kustomization
    production/
      checkout-api.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The production overlay pins the image tag. Promotion to production is a pull request that changes &lt;code&gt;newTag&lt;/code&gt; here, and rollback is reverting that pull request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# apps/checkout-api/overlays/production/kustomization.yaml&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kustomize.config.k8s.io/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Kustomization&lt;/span&gt;
&lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;../../base&lt;/span&gt;
&lt;span class="na"&gt;images&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/acme/checkout-api&lt;/span&gt;
    &lt;span class="na"&gt;newTag&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.42.0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Option A: Argo CD Application
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argoproj.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Application&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkout-api-production&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argocd&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
  &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;repoURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/acme/platform-config.git&lt;/span&gt;
    &lt;span class="na"&gt;targetRevision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/checkout-api/overlays/production&lt;/span&gt;
  &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://kubernetes.default.svc&lt;/span&gt;
    &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkout&lt;/span&gt;
  &lt;span class="na"&gt;ignoreDifferences&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps&lt;/span&gt;
      &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
      &lt;span class="na"&gt;jsonPointers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/spec/replicas&lt;/span&gt;          &lt;span class="c1"&gt;# owned by the HPA, not Git&lt;/span&gt;
  &lt;span class="na"&gt;syncPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;automated&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;prune&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;                 &lt;span class="c1"&gt;# delete resources removed from Git&lt;/span&gt;
      &lt;span class="na"&gt;selfHeal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;              &lt;span class="c1"&gt;# revert manual changes in the cluster&lt;/span&gt;
    &lt;span class="na"&gt;syncOptions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CreateNamespace=true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;RespectIgnoreDifferences=true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes: &lt;code&gt;prune&lt;/code&gt; and &lt;code&gt;selfHeal&lt;/code&gt; are both off by default in Argo CD, so drift correction only happens if you turn them on. In real setups, replace &lt;code&gt;project: default&lt;/code&gt; with a dedicated AppProject that limits which repos and namespaces this team can deploy to.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option B: Flux GitRepository and Kustomization
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;source.toolkit.fluxcd.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GitRepository&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;platform-config&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flux-system&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1m&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/acme/platform-config.git&lt;/span&gt;
  &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kustomize.toolkit.fluxcd.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Kustomization&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkout-api&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flux-system&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10m&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./apps/checkout-api/overlays/production&lt;/span&gt;
  &lt;span class="na"&gt;prune&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;sourceRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GitRepository&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;platform-config&lt;/span&gt;
  &lt;span class="na"&gt;targetNamespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkout&lt;/span&gt;
  &lt;span class="na"&gt;wait&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3m&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes: Flux re-applies the desired state on every &lt;code&gt;interval&lt;/code&gt;, which corrects drift. For a private repository, add a &lt;code&gt;secretRef&lt;/code&gt; on the GitRepository. For the HPA case, the simplest Flux-side fix is to leave &lt;code&gt;replicas&lt;/code&gt; out of the Deployment manifest entirely, so the HPA owns it. Add &lt;code&gt;dependsOn&lt;/code&gt; if this app needs, for example, cert-manager to be ready first.&lt;/p&gt;

&lt;p&gt;Whichever tool you use, add health checks that mean something. A sync that "succeeds" with pods in a restart loop is a false green; see &lt;a href="https://meteorops.com/blog/how-to-configure-kubernetes-liveness-probes-without-causing-restart-loops" rel="noopener noreferrer"&gt;how to configure Kubernetes liveness probes without causing restart loops&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical GitOps adoption path: from first app to fleet
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pick one non-critical service and one environment.&lt;/strong&gt; Install Argo CD or Flux, point it at a config repo, and deploy that service with automated sync. Goal: the team sees a merge turn into a deployment in a few minutes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Solve secrets before the second service.&lt;/strong&gt; Choose External Secrets Operator, Sealed Secrets or SOPS and document the pattern. This is the decision teams most regret postponing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Move cluster add-ons into Git.&lt;/strong&gt; Ingress controller, cert-manager, external-dns, metrics and logging agents. Now the cluster itself is reproducible.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add environments and promotion.&lt;/strong&gt; Overlays per environment, CI writes the dev tag, promotion to staging and production by pull request. Add notifications and commit statuses so developers see sync results.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Turn on self-heal and remove manual access.&lt;/strong&gt; Once teams trust the flow, enable self-heal, remove CI cluster credentials and reduce human write access to break-glass roles with audit logging.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prove the rebuild.&lt;/strong&gt; Create a fresh cluster from Git and measure how long it takes to serve traffic. Fix whatever was missing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scale to the fleet.&lt;/strong&gt; ApplicationSets or per-cluster Flux paths, policy checks in CI, preview environments per pull request if useful, progressive delivery for critical services.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd53eu2zlo91g4e6m49nk.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd53eu2zlo91g4e6m49nk.webp" alt="A seven-step staircase for adopting GitOps: one service and one environment, solve secrets, cluster add-ons in Git, environments and promotion, self-heal and remove manual access, prove the rebuild, scale to the fleet." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Each step builds the trust and coverage the next one depends on.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Common GitOps mistakes and how to fix them
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Keeping kubectl write access "for emergencies" for everyone.&lt;/strong&gt; Fix: a break-glass role that is audited and rarely used; emergencies go through a fast-tracked pull request.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A branch per environment.&lt;/strong&gt; Fix: directories or overlays per environment on one branch.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Using the &lt;code&gt;latest&lt;/code&gt; tag.&lt;/strong&gt; Git does not change when the image does, so there is no audit trail and no revert. Fix: immutable tags or digests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Declaring replicas for autoscaled workloads.&lt;/strong&gt; Fix: remove them and ignore the field in the agent.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Turning on prune on day one without a review.&lt;/strong&gt; A path mistake can delete a namespace's worth of resources. Fix: enable prune after the layout is stable, and protect critical resources (Argo CD supports a &lt;code&gt;Prune=false&lt;/code&gt; sync option per resource).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Nobody watching sync status.&lt;/strong&gt; Fix: notifications to the owning team's channel and failing commit statuses.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Secrets via render-time plugins.&lt;/strong&gt; Fix: move to destination-cluster secret management as Argo CD recommends.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to measure GitOps value with DORA metrics
&lt;/h2&gt;

&lt;p&gt;GitOps should move delivery metrics, and you should measure before and after. &lt;a href="https://dora.dev/guides/dora-metrics/" rel="noopener noreferrer"&gt;DORA&lt;/a&gt; currently defines five software delivery metrics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Change lead time:&lt;/strong&gt; time from commit to running in production. Watch whether deployment queues and manual steps disappear.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Deployment frequency:&lt;/strong&gt; how often you deploy. Smaller, routine pull request deploys should increase it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Failed deployment recovery time:&lt;/strong&gt; how long it takes to restore service after a failed deployment. Revert-based rollback should cut this sharply.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Change fail rate:&lt;/strong&gt; share of deployments that need urgent fixing (rollback or hotfix).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Deployment rework rate:&lt;/strong&gt; share of deployments that were unplanned and caused by a production incident.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The commit history in your config repository plus the agent's sync events give you most of the raw data: commit time, sync time, reverts and their timestamps. Add two GitOps-specific health signals: how often drift is detected (it should trend toward zero as manual access disappears) and how many syncs fail (it should be low and owned by the team that made the change, not the platform team).&lt;/p&gt;

&lt;h2&gt;
  
  
  GitOps implementation checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Desired state for apps and cluster add-ons is in Git, in a structure developers understand.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Branch protection and required reviews on environment paths; CODEOWNERS for platform-owned paths.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;One secrets approach, documented, with no plaintext secrets in Git.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Immutable image tags; CI writes tags, the agent deploys.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Promotion between environments is a pull request.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fields owned by other controllers (HPA replicas, webhook-injected fields) are ignored by the agent.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sync notifications reach the owning team; developers can see sync status and logs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Self-heal and prune enabled deliberately; critical resources protected.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CI has no cluster admin credentials; human write access is break-glass only.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A cluster rebuild from Git has been tested and timed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DORA baseline captured before rollout.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When outside GitOps help makes sense
&lt;/h2&gt;

&lt;p&gt;Most teams can install Argo CD or Flux in an afternoon. What takes months is the design around it: repository structure, secrets, promotion, multi-cluster, and migrating existing services without downtime. Bringing in experienced engineers makes sense when you are migrating a large number of existing services, moving to multiple clusters or regions, preparing for a compliance audit, or when the people who would own GitOps are already fully booked on product work. The goal of any good engagement should be a setup your own team can run and extend afterwards, not a dependency.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitOps FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What are the main benefits of GitOps?
&lt;/h3&gt;

&lt;p&gt;A complete audit trail of production changes, rollback by reverting a commit, automatic detection and correction of drift, a safer pull-based security model where CI holds no cluster credentials, developer self-service through pull requests, and the ability to rebuild clusters from Git. Each benefit depends on removing other ways to change production.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the biggest disadvantage of GitOps?
&lt;/h3&gt;

&lt;p&gt;The added complexity around the edges: secrets, environment promotion, repository structure and debugging asynchronous sync failures all need deliberate design. For a small team with one app on a managed platform, that overhead can outweigh the benefits.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are the four principles of GitOps?
&lt;/h3&gt;

&lt;p&gt;According to OpenGitOps: the desired state is declarative; it is versioned and immutable; it is pulled automatically by software agents; and it is continuously reconciled against the actual state.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is GitOps only for Kubernetes?
&lt;/h3&gt;

&lt;p&gt;The principles are not Kubernetes specific, but the mature tooling (Argo CD, Flux) is built for Kubernetes. Cloud resources can be managed the GitOps way through Kubernetes-based controllers such as Crossplane, while many teams use Terraform with pull-request plans for infrastructure instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is GitOps the same as CI/CD?
&lt;/h3&gt;

&lt;p&gt;No. CI builds and tests code and produces artifacts. GitOps is a way to do the deployment part of CD, where an agent pulls the desired state from Git and reconciles the environment. You still need CI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I use Argo CD or Flux?
&lt;/h3&gt;

&lt;p&gt;Choose Argo CD if you want a strong UI and a central control plane for many clusters and teams. Choose Flux if you prefer a lightweight per-cluster agent, native Helm lifecycle, built-in SOPS decryption and image automation. Both are CNCF graduated and production ready.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you roll back with GitOps?
&lt;/h3&gt;

&lt;p&gt;Revert the commit (or pull request) that introduced the change, and the agent syncs the environment back. In Argo CD, UI rollback is disabled for Applications with automated sync, so Git revert is the intended path. Database schema changes need to be backward compatible because reverting manifests does not undo migrations.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you handle secrets in GitOps?
&lt;/h3&gt;

&lt;p&gt;Either store a reference in Git and let External Secrets Operator or the Secrets Store CSI Driver fetch the value from a secret manager, or store encrypted secrets with Sealed Secrets or SOPS. Avoid render-time injection plugins, which Argo CD's own docs caution against.&lt;/p&gt;

&lt;p&gt;If you are weighing GitOps for your stack and want a second opinion on structure, secrets and tool choice, you can &lt;a href="https://meteorops.com/devops-setup-for-production-free-consultation" rel="noopener noreferrer"&gt;book a free consultation&lt;/a&gt; with a senior DevOps engineer.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>cicd</category>
      <category>sre</category>
    </item>
    <item>
      <title>Terraform Kubernetes Provider: Deployment Guide and Examples</title>
      <dc:creator>Michael Zion</dc:creator>
      <pubDate>Tue, 27 Oct 2026 15:30:18 +0000</pubDate>
      <link>https://dev.to/meteorops/terraform-kubernetes-provider-deployment-guide-and-examples-1jg4</link>
      <guid>https://dev.to/meteorops/terraform-kubernetes-provider-deployment-guide-and-examples-1jg4</guid>
      <description>&lt;p&gt;&lt;em&gt;This guide was written by &lt;a href="https://meteorops.com/team/arthur-azrieli" rel="noopener noreferrer"&gt;Arthur Azrieli&lt;/a&gt;, CTO at MeteorOps, and first published on the &lt;a href="https://meteorops.com/blog/deploy-kubernetes-resources-using-terraform" rel="noopener noreferrer"&gt;MeteorOps blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;You can deploy and manage Kubernetes resources with Terraform using the official &lt;code&gt;hashicorp/kubernetes&lt;/code&gt; provider. It gives you typed resources such as &lt;code&gt;kubernetes_deployment_v1&lt;/code&gt;, &lt;code&gt;kubernetes_service_v1&lt;/code&gt; and &lt;code&gt;kubernetes_ingress_v1&lt;/code&gt;, a generic &lt;code&gt;kubernetes_manifest&lt;/code&gt; resource for anything else (including custom resources), and it pairs with the &lt;code&gt;hashicorp/helm&lt;/code&gt; provider for charts. Terraform plans the change, shows you a diff, and keeps everything in state next to the cloud resources it depends on.&lt;/p&gt;

&lt;p&gt;The harder question is where Terraform should stop. Our short answer, from running this at startups and scale-ups: use Terraform for the cluster and the platform layer it needs to be useful (namespaces, RBAC, storage classes, the ingress controller, cert-manager, external-dns, and the GitOps controller itself), and hand application workloads to a GitOps tool such as Argo CD or Flux. Use Terraform for app Deployments only when the app is small, rarely changes, or is tightly coupled to cloud resources Terraform already creates.&lt;/p&gt;

&lt;p&gt;This guide covers that decision first, then gives you copy-paste-correct HCL (HashiCorp Configuration Language) for provider authentication on EKS, GKE and AKS, a fully annotated &lt;code&gt;kubernetes_deployment_v1&lt;/code&gt; with the &lt;code&gt;container&lt;/code&gt; and &lt;code&gt;port&lt;/code&gt; blocks, Service and Ingress, YAML-from-file patterns, &lt;code&gt;helm_release&lt;/code&gt;, and the errors you will actually hit. Everything is checked against the current provider releases: &lt;code&gt;hashicorp/kubernetes&lt;/code&gt; v3.4.0 and &lt;code&gt;hashicorp/helm&lt;/code&gt; v3.3.0 as of October 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you manage Kubernetes resources with Terraform?
&lt;/h2&gt;

&lt;p&gt;Kubernetes already has its own reconciliation loop. Terraform has a different one that only runs when someone runs a plan and apply. Putting both on the same object works, but you need to be deliberate about which tool owns what. This table is the boundary we use.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Examples&lt;/th&gt;
&lt;th&gt;Recommended owner&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cloud and cluster&lt;/td&gt;
&lt;td&gt;VPC, EKS/GKE/AKS cluster, node pools, IAM roles for service accounts&lt;/td&gt;
&lt;td&gt;Terraform&lt;/td&gt;
&lt;td&gt;Cloud APIs, long-lived, needs plan and review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cluster bootstrap&lt;/td&gt;
&lt;td&gt;Namespaces, RBAC, StorageClasses, priority classes, quotas, the Argo CD or Flux install&lt;/td&gt;
&lt;td&gt;Terraform&lt;/td&gt;
&lt;td&gt;Changes rarely, must exist before anything else, often wired to cloud IDs (IAM role ARNs, bucket names)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Platform add-ons&lt;/td&gt;
&lt;td&gt;Ingress controller, cert-manager, external-dns, Karpenter, metrics-server&lt;/td&gt;
&lt;td&gt;Terraform with helm_release, or GitOps once it is bootstrapped&lt;/td&gt;
&lt;td&gt;Either works; pick one owner per add-on and stick to it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Application workloads&lt;/td&gt;
&lt;td&gt;Deployments, Services, Ingresses, HPAs, ConfigMaps for your own services&lt;/td&gt;
&lt;td&gt;GitOps (Argo CD, Flux) with Helm or Kustomize&lt;/td&gt;
&lt;td&gt;Deployed many times a day by CI, needs fast rollbacks and continuous drift correction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One-off glue&lt;/td&gt;
&lt;td&gt;A ConfigMap holding Terraform outputs (database endpoint, queue URL) for apps to read&lt;/td&gt;
&lt;td&gt;Terraform&lt;/td&gt;
&lt;td&gt;The values only exist in Terraform; this is the cleanest hand-off&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9lya0k71sexc58rn23ao.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9lya0k71sexc58rn23ao.webp" alt="Layered stack showing which tool owns each Kubernetes layer: Terraform owns cloud and cluster and cluster bootstrap, GitOps with Argo CD or Flux owns application workloads, and platform add-ons can go either way with one owner each." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Terraform builds the foundation; GitOps owns what changes every day.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Signs Terraform is the right tool for an in-cluster object:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;It is created once per environment and changes a few times a year.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It needs values that only Terraform knows (an IAM role ARN for a ServiceAccount annotation, an RDS endpoint, a VPC ID).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You want a new environment to come up from one command. We wrote about that goal in &lt;a href="https://meteorops.com/blog/one-click-environment-the-ultimate-devops-goal" rel="noopener noreferrer"&gt;the one-click environment&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There is no GitOps controller yet, and a single small service does not justify one.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Signs it is the wrong tool:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The image tag changes on every merge. Running a Terraform apply per deploy is slow, needs cloud credentials in CI, and serializes deploys on the state lock.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Something else mutates the object (HPA, VPA, a service mesh injector, an operator). You will fight drift on every plan.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You need progressive delivery (canary, blue/green). That lives in tools like Argo Rollouts; see &lt;a href="https://meteorops.com/blog/how-to-deploy-zero-downtime-kubernetes-releases-with-helm-and-argo-rollouts" rel="noopener noreferrer"&gt;zero-downtime releases with Helm and Argo Rollouts&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Developers need to change their own manifests without touching infrastructure state.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Terraform Kubernetes provider setup and authentication
&lt;/h2&gt;

&lt;p&gt;Pin both providers. The &lt;code&gt;hashicorp/kubernetes&lt;/code&gt; provider v3 requires Terraform v1.0 or later, and the provider functions used later in this guide need Terraform 1.8 or later.&lt;br&gt;
&lt;/p&gt;

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

  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;kubernetes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/kubernetes"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 3.4"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;helm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/helm"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 3.3"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Local development: kubeconfig
&lt;/h3&gt;

&lt;p&gt;The provider does &lt;strong&gt;not&lt;/strong&gt; read the &lt;code&gt;KUBECONFIG&lt;/code&gt; environment variable. Point it at a file explicitly with &lt;code&gt;config_path&lt;/code&gt; (or &lt;code&gt;KUBE_CONFIG_PATH&lt;/code&gt;), and pin the context so a stray &lt;code&gt;kubectl config use-context&lt;/code&gt; never sends an apply to production.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"kubernetes"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;config_path&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~/.kube/config"&lt;/span&gt;
  &lt;span class="nx"&gt;config_context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"kind-dev"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  EKS: exec authentication
&lt;/h3&gt;

&lt;p&gt;EKS tokens are short-lived. An &lt;code&gt;exec&lt;/code&gt; block fetches a fresh token on every run, which avoids the classic "Unauthorized" error on long applies that you get when you pass a static token from &lt;code&gt;aws_eks_cluster_auth&lt;/code&gt;. Do not mix &lt;code&gt;exec&lt;/code&gt; with &lt;code&gt;token&lt;/code&gt; or client certificates in the same provider block; the provider docs call that undefined behavior.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_eks_cluster"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_name&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"kubernetes"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;host&lt;/span&gt;                   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_eks_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;
  &lt;span class="nx"&gt;cluster_ca_certificate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_eks_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;certificate_authority&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;exec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;api_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"client.authentication.k8s.io/v1beta1"&lt;/span&gt;
    &lt;span class="nx"&gt;command&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt;
    &lt;span class="nx"&gt;args&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"eks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"get-token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"--cluster-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The IAM principal that runs Terraform also needs Kubernetes permissions: an EKS access entry (or an aws-auth mapping on older clusters) bound to an appropriate access policy. Without it, authentication succeeds and every request is rejected.&lt;/p&gt;

&lt;h3&gt;
  
  
  GKE and AKS
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# GKE: requires gke-gcloud-auth-plugin on the machine running Terraform&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"google_container_cluster"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_name&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"kubernetes"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;host&lt;/span&gt;                   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://${data.google_container_cluster.this.endpoint}"&lt;/span&gt;
  &lt;span class="nx"&gt;cluster_ca_certificate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;google_container_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;master_auth&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;cluster_ca_certificate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;exec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;api_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"client.authentication.k8s.io/v1beta1"&lt;/span&gt;
    &lt;span class="nx"&gt;command&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"gke-gcloud-auth-plugin"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# AKS with local accounts: credentials from the cluster data source&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_kubernetes_cluster"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_name&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_group&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"kubernetes"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;alias&lt;/span&gt;                  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aks"&lt;/span&gt;
  &lt;span class="nx"&gt;host&lt;/span&gt;                   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;azurerm_kubernetes_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kube_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;
  &lt;span class="nx"&gt;client_certificate&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;azurerm_kubernetes_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kube_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;client_certificate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;client_key&lt;/span&gt;             &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;azurerm_kubernetes_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kube_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;client_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;cluster_ca_certificate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;azurerm_kubernetes_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kube_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;cluster_ca_certificate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For AKS clusters with Microsoft Entra ID integration and local accounts disabled, use an &lt;code&gt;exec&lt;/code&gt; block that calls &lt;code&gt;kubelogin&lt;/code&gt; instead of client certificates.&lt;/p&gt;

&lt;h3&gt;
  
  
  The chicken-and-egg problem: cluster and resources in one root module
&lt;/h3&gt;

&lt;p&gt;The most common failure with this provider is creating the cluster and the in-cluster resources in the same root module. The provider block is configured before the cluster exists, so plans fail or, worse, the provider silently falls back to a default and targets the wrong cluster. The official provider docs warn against exactly this and recommend that the cluster and the Kubernetes resources are managed with separate &lt;code&gt;apply&lt;/code&gt; operations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbu9qjzdxi1c2iif2vgav.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbu9qjzdxi1c2iif2vgav.webp" alt="Comparison of one root module, where the Kubernetes provider is configured before the cluster exists so the plan fails or targets the wrong cluster, with two root modules: infra/cluster creates the cluster, then infra/cluster-bootstrap reads it with a data source." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Split cluster creation and in-cluster resources into separate states applied in order.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Practical options, from most to least robust:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Two root modules (two states).&lt;/strong&gt; &lt;code&gt;infra/cluster&lt;/code&gt; creates the cluster. &lt;code&gt;infra/cluster-bootstrap&lt;/code&gt; reads it with &lt;code&gt;data "aws_eks_cluster"&lt;/code&gt; (or the GKE and AKS equivalents) and configures the provider from that. Terragrunt or your CI runs them in order. This is what we default to; see our &lt;a href="https://meteorops.com/blog/terraform-starter-boilerplate-for-gcp-using-terragrunt" rel="noopener noreferrer"&gt;Terragrunt starter for GCP&lt;/a&gt; for the layout.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;HCP Terraform Stacks with deferred changes.&lt;/strong&gt; Stacks can defer the Kubernetes component until the cluster outputs are known, so one deployment creates both. This is a Stacks feature, not plain Terraform CLI.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Targeted first apply.&lt;/strong&gt; &lt;code&gt;terraform apply -target=module.eks&lt;/code&gt; then a full apply. It works for a demo, but &lt;code&gt;-target&lt;/code&gt; is meant for exceptions, and you will forget it when rebuilding an environment.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  kubernetes_deployment_v1: a complete, annotated example
&lt;/h2&gt;

&lt;p&gt;Use the &lt;code&gt;_v1&lt;/code&gt; resources. In provider v3.0.0 every unversioned resource (&lt;code&gt;kubernetes_deployment&lt;/code&gt;, &lt;code&gt;kubernetes_service&lt;/code&gt;, &lt;code&gt;kubernetes_namespace&lt;/code&gt; and the rest) was marked deprecated in favor of the version-suffixed name. The schema is the same, so examples written for &lt;code&gt;kubernetes_deployment&lt;/code&gt; work after renaming. How to migrate existing state is covered in the FAQ below.&lt;/p&gt;

&lt;p&gt;This example is what a production-ready Deployment looks like in HCL: namespace, named container port, env from literal values, a Secret and a ConfigMap, resource requests and limits, three probes, and a rolling update strategy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"kubernetes_namespace_v1"&lt;/span&gt; &lt;span class="s2"&gt;"app"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;metadata&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"checkout"&lt;/span&gt;
    &lt;span class="nx"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"app.kubernetes.io/part-of"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"shop"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;app_labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"app.kubernetes.io/name"&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"checkout-api"&lt;/span&gt;
    &lt;span class="s2"&gt;"app.kubernetes.io/instance"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"checkout-api"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"kubernetes_deployment_v1"&lt;/span&gt; &lt;span class="s2"&gt;"api"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;metadata&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"checkout-api"&lt;/span&gt;
    &lt;span class="nx"&gt;namespace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;kubernetes_namespace_v1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
    &lt;span class="nx"&gt;labels&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app_labels&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Default is true: Terraform waits until the rollout completes,&lt;/span&gt;
  &lt;span class="c1"&gt;# so a broken image fails the apply instead of passing silently.&lt;/span&gt;
  &lt;span class="nx"&gt;wait_for_rollout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nx"&gt;spec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;replicas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;

    &lt;span class="nx"&gt;selector&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;match_labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app_labels&lt;/span&gt;   &lt;span class="c1"&gt;# must match template labels&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;strategy&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"RollingUpdate"&lt;/span&gt;
      &lt;span class="nx"&gt;rolling_update&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;max_surge&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"25%"&lt;/span&gt;
        &lt;span class="nx"&gt;max_unavailable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;metadata&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app_labels&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nx"&gt;spec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;service_account_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"checkout-api"&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;name&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"api"&lt;/span&gt;                        &lt;span class="c1"&gt;# required&lt;/span&gt;
          &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ghcr.io/acme/checkout-api:1.14.2"&lt;/span&gt;

          &lt;span class="c1"&gt;# One port block per port. container_port is required.&lt;/span&gt;
          &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;name&lt;/span&gt;           &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http"&lt;/span&gt;            &lt;span class="c1"&gt;# referenced by probes and the Service&lt;/span&gt;
            &lt;span class="nx"&gt;container_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;
            &lt;span class="nx"&gt;protocol&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"TCP"&lt;/span&gt;             &lt;span class="c1"&gt;# default; UDP also valid&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;

          &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;name&lt;/span&gt;           &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"metrics"&lt;/span&gt;
            &lt;span class="nx"&gt;container_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9090&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;

          &lt;span class="c1"&gt;# One env block per variable&lt;/span&gt;
          &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;name&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"LOG_LEVEL"&lt;/span&gt;
            &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"info"&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;

          &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"DATABASE_PASSWORD"&lt;/span&gt;
            &lt;span class="nx"&gt;value_from&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nx"&gt;secret_key_ref&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"checkout-db"&lt;/span&gt;
                &lt;span class="nx"&gt;key&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"password"&lt;/span&gt;
              &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;

          &lt;span class="c1"&gt;# Import every key of a ConfigMap as env vars&lt;/span&gt;
          &lt;span class="nx"&gt;env_from&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;config_map_ref&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"checkout-config"&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;

          &lt;span class="nx"&gt;resources&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;requests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nx"&gt;cpu&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"250m"&lt;/span&gt;
              &lt;span class="nx"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"256Mi"&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nx"&gt;limits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nx"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"512Mi"&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;

          &lt;span class="nx"&gt;startup_probe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;http_get&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/healthz"&lt;/span&gt;
              &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http"&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nx"&gt;period_seconds&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
            &lt;span class="nx"&gt;failure_threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;   &lt;span class="c1"&gt;# up to 150s to boot&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;

          &lt;span class="nx"&gt;readiness_probe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;http_get&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/ready"&lt;/span&gt;
              &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http"&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nx"&gt;period_seconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;

          &lt;span class="nx"&gt;liveness_probe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;http_get&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/healthz"&lt;/span&gt;
              &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http"&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nx"&gt;period_seconds&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
            &lt;span class="nx"&gt;failure_threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes on the parts people get wrong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;selector.match_labels&lt;/code&gt; must be a subset of &lt;code&gt;template.metadata.labels&lt;/code&gt;, and it is immutable after creation. Changing it forces a replacement of the Deployment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Probes on &lt;code&gt;http_get&lt;/code&gt; take &lt;code&gt;port&lt;/code&gt; as a string, so it accepts either a number (&lt;code&gt;8080&lt;/code&gt;) or the port name (&lt;code&gt;"http"&lt;/code&gt;). Using names means you change the port in one place.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Setting a memory limit without a CPU limit is deliberate: CPU limits cause throttling, memory limits protect the node. See &lt;a href="https://meteorops.com/glossary/kubernetes-resource-requests-and-limits" rel="noopener noreferrer"&gt;resource requests and limits&lt;/a&gt; for the reasoning.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A liveness probe that checks dependencies (database, downstream APIs) turns a partial outage into a restart storm. Keep it shallow; our guide on &lt;a href="https://meteorops.com/blog/how-to-configure-kubernetes-liveness-probes-without-causing-restart-loops" rel="noopener noreferrer"&gt;liveness probes without restart loops&lt;/a&gt; goes deeper.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Container port block syntax reference (and other nested blocks)
&lt;/h2&gt;

&lt;p&gt;The provider maps every Kubernetes list to a repeated block, and every Kubernetes map to an HCL map attribute with &lt;code&gt;=&lt;/code&gt;. That one rule explains most syntax errors: &lt;code&gt;port&lt;/code&gt; and &lt;code&gt;env&lt;/code&gt; are blocks (no equals sign, repeat them), while &lt;code&gt;labels&lt;/code&gt;, &lt;code&gt;requests&lt;/code&gt; and &lt;code&gt;limits&lt;/code&gt; are maps (equals sign, braces).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhxk45abn0nwajzb39jhr.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhxk45abn0nwajzb39jhr.webp" alt="Side-by-side comparison: Kubernetes lists such as port and env become repeated HCL blocks with no equals sign, while Kubernetes maps such as labels, requests and limits become map attributes with an equals sign and braces." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;One mapping rule explains most HCL syntax errors in this provider.&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Kubernetes YAML&lt;/th&gt;
&lt;th&gt;Terraform HCL&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ports: - containerPort: 8080&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;port { container_port = 8080 }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Repeat the &lt;code&gt;port&lt;/code&gt; block per port. Optional: &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;protocol&lt;/code&gt;, &lt;code&gt;host_port&lt;/code&gt;, &lt;code&gt;host_ip&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;env: - name: X value: y&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;env { name = "X" value = "y" }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Repeat per variable. Use &lt;code&gt;value_from&lt;/code&gt; for Secret and ConfigMap keys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;envFrom: - configMapRef:&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;env_from { config_map_ref { name = ... } }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Or &lt;code&gt;secret_ref&lt;/code&gt;; optional &lt;code&gt;prefix&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;resources: requests: cpu&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;resources { requests = { cpu = "250m" } }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;requests&lt;/code&gt; and &lt;code&gt;limits&lt;/code&gt; are maps of strings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;livenessProbe: httpGet:&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;liveness_probe { http_get { path = "/" port = "http" } }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Same shape for &lt;code&gt;readiness_probe&lt;/code&gt; and &lt;code&gt;startup_probe&lt;/code&gt;; also &lt;code&gt;tcp_socket&lt;/code&gt;, &lt;code&gt;exec&lt;/code&gt;, &lt;code&gt;grpc&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;volumeMounts:&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;volume_mount { name = ... mount_path = ... }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pair with a &lt;code&gt;volume&lt;/code&gt; block in the pod spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;imagePullSecrets:&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;image_pull_secrets { name = ... }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;In the pod spec, not the container&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The schema reference for every field is on the &lt;a href="https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/deployment_v1" rel="noopener noreferrer"&gt;kubernetes_deployment_v1 registry page&lt;/a&gt;. When you need many ports or env vars from a variable, use a &lt;code&gt;dynamic&lt;/code&gt; block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"ports"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&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;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;http&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;
    &lt;span class="nx"&gt;metrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9090&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# inside container { ... }&lt;/span&gt;
&lt;span class="nx"&gt;dynamic&lt;/span&gt; &lt;span class="s2"&gt;"port"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ports&lt;/span&gt;
  &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;           &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;
    &lt;span class="nx"&gt;container_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Service and Ingress with kubernetes_service_v1 and kubernetes_ingress_v1
&lt;/h2&gt;

&lt;p&gt;Reference the Deployment's labels and port names instead of repeating literals. &lt;code&gt;target_port&lt;/code&gt; is a string, so it can be the container port name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"kubernetes_service_v1"&lt;/span&gt; &lt;span class="s2"&gt;"api"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;metadata&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"checkout-api"&lt;/span&gt;
    &lt;span class="nx"&gt;namespace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;kubernetes_namespace_v1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;spec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;selector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app_labels&lt;/span&gt;
    &lt;span class="nx"&gt;type&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ClusterIP"&lt;/span&gt;

    &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http"&lt;/span&gt;
      &lt;span class="nx"&gt;port&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
      &lt;span class="nx"&gt;target_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http"&lt;/span&gt;   &lt;span class="c1"&gt;# the named container port&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"kubernetes_ingress_v1"&lt;/span&gt; &lt;span class="s2"&gt;"api"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;metadata&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"checkout-api"&lt;/span&gt;
    &lt;span class="nx"&gt;namespace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;kubernetes_namespace_v1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
    &lt;span class="nx"&gt;annotations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"cert-manager.io/cluster-issuer"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"letsencrypt"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;spec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ingress_class_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"nginx"&lt;/span&gt;

    &lt;span class="nx"&gt;rule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"checkout.example.com"&lt;/span&gt;
      &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;path&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;
          &lt;span class="nx"&gt;path_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Prefix"&lt;/span&gt;
          &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;service&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;kubernetes_service_v1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
              &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http"&lt;/span&gt;
              &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;tls&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;hosts&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"checkout.example.com"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="nx"&gt;secret_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"checkout-api-tls"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a &lt;code&gt;LoadBalancer&lt;/code&gt; Service or an Ingress that provisions a cloud load balancer, &lt;code&gt;wait_for_load_balancer = true&lt;/code&gt; makes Terraform wait for an address, which is useful when a DNS record in the same apply depends on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  kubernetes_manifest and deploying YAML files with Terraform
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;kubernetes_manifest&lt;/code&gt; takes any Kubernetes object as an HCL map and applies it with server-side apply. Use it for custom resources (cert-manager &lt;code&gt;ClusterIssuer&lt;/code&gt;, Karpenter &lt;code&gt;NodePool&lt;/code&gt;, Argo CD &lt;code&gt;Application&lt;/code&gt;) and for kinds the typed resources do not cover. Two requirements matter in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It needs API access at plan time.&lt;/strong&gt; The provider asks the cluster for the resource schema during plan, so the cluster must already exist and be reachable. This is another reason to split cluster creation into its own state.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The CRD must exist before the plan.&lt;/strong&gt; If the &lt;code&gt;CustomResourceDefinition&lt;/code&gt; is installed in the same apply (for example by a &lt;code&gt;helm_release&lt;/code&gt; of cert-manager), the plan fails with "API did not recognize GroupVersionKind from manifest (CRD may not be installed)". &lt;code&gt;depends_on&lt;/code&gt; does not help, because the failure happens at plan time. Install CRDs in an earlier stage.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhr5flkp6cqdccryvfzgw.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhr5flkp6cqdccryvfzgw.webp" alt="Two lanes: installing cert-manager with helm_release and a ClusterIssuer with kubernetes_manifest in one plan fails because the CRD may not be installed, and depends_on does not help; installing CRDs in stage 1 and kubernetes_manifest in stage 2 lets the plan succeed." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The failure happens at plan time, so the fix is a separate earlier stage.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  A single YAML file
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"kubernetes_manifest"&lt;/span&gt; &lt;span class="s2"&gt;"issuer"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;manifest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;yamldecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"${path.module}/manifests/cluster-issuer.yaml"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Terraform's built-in &lt;code&gt;yamldecode&lt;/code&gt; accepts exactly one YAML document; a file that contains several documents returns an error. For multi-document files, use the provider functions.&lt;/p&gt;
&lt;h3&gt;
  
  
  Multi-document YAML with provider functions (Terraform 1.8+)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nx"&gt;kubernetes&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nx"&gt;manifest_decode_multi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"${path.module}/manifests/monitoring.yaml"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"kubernetes_manifest"&lt;/span&gt; &lt;span class="s2"&gt;"monitoring"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# Key by kind/namespace/name so reordering the file does not recreate objects&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;docs&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="s2"&gt;"${m.kind}/${try(m.metadata.namespace, "&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="s2"&gt;")}/${m.metadata.name}"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;manifest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Key &lt;code&gt;for_each&lt;/code&gt; by a stable identity, never by list index. With &lt;code&gt;count&lt;/code&gt;, inserting one document at the top of the file shifts every index and Terraform plans to destroy and recreate everything below it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0bz5y5juamzg144dtehx.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0bz5y5juamzg144dtehx.webp" alt="Comparison of inserting one YAML document at the top: with count, every list index shifts and Terraform destroys and recreates the objects; with for_each keyed by kind, namespace and name, existing objects keep their keys and only the new object is created." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Stable keys keep reordering a YAML file from touching existing objects.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  A directory of YAML files
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fileset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"${path.module}/manifests"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"*.yaml"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;objects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nx"&gt;kubernetes&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nx"&gt;manifest_decode_multi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"${path.module}/manifests/${f}"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then feed &lt;code&gt;local.objects&lt;/code&gt; into the same keyed &lt;code&gt;for_each&lt;/code&gt; as above.&lt;/p&gt;
&lt;h3&gt;
  
  
  When to use the kubectl provider instead
&lt;/h3&gt;

&lt;p&gt;The community kubectl provider takes raw YAML (&lt;code&gt;yaml_body&lt;/code&gt;) and does not need the CRD schema at plan time, which is why many teams use it for CRD-heavy bootstraps. The original &lt;code&gt;gavinbunney/kubectl&lt;/code&gt; has not released since January 2025; the actively maintained fork is &lt;code&gt;alekc/kubectl&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;kubectl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"alekc/kubectl"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 2.4"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"kubectl_file_documents"&lt;/span&gt; &lt;span class="s2"&gt;"crds"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"${path.module}/manifests/crds.yaml"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"kubectl_manifest"&lt;/span&gt; &lt;span class="s2"&gt;"crds"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kubectl_file_documents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;crds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;manifests&lt;/span&gt;
  &lt;span class="nx"&gt;yaml_body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trade-off: you lose the field-level diff that &lt;code&gt;kubernetes_manifest&lt;/code&gt; shows, and you add a third-party provider to your supply chain.&lt;/p&gt;

&lt;h2&gt;
  
  
  helm_release: installing charts with the Terraform Helm provider
&lt;/h2&gt;

&lt;p&gt;For third-party software that ships as a Helm chart, &lt;code&gt;helm_release&lt;/code&gt; is almost always better than translating the chart into &lt;code&gt;kubernetes_*&lt;/code&gt; resources. Note the Helm provider v3 syntax: &lt;code&gt;kubernetes&lt;/code&gt; in the provider and &lt;code&gt;set&lt;/code&gt; in the release are now attributes with &lt;code&gt;=&lt;/code&gt;, not blocks. Most examples online still show the v2 block syntax and fail on v3.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"helm"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;kubernetes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;host&lt;/span&gt;                   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_eks_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;
    &lt;span class="nx"&gt;cluster_ca_certificate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_eks_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;certificate_authority&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;exec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;api_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"client.authentication.k8s.io/v1beta1"&lt;/span&gt;
      &lt;span class="nx"&gt;command&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt;
      &lt;span class="nx"&gt;args&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"eks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"get-token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"--cluster-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"helm_release"&lt;/span&gt; &lt;span class="s2"&gt;"argocd"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;             &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"argocd"&lt;/span&gt;
  &lt;span class="nx"&gt;namespace&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"argocd"&lt;/span&gt;
  &lt;span class="nx"&gt;create_namespace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nx"&gt;repository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://argoproj.github.io/argo-helm"&lt;/span&gt;
  &lt;span class="nx"&gt;chart&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"argo-cd"&lt;/span&gt;
  &lt;span class="nx"&gt;version&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.10.5"&lt;/span&gt;   &lt;span class="c1"&gt;# always pin; unpinned installs the latest chart&lt;/span&gt;

  &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"${path.module}/values/argocd.yaml"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

  &lt;span class="nx"&gt;set&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"configs.params.server&lt;/span&gt;&lt;span class="err"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.insecure"&lt;/span&gt;
      &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prefer a &lt;code&gt;values&lt;/code&gt; file over long &lt;code&gt;set&lt;/code&gt; lists: it is reviewable, and it is the same file you would use with the Helm CLI. Installing Argo CD or Flux this way, and then letting it own the application layer, is the cleanest Terraform to GitOps hand-off we know.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison: typed resources vs kubernetes_manifest vs kubectl vs Helm vs GitOps
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;th&gt;Reads schema from cluster at plan&lt;/th&gt;
&lt;th&gt;CRDs&lt;/th&gt;
&lt;th&gt;Drift handling&lt;/th&gt;
&lt;th&gt;Main drawback&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubernetes_*_v1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Core objects: namespaces, RBAC, Services, small Deployments&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Diff on next plan&lt;/td&gt;
&lt;td&gt;Verbose HCL; not every kind exists&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubernetes_manifest&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Custom resources, any kind, YAML you already have&lt;/td&gt;
&lt;td&gt;Required&lt;/td&gt;
&lt;td&gt;Yes, if the CRD already exists&lt;/td&gt;
&lt;td&gt;Diff on next plan (server-side apply)&lt;/td&gt;
&lt;td&gt;Fails at plan when the cluster or CRD is missing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;kubectl_manifest&lt;/code&gt; (alekc)&lt;/td&gt;
&lt;td&gt;CRD-heavy bootstraps, raw YAML&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes, same apply&lt;/td&gt;
&lt;td&gt;Diff on next plan (less detailed)&lt;/td&gt;
&lt;td&gt;Community provider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;helm_release&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Third-party charts (ingress, cert-manager, Argo CD)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes, chart installs them&lt;/td&gt;
&lt;td&gt;Release-level diff&lt;/td&gt;
&lt;td&gt;Chart internals are opaque in the plan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Argo CD or Flux&lt;/td&gt;
&lt;td&gt;Application workloads&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;td&gt;Yes, ordered with sync waves or dependencies&lt;/td&gt;
&lt;td&gt;Continuous reconciliation&lt;/td&gt;
&lt;td&gt;Another system to run; needs a bootstrap&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Common errors and how to fix them
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Unauthorized or forbidden
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;"Unauthorized" partway through a long apply:&lt;/strong&gt; a static token from &lt;code&gt;aws_eks_cluster_auth&lt;/code&gt; expired. Switch to an &lt;code&gt;exec&lt;/code&gt; block.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Authenticated but every call is "forbidden":&lt;/strong&gt; the CI role has no Kubernetes RBAC. On EKS, add an access entry for the role Terraform assumes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Terraform targets the wrong cluster or localhost:&lt;/strong&gt; the provider got empty values (cluster not created yet, or &lt;code&gt;KUBECONFIG&lt;/code&gt; assumed). Split the states and set &lt;code&gt;config_context&lt;/code&gt; explicitly.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  API did not recognize GroupVersionKind (CRD may not be installed)
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;kubernetes_manifest&lt;/code&gt; references a custom resource whose CRD is not in the cluster at plan time. Install the CRD (or the chart that ships it) in a previous stage, or use &lt;code&gt;kubectl_manifest&lt;/code&gt; for that object.&lt;/p&gt;

&lt;h3&gt;
  
  
  Perpetual diff on replicas because of an HPA
&lt;/h3&gt;

&lt;p&gt;When a HorizontalPodAutoscaler scales the Deployment, the next plan wants to reset &lt;code&gt;replicas&lt;/code&gt; to the value in code. Tell Terraform to stop managing it after creation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"kubernetes_deployment_v1"&lt;/span&gt; &lt;span class="s2"&gt;"api"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="nx"&gt;lifecycle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ignore_changes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;replicas&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set the initial replica count to your HPA minimum so the first apply does not scale down. If the HPA itself flaps, see &lt;a href="https://meteorops.com/blog/how-to-configure-kubernetes-hpa-without-causing-scaling-flaps" rel="noopener noreferrer"&gt;configuring HPA without scaling flaps&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Drift from controllers, annotations and restarts
&lt;/h3&gt;

&lt;p&gt;Admission webhooks, service meshes and &lt;code&gt;kubectl rollout restart&lt;/code&gt; add annotations and labels. Ignore them at the provider level with regex lists, or per resource for the pod template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"kubernetes"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="nx"&gt;ignore_annotations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"^service&lt;/span&gt;&lt;span class="err"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.beta&lt;/span&gt;&lt;span class="err"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.kubernetes&lt;/span&gt;&lt;span class="err"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.io&lt;/span&gt;&lt;span class="err"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;/aws-load-balancer.*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"kubernetes_deployment_v1"&lt;/span&gt; &lt;span class="s2"&gt;"api"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="nx"&gt;lifecycle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ignore_changes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nx"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;annotations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"kubectl.kubernetes.io/restartedAt"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Provider-level &lt;code&gt;ignore_annotations&lt;/code&gt; and &lt;code&gt;ignore_labels&lt;/code&gt; only apply to top-level metadata, not to pod templates. For &lt;code&gt;kubernetes_manifest&lt;/code&gt;, fields the API server mutates go in &lt;code&gt;computed_fields&lt;/code&gt;; &lt;code&gt;metadata.labels&lt;/code&gt; and &lt;code&gt;metadata.annotations&lt;/code&gt; are included by default.&lt;/p&gt;

&lt;h3&gt;
  
  
  Provider produced inconsistent result after apply
&lt;/h3&gt;

&lt;p&gt;Usually a mutating webhook changed a field you set in a &lt;code&gt;kubernetes_manifest&lt;/code&gt;. Add that field path to &lt;code&gt;computed_fields&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Apply hangs, then times out waiting for rollout
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;wait_for_rollout&lt;/code&gt; defaults to true, so a crash-looping pod or an image that cannot be pulled holds the apply until it times out. Check &lt;code&gt;kubectl describe pod&lt;/code&gt; and events. Do not disable the wait to make the error go away; it is telling you the deploy failed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Field is immutable / forces replacement
&lt;/h3&gt;

&lt;p&gt;Selectors, container names and Service &lt;code&gt;cluster_ip&lt;/code&gt; cannot change in place. The plan will show a replacement. For Deployments that serve traffic, create the new object under a new name and switch the Service, rather than letting Terraform destroy first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Providers pinned (&lt;code&gt;~&amp;gt; 3.4&lt;/code&gt; for kubernetes, &lt;code&gt;~&amp;gt; 3.3&lt;/code&gt; for helm) and the lock file committed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cluster creation and in-cluster resources in separate states, applied in order.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Short-lived credentials through &lt;code&gt;exec&lt;/code&gt;; no long-lived tokens in variables or state.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Only &lt;code&gt;_v1&lt;/code&gt; (or newer versioned) resource names; no deprecated unversioned ones in new code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A clear owner per object: Terraform or GitOps, never both.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ignore_changes&lt;/code&gt; on &lt;code&gt;replicas&lt;/code&gt; for every Deployment that has an HPA.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CRDs installed in an earlier stage than the custom resources that use them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Helm charts pinned to an exact &lt;code&gt;version&lt;/code&gt;, with values in files.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;for_each&lt;/code&gt; keyed by kind, namespace and name for anything decoded from YAML.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Secrets sourced from a secret manager (External Secrets Operator, or a cloud secret store) rather than written into Terraform state in plain text.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A plan step in CI on every pull request, so reviewers see the Kubernetes diff before it lands.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the difference between kubernetes_deployment and kubernetes_deployment_v1?
&lt;/h3&gt;

&lt;p&gt;They have the same schema and both create an &lt;code&gt;apps/v1&lt;/code&gt; Deployment. Since provider v3.0.0, &lt;code&gt;kubernetes_deployment&lt;/code&gt; is deprecated and &lt;code&gt;kubernetes_deployment_v1&lt;/code&gt; is the supported name. No removal date is announced, but new code should use the &lt;code&gt;_v1&lt;/code&gt; name.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I migrate kubernetes_deployment to kubernetes_deployment_v1 without recreating it?
&lt;/h3&gt;

&lt;p&gt;Rename the resource in code, then remove the old address from state and import the existing object under the new one: &lt;code&gt;terraform state rm kubernetes_deployment.api&lt;/code&gt; followed by &lt;code&gt;terraform import kubernetes_deployment_v1.api checkout/checkout-api&lt;/code&gt; (namespace/name). Run the plan only after the import and confirm it shows no replacement. Provider v3.3.0 and v3.4.0 also support &lt;code&gt;moved&lt;/code&gt; blocks from the old names for &lt;code&gt;kubernetes_namespace&lt;/code&gt; and &lt;code&gt;kubernetes_role&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I write the container port block in a Terraform kubernetes_deployment?
&lt;/h3&gt;

&lt;p&gt;Inside &lt;code&gt;spec.template.spec.container&lt;/code&gt;, add one &lt;code&gt;port&lt;/code&gt; block per port with &lt;code&gt;container_port&lt;/code&gt; (required) and optionally &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;protocol&lt;/code&gt;: &lt;code&gt;port { name = "http" container_port = 8080 }&lt;/code&gt;. It is a block, so there is no &lt;code&gt;=&lt;/code&gt; after &lt;code&gt;port&lt;/code&gt;, and you repeat it for multiple ports or generate it with a &lt;code&gt;dynamic "port"&lt;/code&gt; block.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can Terraform apply a Kubernetes YAML manifest from a file?
&lt;/h3&gt;

&lt;p&gt;Yes. For a single document, use &lt;code&gt;kubernetes_manifest&lt;/code&gt; with &lt;code&gt;yamldecode(file(...))&lt;/code&gt;. For multi-document files, use &lt;code&gt;provider::kubernetes::manifest_decode_multi&lt;/code&gt; with a keyed &lt;code&gt;for_each&lt;/code&gt;, or the &lt;code&gt;alekc/kubectl&lt;/code&gt; provider's &lt;code&gt;kubectl_file_documents&lt;/code&gt; data source.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I use Terraform or Helm for Kubernetes?
&lt;/h3&gt;

&lt;p&gt;They are not alternatives at the same layer. Helm packages Kubernetes manifests; Terraform orchestrates infrastructure. Use Terraform's &lt;code&gt;helm_release&lt;/code&gt; to install platform charts during cluster bootstrap, and use Helm charts through Argo CD or Flux for your own applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Terraform or Argo CD better for deploying applications to Kubernetes?
&lt;/h3&gt;

&lt;p&gt;For applications that deploy frequently, a GitOps controller like Argo CD is usually better: it reconciles continuously, corrects drift, and gives developers rollbacks without infrastructure credentials. Terraform is better for the cluster and the bootstrap layer, including installing Argo CD itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does kubernetes_manifest fail when I create the cluster in the same configuration?
&lt;/h3&gt;

&lt;p&gt;It needs to contact the API server during plan to read the resource schema, and the cluster does not exist yet. Create the cluster in a separate state (or use HCP Terraform Stacks deferred changes) and run the Kubernetes configuration afterwards.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can Terraform install Kubernetes itself?
&lt;/h3&gt;

&lt;p&gt;Terraform creates managed clusters through the cloud providers (EKS, GKE, AKS) and can provision VMs for self-managed clusters, but it does not install Kubernetes on its own. For local testing, kind or minikube is faster than any Terraform setup.&lt;/p&gt;

&lt;p&gt;Drawing the line between Terraform and GitOps is a decision you live with for years, and it is easier to get right before the first hundred services. If you want a second opinion on your cluster bootstrap or a review of your Terraform layout, &lt;a href="https://meteorops.com/devops-setup-for-production-free-consultation" rel="noopener noreferrer"&gt;book a free consultation with our engineers&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>kubernetes</category>
      <category>devops</category>
      <category>aws</category>
    </item>
    <item>
      <title>How to Reduce Cloud Costs Without Slowing Engineering</title>
      <dc:creator>Michael Zion</dc:creator>
      <pubDate>Mon, 26 Oct 2026 15:31:40 +0000</pubDate>
      <link>https://dev.to/meteorops/how-to-reduce-cloud-costs-without-slowing-engineering-5d3j</link>
      <guid>https://dev.to/meteorops/how-to-reduce-cloud-costs-without-slowing-engineering-5d3j</guid>
      <description>&lt;p&gt;&lt;em&gt;This guide was written by &lt;a href="https://meteorops.com/team/arthur-azrieli" rel="noopener noreferrer"&gt;Arthur Azrieli&lt;/a&gt;, CTO at MeteorOps, and first published on the &lt;a href="https://meteorops.com/blog/how-to-cut-cloud-costs-without-slowing-teams" rel="noopener noreferrer"&gt;MeteorOps blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The way to reduce cloud costs without slowing engineering is to take the decisions off engineers' plates. Make waste disappear through defaults and automation (lifecycle rules, log retention, after-hours schedules, environment TTLs), buy discounts on the stable baseline you already run, and show each team its own spend where it already works: in pull requests and in a weekly per-team report. Do not add approval gates, ticket queues, or a monthly "cost review" meeting that pulls engineers out of delivery.&lt;/p&gt;

&lt;p&gt;In practice that means a specific order. First, get spend attributed to teams and kill obvious waste (idle resources, forgotten non-production environments, unbounded log retention). Second, rightsize and fix the platform defaults that create the waste (Kubernetes requests, node autoscaling, storage classes, NAT gateway traffic). Third, commit to Savings Plans once the baseline is clean. Fourth, put guardrails in code so the bill does not drift back.&lt;/p&gt;

&lt;p&gt;This playbook is written for CTOs, VPs of Engineering, and platform or DevOps leads at startups and scale-ups (roughly 10 to 200 engineers, mostly on AWS, often on Kubernetes) whose cloud bill is growing faster than revenue. Every AWS fact below links to AWS documentation; where we give an opinion, it comes from running these changes in production for teams like yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why cost work usually slows engineering (and how to avoid it)
&lt;/h2&gt;

&lt;p&gt;Cost initiatives slow teams down when they turn spending into a permission problem. The common failure patterns are familiar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Approval gates.&lt;/strong&gt; A ticket to request a bigger instance or a new environment. Engineers wait, then over-provision when they finally get approval so they never have to ask again.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Finance-shaped dashboards.&lt;/strong&gt; A chart that says "EC2 is up 18%" gives nobody a decision to make. The engineer still has to dig through clusters, accounts, and services to find the owner.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;One-off cleanup sprints.&lt;/strong&gt; A team spends two weeks deleting things, the bill drops, and six months later it is back because nothing changed in how resources are created.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Risky savings.&lt;/strong&gt; Aggressive rightsizing or Spot adoption without the reliability groundwork causes an incident, and the organization learns that "cost work breaks production."&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The alternative is to treat cost like any other non-functional requirement: encode it in platform defaults, measure it per service, and give owners fast feedback. The FinOps Foundation describes the cycle as three phases, &lt;a href="https://www.finops.org/framework/phases/" rel="noopener noreferrer"&gt;Inform, Optimize, and Operate&lt;/a&gt;: see the data, find improvements, then make them part of how you run. The rest of this guide follows that cycle with concrete AWS and Kubernetes steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 30/60/90 day plan to reduce cloud costs
&lt;/h2&gt;

&lt;p&gt;Here is the sequence we recommend. It front-loads changes that need no product decisions and no engineer time from feature teams.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;Goal&lt;/th&gt;
&lt;th&gt;Actions&lt;/th&gt;
&lt;th&gt;Who does the work&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Days 1 to 30&lt;/td&gt;
&lt;td&gt;Visibility and zero-risk waste&lt;/td&gt;
&lt;td&gt;Activate cost allocation tags, turn on Cost Anomaly Detection and budgets, delete unattached volumes and old snapshots, set log retention, migrate gp2 to gp3, schedule non-prod off after hours&lt;/td&gt;
&lt;td&gt;Platform team (1 to 2 people)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Days 31 to 60&lt;/td&gt;
&lt;td&gt;Fix the defaults that create waste&lt;/td&gt;
&lt;td&gt;Rightsize Kubernetes requests, enable node consolidation, add Spot for tolerant workloads, add S3 gateway endpoints, add lifecycle rules, cut log and metric volume&lt;/td&gt;
&lt;td&gt;Platform team, with service owners reviewing requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Days 61 to 90&lt;/td&gt;
&lt;td&gt;Lock in rates and prevent drift&lt;/td&gt;
&lt;td&gt;Buy Savings Plans on the cleaned baseline, add cost estimates to pull requests, enforce tags and TTLs with policy as code, start per-team showback&lt;/td&gt;
&lt;td&gt;Platform team plus engineering leadership&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The order matters. If you buy commitments first, you lock in a discount on waste you were about to delete. If you add guardrails before visibility, nobody knows why the guardrail exists.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fra0jjy7up92fi2gppc47.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fra0jjy7up92fi2gppc47.webp" alt="Timeline of four steps: attribute spend and kill waste, rightsize and fix defaults in days 1 to 60, then commit to Savings Plans and add guardrails in code in days 61 to 90; buying commitments first means a discount on waste." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Commit to discounts only after the waste is gone, then lock in the result with guardrails.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Days 1 to 30: visibility and quick wins
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Make spend attributable to an owner
&lt;/h3&gt;

&lt;p&gt;Unowned spend becomes permanent spend. Before anything else, every dollar should map to a team and a service. The two strongest tools are account structure and tags.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Separate accounts per environment&lt;/strong&gt; (at minimum production and non-production) through AWS Organizations. Account boundaries are the most reliable cost split because they need no tagging discipline.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A small, mandatory tag set.&lt;/strong&gt; Keep it to three or four keys, for example &lt;code&gt;team&lt;/code&gt;, &lt;code&gt;service&lt;/code&gt;, &lt;code&gt;env&lt;/code&gt;, and optionally &lt;code&gt;ttl&lt;/code&gt; for ephemeral resources. More keys means lower compliance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Activate the tags for billing.&lt;/strong&gt; User-defined tags do not appear in billing data until you activate them as cost allocation tags. AWS notes it can take &lt;a href="https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/activating-tags.html" rel="noopener noreferrer"&gt;up to 24 hours for tag keys to appear and up to another 24 hours to activate&lt;/a&gt;, so do this on day one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Kubernetes needs its own allocation.&lt;/strong&gt; On EKS, the bill shows EC2 nodes, not pods. AWS offers &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/cost-monitoring.html" rel="noopener noreferrer"&gt;split cost allocation data for EKS&lt;/a&gt; in its billing data, and Kubecost or &lt;a href="https://www.opencost.io/" rel="noopener noreferrer"&gt;OpenCost&lt;/a&gt; (a CNCF incubating project) break costs down by namespace, workload, and label.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tag in Terraform with provider-level &lt;code&gt;default_tags&lt;/code&gt; so engineers do not have to remember:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider "aws" {
  region = "us-east-1"

  default_tags {
    tags = {
      team    = "payments"
      service = "checkout-api"
      env     = "staging"
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Turn on anomaly detection and budgets
&lt;/h3&gt;

&lt;p&gt;The cheapest overspend is the one you catch on day two instead of at the end of the month. &lt;a href="https://docs.aws.amazon.com/cost-management/latest/userguide/manage-ad.html" rel="noopener noreferrer"&gt;AWS Cost Anomaly Detection&lt;/a&gt; uses machine learning models on your net unblended cost, runs roughly three times a day, and can alert by email or SNS (which you can route to Slack). Expect a delay: it relies on Cost Explorer data that can lag up to 24 hours. Set it up in Terraform so it lives next to the rest of your infrastructure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_ce_anomaly_monitor" "services" {
  name              = "per-service-monitor"
  monitor_type      = "DIMENSIONAL"
  monitor_dimension = "SERVICE"
}

resource "aws_ce_anomaly_subscription" "daily" {
  name      = "cost-anomalies-daily"
  frequency = "DAILY"

  monitor_arn_list = [aws_ce_anomaly_monitor.services.arn]

  subscriber {
    type    = "EMAIL"
    address = "platform-team@example.com"
  }

  threshold_expression {
    dimension {
      key           = "ANOMALY_TOTAL_IMPACT_ABSOLUTE"
      match_options = ["GREATER_THAN_OR_EQUAL"]
      values        = ["100"]
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add AWS Budgets on each account with forecast-based alerts. Budgets can also run &lt;a href="https://docs.aws.amazon.com/cost-management/latest/userguide/budgets-controls.html" rel="noopener noreferrer"&gt;budget actions&lt;/a&gt; such as applying an IAM policy or SCP, or stopping specific EC2 and RDS instances. Use actions for sandbox accounts only; on production, alerts should go to a human who owns the service.&lt;/p&gt;

&lt;h3&gt;
  
  
  Delete idle resources
&lt;/h3&gt;

&lt;p&gt;Idle resources are the safest savings because nobody uses them. &lt;a href="https://docs.aws.amazon.com/cost-management/latest/userguide/cost-optimization-hub.html" rel="noopener noreferrer"&gt;AWS Cost Optimization Hub&lt;/a&gt; consolidates idle-resource, rightsizing, Savings Plans, and Reserved Instance recommendations across accounts and Regions, so start there. Then check the usual suspects directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Unattached EBS volumes (paying for storage nobody mounts)
aws ec2 describe-volumes \
  --filters Name=status,Values=available \
  --query 'Volumes[].{ID:VolumeId,SizeGiB:Size,Created:CreateTime}' \
  --output table

# Elastic IPs not associated with anything
aws ec2 describe-addresses \
  --query 'Addresses[?AssociationId==`null`].PublicIp' \
  --output table

# CloudWatch log groups with no retention (kept forever by default)
aws logs describe-log-groups \
  --query 'logGroups[?!retentionInDays].[logGroupName,storedBytes]' \
  --output table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also look for old EBS snapshots and AMIs from past migrations, load balancers with no healthy targets, idle RDS instances in non-production, and NAT gateways in VPCs nobody uses. Snapshot before you delete anything you are not sure about; snapshots are cheap insurance compared with a surprise outage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set log retention and switch gp2 to gp3
&lt;/h3&gt;

&lt;p&gt;Two changes that are almost always safe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Log retention.&lt;/strong&gt; CloudWatch log groups keep data indefinitely unless you set a retention period. Pick a default (for example 30 days for application logs, longer only where compliance requires it) and apply it with &lt;code&gt;aws logs put-retention-policy --log-group-name NAME --retention-in-days 30&lt;/code&gt; or in Terraform.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;gp2 to gp3.&lt;/strong&gt; AWS states gp3 volumes cost &lt;a href="https://aws.amazon.com/ebs/general-purpose/" rel="noopener noreferrer"&gt;up to 20% less per GB than gp2&lt;/a&gt; and deliver a baseline of 3,000 IOPS and 125 MB/s at any size. The migration is an online volume modification: &lt;code&gt;aws ec2 modify-volume --volume-id vol-0123456789abcdef0 --volume-type gp3&lt;/code&gt;. Check volumes that need more than the gp3 baseline before converting them, and update your Kubernetes StorageClass so new volumes default to gp3.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Turn non-production off after hours
&lt;/h3&gt;

&lt;p&gt;Development and staging environments that run 24/7 but are used 50 hours a week are pure waste. AWS's own &lt;a href="https://docs.aws.amazon.com/solutions/latest/instance-scheduler-on-aws/solution-overview.html" rel="noopener noreferrer"&gt;Instance Scheduler on AWS&lt;/a&gt; starts and stops tagged EC2 and RDS instances on a schedule, and AWS cites up to 70% savings for instances only needed during business hours (168 weekly hours reduced to 50). On Kubernetes, scale workloads to zero with a KEDA cron trigger and let the node autoscaler remove the empty nodes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: checkout-api-business-hours
  namespace: staging
spec:
  scaleTargetRef:
    name: checkout-api
  minReplicaCount: 0
  triggers:
    - type: cron
      metadata:
        timezone: Europe/London
        start: "0 7 * * 1-5"
        end: "0 20 * * 1-5"
        desiredReplicas: "2"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make the schedule opt-out, not opt-in, and give engineers a one-command way to wake an environment (a Slack command or a CI job). The guardrail that does not slow anyone down is the one with an easy override.&lt;/p&gt;

&lt;h2&gt;
  
  
  Days 31 to 60: rightsizing and platform defaults
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Rightsize Kubernetes requests and enable bin-packing
&lt;/h3&gt;

&lt;p&gt;On Kubernetes, you pay for nodes, and nodes are sized by the sum of pod &lt;em&gt;requests&lt;/em&gt;, not actual usage. If every service requests 1 CPU and uses 0.1, your cluster is roughly 90% air no matter how good your autoscaler is. This is usually the single biggest lever for Kubernetes cost optimization. (If the difference between requests and limits is fuzzy, start with our &lt;a href="https://meteorops.com/glossary/kubernetes-resource-requests-and-limits" rel="noopener noreferrer"&gt;guide to Kubernetes resource requests and limits&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2iw2rlbpwnvpl6hjowh3.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2iw2rlbpwnvpl6hjowh3.webp" alt="Before rightsizing, a node holds four pods that each request 1 CPU but use 0.1, leaving the cluster roughly 90% air; after rightsizing requests and consolidating, the same pods fit on a smaller node and a node is removed." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;You pay for what pods request, so savings appear only when requests shrink and nodes are removed.&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Measure.&lt;/strong&gt; Compare requested CPU and memory with actual p95 usage per workload, using Prometheus, Kubecost, or OpenCost.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Get recommendations without risk.&lt;/strong&gt; Run the Vertical Pod Autoscaler in recommendation-only mode so it suggests requests but never restarts pods:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: checkout-api
  namespace: production
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: checkout-api
  updatePolicy:
    updateMode: "Off"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Apply through pull requests&lt;/strong&gt; to each service's manifests or Helm values, reviewed by the owner. Lower CPU requests are low risk (CPU is compressible); be more careful with memory, because a memory limit that is too low means OOM kills.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Let the cluster shrink.&lt;/strong&gt; Rightsized requests only save money if nodes are removed. With &lt;a href="https://karpenter.sh/docs/concepts/disruption/" rel="noopener noreferrer"&gt;Karpenter&lt;/a&gt;, consolidation does this: &lt;code&gt;WhenEmptyOrUnderutilized&lt;/code&gt; (the default) replaces or removes nodes whose pods fit elsewhere, and disruption budgets cap how many nodes move at once.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: general
spec:
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 5m
    budgets:
      - nodes: "10%"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Consolidation moves pods, so it is only safe when your workloads tolerate it: PodDisruptionBudgets, multiple replicas, and correct readiness probes. Our guides on &lt;a href="https://meteorops.com/blog/how-to-drain-kubernetes-nodes-without-evicting-critical-workloads" rel="noopener noreferrer"&gt;draining nodes without evicting critical workloads&lt;/a&gt; and &lt;a href="https://meteorops.com/blog/how-to-set-up-kubernetes-autoscaling-without-creating-cost-surprises" rel="noopener noreferrer"&gt;Kubernetes autoscaling without cost surprises&lt;/a&gt; cover that groundwork.&lt;/p&gt;
&lt;h3&gt;
  
  
  Use Spot where interruptions are cheap
&lt;/h3&gt;

&lt;p&gt;AWS advertises Spot Instances at &lt;a href="https://aws.amazon.com/ec2/spot/" rel="noopener noreferrer"&gt;up to 90% off On-Demand prices&lt;/a&gt;. The trade-off is interruption: EC2 issues an &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/spot-instance-termination-notices.html" rel="noopener noreferrer"&gt;interruption notice two minutes before&lt;/a&gt; it stops or terminates a Spot Instance, on a best-effort basis. Good candidates are CI runners, batch and queue workers, stateless services with several replicas, and most non-production environments. Poor candidates are single-replica stateful services and anything that takes longer than two minutes to shut down cleanly. Diversify across many instance types and Availability Zones so a single capacity pool going away does not take all your nodes with it, and keep an On-Demand floor for critical services.&lt;/p&gt;
&lt;h3&gt;
  
  
  Rightsize instances and databases, and try Graviton
&lt;/h3&gt;

&lt;p&gt;Outside Kubernetes, use AWS Compute Optimizer (surfaced in Cost Optimization Hub) to find EC2, RDS, Lambda, and Fargate resources that are larger than their measured usage. Then consider Graviton: AWS states Graviton-based instances &lt;a href="https://aws.amazon.com/ec2/graviton/" rel="noopener noreferrer"&gt;cost up to 20% less than comparable x86-based EC2 instances&lt;/a&gt;. For interpreted languages (Node.js, Python, Ruby) and the JVM, the move is often just a multi-architecture container build plus a node pool change. Native dependencies and third-party agents are what usually block it, so test one service end to end before planning the fleet.&lt;/p&gt;
&lt;h3&gt;
  
  
  Cut data transfer and NAT gateway costs
&lt;/h3&gt;

&lt;p&gt;Data transfer is the line item teams understand least. The most common surprise is the NAT gateway. AWS charges &lt;a href="https://aws.amazon.com/vpc/pricing/" rel="noopener noreferrer"&gt;an hourly charge per NAT gateway plus a per-GB data processing charge&lt;/a&gt; for every gigabyte processed through it, regardless of source or destination (AWS's example rate in US East (Ohio) is $0.045 per hour and $0.045 per GB). So a private-subnet service pulling container images or reading S3 through a NAT gateway pays a processing fee on every byte.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add gateway VPC endpoints for S3 and DynamoDB.&lt;/strong&gt; AWS states there are &lt;a href="https://aws.amazon.com/vpc/pricing/" rel="noopener noreferrer"&gt;no data processing or hourly charges for gateway-type VPC endpoints&lt;/a&gt;, and S3 traffic then bypasses the NAT gateway. This is usually the highest return-on-effort change in the whole network.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use a pull-through cache or ECR in the same Region&lt;/strong&gt; for container images rather than pulling from public registries through NAT on every node start.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Watch cross-AZ traffic.&lt;/strong&gt; Traffic between Availability Zones carries data transfer charges. Chatty services and replicas spread across zones can generate a surprising bill. Check the &lt;code&gt;DataTransfer-Regional-Bytes&lt;/code&gt; usage types in Cost Explorer before optimizing, so you fix what actually costs money.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6ba1hiilrowynfuftx9j.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6ba1hiilrowynfuftx9j.webp" alt="A private-subnet service reaching S3 through a NAT gateway pays $0.045 per hour plus $0.045 per GB, while the same traffic through a gateway VPC endpoint has no hourly or data processing charge." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A gateway endpoint takes S3 traffic off the NAT gateway and its per-GB processing fee.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Storage tiers and lifecycle policies
&lt;/h3&gt;

&lt;p&gt;S3 buckets that only grow are a slow leak. Apply lifecycle rules to anything with a predictable access pattern (logs, backups, build artifacts) and use &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/intelligent-tiering-overview.html" rel="noopener noreferrer"&gt;S3 Intelligent-Tiering&lt;/a&gt; where access is unpredictable. Intelligent-Tiering moves objects to an Infrequent Access tier after 30 days without access and to Archive Instant Access after 90, for a small per-object monitoring charge; objects under 128 KB are not monitored or tiered, so it does little for buckets full of tiny files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_s3_bucket_lifecycle_configuration" "logs" {
  bucket = aws_s3_bucket.logs.id

  rule {
    id     = "logs-tiering-and-expiry"
    status = "Enabled"

    filter {
      prefix = "logs/"
    }

    transition {
      days          = 30
      storage_class = "STANDARD_IA"
    }

    expiration {
      days = 365
    }

    abort_incomplete_multipart_upload {
      days_after_initiation = 7
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Reduce observability and log costs without losing visibility
&lt;/h3&gt;

&lt;p&gt;Observability is often the second or third largest line on a startup's bill once you add the vendor invoice (Datadog, New Relic, Grafana Cloud, or CloudWatch itself). The goal is not less visibility; it is paying only for data someone will actually look at. Work through these in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Find the top emitters.&lt;/strong&gt; In most systems a handful of services and log lines produce most of the volume: health checks, debug logs left on, per-request access logs, and retry loops. Rank by volume per service and per log pattern before changing anything.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Drop or demote noise at the source.&lt;/strong&gt; Set production log levels to INFO or WARN, stop logging successful health checks, and sample high-volume success logs (for example keep 1 in 100 successful requests, all errors).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Filter before indexing.&lt;/strong&gt; Most vendors let you ingest without indexing. In Datadog, &lt;a href="https://docs.datadoghq.com/logs/log_configuration/indexes/" rel="noopener noreferrer"&gt;exclusion filters&lt;/a&gt; discard logs from indexes while still letting them feed log-based metrics and archives. Convert "count of X" log searches into metrics, which are far cheaper to keep.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tier by access.&lt;/strong&gt; CloudWatch's &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CloudWatch_Logs_Log_Classes.html" rel="noopener noreferrer"&gt;Infrequent Access log class&lt;/a&gt; has a lower ingestion price per GB but drops features such as Live Tail, metric filters, and subscription filters, and a log group's class cannot be changed after creation. Use it for audit and forensic logs you query rarely, and archive to S3 for long-term retention.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sample traces intelligently.&lt;/strong&gt; Tail sampling keeps every error and slow trace while sampling the boring ones. With the OpenTelemetry Collector:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;processors:
  tail_sampling:
    decision_wait: 10s
    policies:
      - name: keep-errors
        type: status_code
        status_code:
          status_codes: [ERROR]
      - name: keep-slow
        type: latency
        latency:
          threshold_ms: 2000
      - name: sample-the-rest
        type: probabilistic
        probabilistic:
          sampling_percentage: 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Control metric cardinality.&lt;/strong&gt; Custom metrics tagged with user IDs, request IDs, or pod names explode series counts. Drop high-cardinality labels in the collector or agent before they reach the vendor.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To prove you did not lose visibility, keep a short list of the queries and alerts your on-call actually used in the last quarter and confirm each one still works after every change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Days 61 to 90: Savings Plans vs Reserved Instances vs Spot
&lt;/h2&gt;

&lt;p&gt;Commitments are the largest discount available and the easiest to get wrong. Buy them last, on the baseline that remains after cleanup and rightsizing. AWS defines &lt;a href="https://docs.aws.amazon.com/savingsplans/latest/userguide/plan-types.html" rel="noopener noreferrer"&gt;four Savings Plans types&lt;/a&gt;, all with 1-year or 3-year terms and All Upfront, Partial Upfront, or No Upfront payment:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Maximum discount (per AWS)&lt;/th&gt;
&lt;th&gt;Flexibility&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;Compute Savings Plans&lt;/td&gt;
&lt;td&gt;Up to 66% off On-Demand&lt;/td&gt;
&lt;td&gt;Any EC2 family, size, Region, OS; also Fargate and Lambda&lt;/td&gt;
&lt;td&gt;Most startups; teams still changing instance types, moving to Graviton, or shifting to Fargate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EC2 Instance Savings Plans&lt;/td&gt;
&lt;td&gt;Up to 72% off On-Demand&lt;/td&gt;
&lt;td&gt;One instance family in one Region; any size within it&lt;/td&gt;
&lt;td&gt;Very stable fleets you will not migrate during the term&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database Savings Plans&lt;/td&gt;
&lt;td&gt;Up to 35%&lt;/td&gt;
&lt;td&gt;Aurora, RDS, DynamoDB, ElastiCache, DocumentDB, OpenSearch and others, across engines and Regions&lt;/td&gt;
&lt;td&gt;Steady database spend, especially if you may change engines or instance generations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reserved Instances&lt;/td&gt;
&lt;td&gt;Varies by service and term&lt;/td&gt;
&lt;td&gt;Tied to specific attributes&lt;/td&gt;
&lt;td&gt;Services not covered by a Savings Plan you need, or capacity reservations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spot&lt;/td&gt;
&lt;td&gt;Up to 90% off On-Demand&lt;/td&gt;
&lt;td&gt;Can be interrupted with a two-minute notice&lt;/td&gt;
&lt;td&gt;Interruption-tolerant workloads; not a commitment at all&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Our guidance for a growing startup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prefer Compute Savings Plans.&lt;/strong&gt; You give up a few percentage points versus EC2 Instance plans and keep the freedom to change architecture. For a team that is still evolving, that freedom is worth more.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cover the floor, not the average.&lt;/strong&gt; Commit to the lowest sustained hourly compute spend over recent months, not the average. Under-committing costs you a small missed discount; over-committing costs you money for nothing, and AWS states the terms &lt;a href="https://docs.aws.amazon.com/savingsplans/latest/userguide/plan-types.html" rel="noopener noreferrer"&gt;cannot be changed after purchase&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ladder purchases.&lt;/strong&gt; Buy smaller plans every quarter rather than one large plan, so expiries are staggered and you can adjust as you grow.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Know what is not covered.&lt;/strong&gt; On EKS, Savings Plans cover the underlying EC2 instances but not the EKS cluster charge itself.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbft20zazz93pz8sis908.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbft20zazz93pz8sis908.webp" alt="Chart of hourly compute spend over recent months: the Savings Plan commitment sits at the lowest sustained spend, while committing at the average leaves dips below it where you pay for nothing, and terms cannot be changed after purchase." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Commit to the lowest sustained spend so every committed dollar is actually used.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Cost Explorer generates Savings Plans purchase recommendations from your history; treat them as a starting point and adjust for anything you know is about to change (a migration, a big customer, a planned Graviton move).&lt;/p&gt;
&lt;h2&gt;
  
  
  Guardrails that do not slow engineers down
&lt;/h2&gt;

&lt;p&gt;A good guardrail is automatic, explains itself, and has an obvious escape hatch. A bad one is a ticket. Here are the guardrails we install, roughly in order of value:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Guardrail&lt;/th&gt;
&lt;th&gt;How to implement&lt;/th&gt;
&lt;th&gt;Why it does not slow anyone&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Default tags&lt;/td&gt;
&lt;td&gt;Terraform provider &lt;code&gt;default_tags&lt;/code&gt;, Kubernetes labels set in shared Helm charts&lt;/td&gt;
&lt;td&gt;Engineers get tagging for free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTLs on ephemeral environments&lt;/td&gt;
&lt;td&gt;A &lt;code&gt;ttl&lt;/code&gt; tag or label plus a scheduled cleanup job; preview environments destroyed on PR merge or close&lt;/td&gt;
&lt;td&gt;Nobody has to remember to clean up&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auto-stop non-prod after hours&lt;/td&gt;
&lt;td&gt;Instance Scheduler on AWS, KEDA cron, or scaling node groups to zero&lt;/td&gt;
&lt;td&gt;Opt-out tag and a wake-up command&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Policy as code in audit mode first&lt;/td&gt;
&lt;td&gt;Kyverno or OPA Gatekeeper in Kubernetes, tag policies and SCPs in AWS Organizations&lt;/td&gt;
&lt;td&gt;Warns before it blocks; blocks only what is clearly wrong&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost estimate in every infrastructure PR&lt;/td&gt;
&lt;td&gt;Infracost or similar in CI&lt;/td&gt;
&lt;td&gt;Feedback arrives in the review the engineer is already doing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anomaly alerts routed to owners&lt;/td&gt;
&lt;td&gt;Cost Anomaly Detection per account or cost category, sent to the owning team's channel&lt;/td&gt;
&lt;td&gt;Only fires when something is actually unusual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sandbox budget actions&lt;/td&gt;
&lt;td&gt;AWS Budgets actions applying an SCP when a sandbox account exceeds its budget&lt;/td&gt;
&lt;td&gt;Applies only to experiments, never production&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Start policies in audit mode, look at what they would have blocked, fix the false positives, then enforce. A Kyverno policy that requires a &lt;code&gt;team&lt;/code&gt; label on Deployments, in audit mode, looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-team-label
spec:
  background: true
  rules:
    - name: check-team-label
      match:
        any:
          - resources:
              kinds:
                - Deployment
      validate:
        failureAction: Audit
        message: "Every Deployment needs a team label for cost allocation."
        pattern:
          metadata:
            labels:
              team: "?*"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The biggest guardrail of all is a self-service path that is cheap by default. If spinning up an environment through your platform gives you right-sized, scheduled, tagged, TTL-bound resources automatically, engineers will use it because it is faster, and cost control comes along for free. That is the idea behind a &lt;a href="https://meteorops.com/blog/one-click-environment-the-ultimate-devops-goal" rel="noopener noreferrer"&gt;one-click environment&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to give engineers cost visibility in their workflow
&lt;/h2&gt;

&lt;p&gt;Engineers care about cost when they can see the effect of their own changes, quickly, in a place they already look. Three layers work well together:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Before deploy: cost in the pull request.&lt;/strong&gt; &lt;a href="https://www.infracost.io/docs/" rel="noopener noreferrer"&gt;Infracost&lt;/a&gt; estimates costs from Terraform, CloudFormation, and AWS CDK and can comment on each pull request with the cost difference, policy violations, and tagging issues introduced by the change, with integrations for GitHub Actions, GitLab CI, and Azure DevOps. A reviewer seeing "+$1,400/month" next to a database instance change is the cheapest cost control you will ever add.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;After deploy: per-team and per-service dashboards.&lt;/strong&gt; Built from your cost allocation tags (Cost Explorer, Cost Categories, or a CUR-based dashboard) and, for Kubernetes, from Kubecost or OpenCost by namespace. The test of a good dashboard: can a team lead answer "what did my services cost last week and why did it change" in under a minute?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Weekly: a short showback message.&lt;/strong&gt; A Slack post per team with last week's spend, the change versus the prior week, and the top three movers. No action required unless something looks wrong. Showback (reporting cost to owners) builds the habit without the overhead of chargeback (billing it to their budget).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fygp691fmg8k616fgnab0.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fygp691fmg8k616fgnab0.webp" alt="Three layers of cost visibility: before deploy, a cost diff such as +$1,400/month in the pull request; after deploy, per-team dashboards; weekly, a showback message with the top three movers." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Engineers see cost at review time, after release, and in a weekly summary, without leaving their workflow.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Where possible, express cost as a unit metric the team cares about: cost per 1,000 requests, per active customer, per build, or per GB processed. "Our bill went up 20%" is alarming; "cost per customer went down 8% while customers grew 30%" is a success, and a unit metric tells you which one you are looking at.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to get engineering buy-in for cost reduction
&lt;/h2&gt;

&lt;p&gt;Most engineers do not resist saving money. They resist being blamed, being slowed down, and being handed toil. Get buy-in by addressing those directly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Frame it as engineering quality, not frugality.&lt;/strong&gt; Over-provisioned requests, unbounded logs, and forgotten environments are bugs in how the system is run. Fixing them is the same craft as fixing a slow query.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The platform team does the first round.&lt;/strong&gt; Quick wins in the first 30 days should cost feature teams zero hours. Showing a real reduction before asking anyone for anything earns the right to ask later.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Give ownership, not orders.&lt;/strong&gt; Each team sees its own spend and decides how to act. Leadership sets a target (for example, a unit cost target or a cap on cloud spend as a percentage of revenue), not a list of instance types.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Never trade reliability for savings silently.&lt;/strong&gt; Every change that touches production capacity (Spot, consolidation, lower requests) goes through the normal review and rollout process. One cost-driven outage will kill the program.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Celebrate the wins publicly.&lt;/strong&gt; Share savings in engineering all-hands with the name of the team that found them. It costs nothing and turns cost into something people want to be associated with.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are an engineering leader structuring this for the first time, our &lt;a href="https://meteorops.com/blog/the-cto-devops-handbook-simple-principles-and-examples" rel="noopener noreferrer"&gt;CTO DevOps handbook&lt;/a&gt; covers how to set ownership boundaries between platform and product teams.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloud cost optimization tools by category
&lt;/h2&gt;

&lt;p&gt;You do not need a paid platform to start. AWS native tools cover the first 90 days for most startups; third-party tools earn their place when you need Kubernetes allocation, multi-cloud views, or engineer-facing workflows. Capabilities below are as described in each product's own documentation; check current pricing directly with the vendor.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;AWS native&lt;/th&gt;
&lt;th&gt;Third-party or open source&lt;/th&gt;
&lt;th&gt;Use it for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Spend analysis and allocation&lt;/td&gt;
&lt;td&gt;Cost Explorer, cost allocation tags, Cost Categories, Cost and Usage Reports&lt;/td&gt;
&lt;td&gt;CloudZero, Vantage, Kubecost, OpenCost&lt;/td&gt;
&lt;td&gt;Who spends what, and unit costs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recommendations&lt;/td&gt;
&lt;td&gt;Cost Optimization Hub, Compute Optimizer, Trusted Advisor&lt;/td&gt;
&lt;td&gt;Most commercial FinOps platforms&lt;/td&gt;
&lt;td&gt;Idle resources, rightsizing, commitments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alerts and budgets&lt;/td&gt;
&lt;td&gt;Cost Anomaly Detection, AWS Budgets and budget actions&lt;/td&gt;
&lt;td&gt;Commercial platforms with Slack routing&lt;/td&gt;
&lt;td&gt;Catching spikes in days, not at month end&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kubernetes cost&lt;/td&gt;
&lt;td&gt;Split cost allocation data for EKS&lt;/td&gt;
&lt;td&gt;Kubecost, OpenCost; Karpenter for consolidation&lt;/td&gt;
&lt;td&gt;Namespace and workload cost, bin-packing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost in the developer workflow&lt;/td&gt;
&lt;td&gt;None built in&lt;/td&gt;
&lt;td&gt;Infracost&lt;/td&gt;
&lt;td&gt;Cost diffs on pull requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scheduling non-prod&lt;/td&gt;
&lt;td&gt;Instance Scheduler on AWS&lt;/td&gt;
&lt;td&gt;KEDA cron scaler, kube-downscaler&lt;/td&gt;
&lt;td&gt;Turning environments off after hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Policy and governance&lt;/td&gt;
&lt;td&gt;Tag policies, SCPs in AWS Organizations, AWS Config rules&lt;/td&gt;
&lt;td&gt;Kyverno, OPA Gatekeeper, Cloud Custodian&lt;/td&gt;
&lt;td&gt;Enforcing tags, TTLs, and allowed resource types&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observability cost&lt;/td&gt;
&lt;td&gt;CloudWatch log classes, retention settings&lt;/td&gt;
&lt;td&gt;OpenTelemetry Collector (sampling, filtering), vendor exclusion filters&lt;/td&gt;
&lt;td&gt;Reducing log, metric, and trace volume&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When you evaluate a paid cost platform for a team that is mostly engineers, ask three questions. Does it attribute Kubernetes spend to teams without a tagging project? Does it deliver insights where engineers already work (pull requests, Slack, the IDE) rather than in another dashboard? And does it need agents or ongoing maintenance from your platform team? A tool that adds operational overhead is just another cost line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common cloud cost reduction mistakes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Buying Savings Plans before cleanup.&lt;/strong&gt; You lock in a discount on waste. Clean up and rightsize first, then commit to the floor.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rightsizing nodes instead of requests.&lt;/strong&gt; On Kubernetes, smaller nodes do nothing if pod requests stay inflated. Fix requests, then let consolidation shrink the fleet.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Setting memory limits too tight.&lt;/strong&gt; CPU over-request is cheap to fix; memory under-provisioning causes OOM kills and restarts. Lower memory requests gradually and watch.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Moving everything to Spot at once.&lt;/strong&gt; Without PodDisruptionBudgets, multiple replicas, and graceful shutdown, Spot interruptions become incidents.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ignoring the NAT gateway.&lt;/strong&gt; Teams optimize compute for weeks while S3 and image traffic quietly pay per-GB processing fees. Add gateway endpoints early.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cutting logs blindly.&lt;/strong&gt; Dropping logs without checking which queries and alerts depend on them saves money until the next incident. Inventory what on-call uses first.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Too many mandatory tags.&lt;/strong&gt; Ten required tag keys means nobody complies. Three or four, applied by default, beats ten applied by hand.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Making it a project instead of a habit.&lt;/strong&gt; A one-time cleanup decays. Anomaly alerts, PR cost diffs, and weekly showback keep it from coming back.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cloud cost reduction checklist
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Visibility&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Production and non-production in separate AWS accounts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;3 to 4 mandatory tags applied through &lt;code&gt;default_tags&lt;/code&gt; and shared Helm charts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cost allocation tags activated in the Billing console&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Kubernetes cost allocation by namespace (split cost allocation data, Kubecost, or OpenCost)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cost Anomaly Detection and budgets on every account, routed to owners&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Quick wins&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Unattached EBS volumes, unused Elastic IPs, and old snapshots removed&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Retention set on every CloudWatch log group&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;gp2 volumes migrated to gp3; StorageClass defaults to gp3&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Non-production scheduled off outside working hours&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;S3 and DynamoDB gateway endpoints in every VPC with a NAT gateway&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Structural changes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Pod requests rightsized from measured usage; VPA in recommendation mode&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Node consolidation enabled with disruption budgets and PDBs in place&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Spot used for CI, batch, and stateless multi-replica services&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;S3 lifecycle rules on log, backup, and artifact buckets&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Log levels, sampling, and exclusion filters reviewed for the top emitters&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Compute Savings Plans covering the sustained floor, purchased in quarterly ladders&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Guardrails&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Cost diff on every infrastructure pull request&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;TTLs on preview and ephemeral environments&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Policy as code for tags, running in audit mode before enforce&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Weekly per-team showback with unit cost trends&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ: reducing cloud spend without slowing engineering
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do engineering teams reduce cloud spend without slowing delivery?
&lt;/h3&gt;

&lt;p&gt;They move cost control into defaults and automation instead of approvals. The platform team removes waste (idle resources, log retention, after-hours schedules), rightsizes Kubernetes requests, and buys Savings Plans on the cleaned baseline, while feature teams only see a cost diff in their pull requests and a weekly report on their own services. Nobody waits on a ticket to ship.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do engineering teams reduce log volume and cost without losing visibility?
&lt;/h3&gt;

&lt;p&gt;Start by ranking log volume by service and pattern, because a few sources usually dominate. Drop health-check and debug noise, sample high-volume success logs while keeping all errors, convert count-style log searches into metrics, and use tail sampling for traces. Before and after each change, confirm the queries and alerts on-call actually used still work.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I create FinOps guardrails that don't slow down engineers?
&lt;/h3&gt;

&lt;p&gt;Make every guardrail automatic and give it an escape hatch. Examples: default tags in Terraform, TTLs on preview environments, non-production that auto-stops after hours with a one-command wake-up, policies that run in audit mode before they enforce, and anomaly alerts that only fire on unusual spend. Avoid manual approval steps for routine resources.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I get developers to care about cloud costs without making it a burden?
&lt;/h3&gt;

&lt;p&gt;Show them the cost of their own changes, in tools they already use, and do not blame them for the past. A cost estimate in the pull request and a weekly per-team spend summary create awareness with no extra work. Express cost as a unit metric (per request, per customer) so teams can see efficiency improve even while the total bill grows with the business.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the best way for engineering teams to take ownership of cloud cost reduction?
&lt;/h3&gt;

&lt;p&gt;Give each team its own spend data, attributed through accounts and tags, and a target such as a unit cost or a share of revenue, then let them decide how to hit it. The platform team owns shared levers (commitments, cluster consolidation, network endpoints); service teams own their requests, environments, and logging. Showback before chargeback keeps it collaborative.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should a startup use Savings Plans or Reserved Instances?
&lt;/h3&gt;

&lt;p&gt;For most startups, Compute Savings Plans are the better default because they apply across instance families, Regions, Fargate, and Lambda, so you can keep changing your architecture. EC2 Instance Savings Plans offer a higher maximum discount but lock you to one instance family in one Region. Buy only after cleanup, cover your lowest sustained usage, and stagger purchases.&lt;/p&gt;

&lt;h3&gt;
  
  
  What should I look for in a cloud cost platform if my team is mostly engineers?
&lt;/h3&gt;

&lt;p&gt;Look for Kubernetes-aware allocation, feedback inside the engineering workflow (pull requests, Slack), and low operational overhead. Check whether it needs agents or ongoing tuning from your platform team. Many teams get most of the value from AWS native tools plus Infracost and OpenCost before a paid platform is justified.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much can you realistically save?
&lt;/h3&gt;

&lt;p&gt;It depends entirely on your starting point, so be wary of anyone quoting a percentage before looking at your bill. The verifiable ceilings come from AWS itself: up to 66% off On-Demand with Compute Savings Plans, up to 90% with Spot, and up to 70% on instances that only need to run during business hours. The biggest real-world savings usually come from waste you can see in the first week of analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to start this week
&lt;/h2&gt;

&lt;p&gt;If you do only three things this week: activate cost allocation tags, turn on Cost Anomaly Detection for every account, and run the three idle-resource queries above. You will have attribution starting to flow, an early warning system, and probably a first round of savings, without taking a single hour from your feature teams. If you want senior engineers to run this playbook with your team, MeteorOps offers a &lt;a href="https://meteorops.com/devops-setup-for-production-free-consultation" rel="noopener noreferrer"&gt;free consultation&lt;/a&gt; to review your setup and find the biggest levers.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>kubernetes</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Kubernetes Requests and Limits: Set CPU and Memory Right</title>
      <dc:creator>Michael Zion</dc:creator>
      <pubDate>Fri, 23 Oct 2026 17:40:03 +0000</pubDate>
      <link>https://dev.to/meteorops/kubernetes-requests-and-limits-set-cpu-and-memory-right-cl8</link>
      <guid>https://dev.to/meteorops/kubernetes-requests-and-limits-set-cpu-and-memory-right-cl8</guid>
      <description>&lt;p&gt;&lt;em&gt;This guide was written by &lt;a href="https://meteorops.com/team/arthur-azrieli" rel="noopener noreferrer"&gt;Arthur Azrieli&lt;/a&gt;, CTO at MeteorOps, and first published on the &lt;a href="https://meteorops.com/blog/how-to-set-kubernetes-resource-requests-and-limits-without-throttling-apps" rel="noopener noreferrer"&gt;MeteorOps blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Set a CPU request and a memory request on every container, sized from real usage: the 95th percentile of CPU and the peak memory working set over one to two weeks, plus headroom. Set the memory limit equal to the memory request. For latency-sensitive services, leave the CPU limit off (or set it well above the request) unless you have a specific reason to cap CPU. That combination keeps the scheduler honest, avoids Completely Fair Scheduler (CFS) throttling, and makes out-of-memory (OOM) kills predictable instead of random.&lt;/p&gt;

&lt;p&gt;The rest of this guide explains why each of those rules exists, shows how to measure the numbers with PromQL and kubectl, covers the exceptions (multi-tenant clusters, batch jobs, quotas, Guaranteed pods), and ends with runtime settings for the JVM, Go and Node.js, a troubleshooting table, and a checklist. If you only need the definitions, the &lt;a href="https://meteorops.com/glossary/kubernetes-resource-requests-and-limits" rel="noopener noreferrer"&gt;Kubernetes resource requests and limits glossary entry&lt;/a&gt; covers them in two minutes. This page is about choosing the numbers and changing them safely in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  What requests and limits actually do (four different mechanisms)
&lt;/h2&gt;

&lt;p&gt;Most confusion about requests and limits comes from treating them as one feature. In reality four separate components read those two fields, and each one does something different with them.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Who reads it&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CPU request&lt;/td&gt;
&lt;td&gt;Scheduler, then the kernel&lt;/td&gt;
&lt;td&gt;The scheduler only places the pod on a node whose unreserved CPU covers the sum of requests. At runtime the request becomes the container's CPU weight, so under contention CPU is shared in proportion to requests.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU limit&lt;/td&gt;
&lt;td&gt;Kernel CFS bandwidth control&lt;/td&gt;
&lt;td&gt;The container gets a quota of CPU time per 100 ms period (the kubelet default &lt;code&gt;cpuCFSQuotaPeriod&lt;/code&gt;). Once the quota is spent, every thread in the container waits for the next period. That wait is throttling, and it happens even if the node is idle.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory request&lt;/td&gt;
&lt;td&gt;Scheduler and the kubelet eviction manager&lt;/td&gt;
&lt;td&gt;Used for placement, and later to decide who gets evicted first when the node runs low on memory: pods using more than their request go first.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory limit&lt;/td&gt;
&lt;td&gt;Kernel cgroup memory controller&lt;/td&gt;
&lt;td&gt;A hard ceiling. When the container exceeds it and the kernel cannot reclaim memory, the kernel OOM killer terminates it and the container shows &lt;code&gt;OOMKilled&lt;/code&gt; with exit code 137.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1fbllgjimu5t1d3p9ysv.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1fbllgjimu5t1d3p9ysv.webp" alt="Diagram: CPU request goes to the scheduler and kernel weight, CPU limit to the CFS quota, memory request to the eviction manager, memory limit to the OOM killer." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Each field is read by a different component, at placement time or at runtime.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Two consequences matter for sizing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The scheduler never looks at actual usage.&lt;/strong&gt; A node can be "full" at 15% real CPU because requests are inflated, or overcommitted at 100% real usage because requests are tiny. Requests are a reservation, and the cluster is only as efficient as those reservations are accurate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CPU is compressible, memory is not.&lt;/strong&gt; Too little CPU makes a process slow. Too little memory kills it. That is why the recommended policies for CPU and memory are different, and why copying the same pattern for both is the most common mistake.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One detail that surprises people: if you set a limit and no request, Kubernetes copies the limit into the request (unless a LimitRange default applies). A manifest with only &lt;code&gt;limits.cpu: "2"&lt;/code&gt; reserves two full CPUs on the node.&lt;/p&gt;
&lt;h2&gt;
  
  
  QoS classes and eviction order: Guaranteed, Burstable, BestEffort
&lt;/h2&gt;

&lt;p&gt;From the requests and limits you set, Kubernetes derives a Quality of Service (QoS) class for each pod. You never set the class directly, and in-place resize cannot change it later, so pick it deliberately.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;QoS class&lt;/th&gt;
&lt;th&gt;How you get it&lt;/th&gt;
&lt;th&gt;OOM score adjustment&lt;/th&gt;
&lt;th&gt;When to use it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Guaranteed&lt;/td&gt;
&lt;td&gt;Every container has CPU and memory requests equal to limits&lt;/td&gt;
&lt;td&gt;-997 (killed last)&lt;/td&gt;
&lt;td&gt;Databases, stateful systems, anything that needs exclusive cores with the static CPU manager policy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Burstable&lt;/td&gt;
&lt;td&gt;At least one container has a request or limit, but it does not meet the Guaranteed rule&lt;/td&gt;
&lt;td&gt;Between 2 and 999, lower for pods with larger memory requests&lt;/td&gt;
&lt;td&gt;Most stateless services, especially with memory request = limit and no CPU limit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BestEffort&lt;/td&gt;
&lt;td&gt;No requests or limits on any container&lt;/td&gt;
&lt;td&gt;1000 (killed first)&lt;/td&gt;
&lt;td&gt;Almost never in production. Throwaway jobs on dedicated nodes at most&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When a node runs short of memory, the kubelet evicts pods in this order: first BestEffort and Burstable pods whose usage exceeds their requests (ranked by priority, then by how far over their request they are), and last Guaranteed pods and Burstable pods that stay under their requests. Read that again, because it is the strongest argument for accurate memory requests: &lt;strong&gt;a pod that stays within its memory request is in the last group to be evicted, regardless of QoS class.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw5fe8ks0uz4fvudiecr6.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw5fe8ks0uz4fvudiecr6.webp" alt="Infographic: BestEffort pods and Burstable pods above their memory request are evicted first; Guaranteed pods and Burstable pods within their request are evicted last." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Eviction order under node memory pressure. Staying within the memory request puts a pod in the last group.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The default Linux hard eviction threshold is &lt;code&gt;memory.available&amp;lt;100Mi&lt;/code&gt;. If the kernel OOM killer acts before the kubelet notices pressure, it picks the container with the highest OOM score, which is why BestEffort pods and Burstable pods with small requests die first.&lt;/p&gt;
&lt;h2&gt;
  
  
  Should you set CPU limits in Kubernetes?
&lt;/h2&gt;

&lt;p&gt;This is the most searched and most argued part of the topic, so here is a clear position: &lt;strong&gt;set CPU requests everywhere; drop CPU limits for latency-sensitive services by default; keep them where you have a concrete reason.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Why CPU limits cause throttling at low average usage
&lt;/h3&gt;

&lt;p&gt;The CFS quota is measured in CPU time per 100 ms period, summed across all threads in the container. A container with &lt;code&gt;limits.cpu: "1"&lt;/code&gt; gets 100 ms of CPU time per 100 ms period. If a request arrives and the application runs 8 threads in parallel (a garbage collector, a thread pool, a Node.js libuv pool), those threads can burn the whole 100 ms of quota in about 12.5 ms of wall-clock time. Every thread then sleeps for the remaining 87.5 ms of the period. Your dashboard shows 30% average CPU, and your p99 latency shows an extra 80 ms that nobody can explain.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7wmwvv5bydunp0maaem2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7wmwvv5bydunp0maaem2.webp" alt="Timeline of one 100 ms CFS period: eight threads run for the first 12.5 ms, then all wait 87.5 ms. Below, a dashboard shows 30% average CPU while p99 latency spikes by 80 ms." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;With limits.cpu: "1", eight busy threads spend the whole period's quota in 12.5 ms and then wait for the next period.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That is why "my pod is throttled but CPU usage is low" is not a contradiction. Averages over 30 seconds or a minute hide bursts that happen inside a 100 ms window.&lt;/p&gt;
&lt;h3&gt;
  
  
  Has the kernel fixed this?
&lt;/h3&gt;

&lt;p&gt;Partly. A real kernel bug made throttling much worse on multi-core machines: per-CPU slices of quota expired unused, so highly threaded workloads were throttled while using a fraction of their limit. The fix ("sched/fair: Fix low cpu usage with high throttling by removing expiration of cpu-local slices") landed in 2019 around Linux 5.4 and was backported to stable kernels, so any modern node image has it. What remains is not a bug: a hard quota per period is how CPU limits are supposed to work, and cgroup v2 (where the limit lives in &lt;code&gt;cpu.max&lt;/code&gt;) enforces the same quota and period model. Kubernetes does not expose the cgroup v2 burst setting, so you cannot ask the kernel to absorb short spikes above the limit.&lt;/p&gt;
&lt;h3&gt;
  
  
  What happens without a CPU limit
&lt;/h3&gt;

&lt;p&gt;Without a limit, a container can use idle CPU on the node. When the node is busy, CPU is divided by weight, and the weight comes from the CPU request. A pod with a 500m request competing against a pod with a 1000m request gets one third of the contended CPU. So removing limits is safe only when &lt;strong&gt;every&lt;/strong&gt; pod on the node has an honest CPU request. A pod with no request at all gets the minimum weight and starves first, which is the right outcome.&lt;/p&gt;
&lt;h3&gt;
  
  
  When to keep CPU limits
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Recommendation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Latency-sensitive API or web service on a cluster where all pods set requests&lt;/td&gt;
&lt;td&gt;No CPU limit, or a limit at 2x to 4x the request as a runaway guard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-tenant cluster or untrusted workloads&lt;/td&gt;
&lt;td&gt;Keep limits, so one tenant cannot consume idle CPU others expect to burst into&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Batch jobs, CI runners, video or ML preprocessing that will use every core it sees&lt;/td&gt;
&lt;td&gt;Keep limits, or isolate on dedicated node pools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Databases or workloads that need dedicated cores with the static CPU manager&lt;/td&gt;
&lt;td&gt;Guaranteed QoS with integer CPU requests equal to limits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Load tests and capacity benchmarks&lt;/td&gt;
&lt;td&gt;Limits equal to requests, so results reflect what you will reserve in production&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Namespace has a ResourceQuota on &lt;code&gt;limits.cpu&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Every pod must set a CPU limit (or receive one from a LimitRange default). Prefer quotas on &lt;code&gt;requests.cpu&lt;/code&gt; instead&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A practical middle ground that works well for teams that are nervous about removing limits entirely: keep a generous CPU limit (for example 4x the request) and alert on throttling. You keep protection against a runaway loop and lose almost all of the latency penalty.&lt;/p&gt;
&lt;h2&gt;
  
  
  Memory requests and limits: why request should equal limit
&lt;/h2&gt;

&lt;p&gt;For memory, the common advice is to set the memory request equal to the memory limit. It is good advice, and the reason is the eviction and OOM mechanics above.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Memory cannot be taken back.&lt;/strong&gt; If a pod is allowed to grow from a 512Mi request to a 2Gi limit, the scheduler placed it as if it needed 512Mi. When several neighbors do the same, the node runs out of real memory, and the kubelet or the kernel kills something. Often not the pod that grew.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Request = limit makes failures local.&lt;/strong&gt; The container can only hurt itself. If it needs more, it is OOMKilled with a clear signal instead of triggering node pressure that evicts unrelated pods.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It keeps you in the "evicted last" group.&lt;/strong&gt; A pod using at most its request is never in the first eviction group.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjhuye8m49ljlp5zdes88.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjhuye8m49ljlp5zdes88.webp" alt="Two nodes side by side. Left: a pod with a 512Mi request and 2Gi limit outgrows its reservation and a neighbor pod is evicted. Right: with request equal to limit, the growing pod is OOMKilled and neighbors are untouched." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A gap between memory request and limit is overcommit. Setting them equal keeps the failure inside the container that grew.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The exception is a workload with a rare, short, known memory spike (a nightly report, a large startup cache) on nodes with spare memory. A gap between request and limit can be acceptable there, but treat it as overcommit you have chosen, and watch node memory pressure.&lt;/p&gt;

&lt;p&gt;On cgroup v2 nodes, the kubelet by default sets &lt;code&gt;memory.oom.group&lt;/code&gt; on container cgroups (the &lt;code&gt;singleProcessOOMKill&lt;/code&gt; kubelet setting defaults to false), so when the OOM killer fires, all processes in the container are killed together. On cgroup v1, only the single largest process was killed, which could leave a container half alive with its main worker gone. If you run multi-process containers (for example a process manager with workers), expect the whole container to restart on cgroup v2.&lt;/p&gt;

&lt;p&gt;Kubernetes also has an opt-in &lt;code&gt;MemoryQoS&lt;/code&gt; feature gate that uses cgroup v2 &lt;code&gt;memory.high&lt;/code&gt; to throttle memory allocation before the limit for Burstable pods. It is off by default; do not size around it unless your platform team has enabled it.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to size requests and limits from real metrics, step by step
&lt;/h2&gt;

&lt;p&gt;Guessing produces either waste or incidents. Here is the method we use when tuning production clusters. It assumes Prometheus with cAdvisor metrics (scraped through the kubelet) and kube-state-metrics, which most clusters already have through kube-prometheus-stack or a managed equivalent.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 1: Collect at least 7 to 14 days of data
&lt;/h3&gt;

&lt;p&gt;The window must include peak traffic days, deploys, cron jobs, cache warmups and any weekly batch. A day of data underestimates almost everything. For brand-new services, start with a conservative guess, run for a week, then apply this method.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2: Find CPU usage at p95 and p99
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# p95 of 5-minute CPU usage per container over 14 days (in cores)
quantile_over_time(0.95,
  sum by (namespace, pod, container) (
    rate(container_cpu_usage_seconds_total{namespace="production", container!="", container!="POD"}[5m])
  )[14d:5m]
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Change &lt;code&gt;0.95&lt;/code&gt; to &lt;code&gt;0.99&lt;/code&gt; to see the tail. To get one number per workload across all of its replicas, wrap the expression in &lt;code&gt;max by (namespace, container) (...)&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 3: Measure throttling
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Fraction of CFS periods in which the container was throttled
sum by (namespace, pod, container) (
  increase(container_cpu_cfs_throttled_periods_total{namespace="production", container!=""}[5m])
)
/
sum by (namespace, pod, container) (
  increase(container_cpu_cfs_periods_total{namespace="production", container!=""}[5m])
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;These counters only exist for containers with a CPU limit. As a rule of thumb, a latency-sensitive service throttled in more than a few percent of periods during peak is worth fixing, and anything above 20% is almost certainly hurting tail latency. Correlate with your p99 latency before deciding. &lt;code&gt;container_cpu_cfs_throttled_seconds_total&lt;/code&gt; gives total time spent throttled if you prefer seconds to periods.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 4: Find peak memory working set
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Peak memory working set per container over 14 days (bytes)
max by (namespace, container) (
  max_over_time(container_memory_working_set_bytes{namespace="production", container!="", container!="POD"}[14d])
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Use the working set, not RSS or total usage: it is the number the kubelet uses for eviction decisions and what &lt;code&gt;kubectl top&lt;/code&gt; shows. Use the max, not a percentile, because memory sizing must survive the worst case.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 5: Compare against what you requested
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Requested CPU per container (cores), from kube-state-metrics
sum by (namespace, pod, container) (
  kube_pod_container_resource_requests{namespace="production", resource="cpu"}
)

# Containers whose last termination was an OOM kill
kube_pod_container_status_last_terminated_reason{namespace="production", reason="OOMKilled"} == 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Dividing the p95 usage from step 2 by the request from step 5 gives a utilization ratio per container. Below 0.3 means you are paying for capacity you do not use. Above 1.0 means the pod regularly relies on spare CPU it never reserved.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 6: Apply the sizing rules
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6r9gzx78e8wybp231nb3.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6r9gzx78e8wybp231nb3.webp" alt="Flow chart: 7 to 14 days of Prometheus metrics split into a CPU lane (p95 of 5-minute usage becomes the CPU request; CPU limit none or 2x to 4x) and a memory lane (peak working set plus 20% to 30% headroom becomes memory request equal to limit), then a gradual rollout." width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The sizing method on one page: p95 CPU for the CPU request, peak working set plus headroom for memory.&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Starting rule&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CPU request&lt;/td&gt;
&lt;td&gt;p95 of 5-minute usage over 14 days&lt;/td&gt;
&lt;td&gt;Use p99 for services with strict latency SLOs. If HPA scales on CPU, see the HPA section: the request is also your scaling denominator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU limit&lt;/td&gt;
&lt;td&gt;None, or 2x to 4x the request&lt;/td&gt;
&lt;td&gt;Equal to request only for Guaranteed workloads and benchmarks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory request&lt;/td&gt;
&lt;td&gt;Peak working set + 20% to 30%&lt;/td&gt;
&lt;td&gt;More headroom for runtimes with large off-heap usage (JVM) and for workloads that grow with data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory limit&lt;/td&gt;
&lt;td&gt;Equal to the memory request&lt;/td&gt;
&lt;td&gt;Raise both together. Never set the limit below the observed peak&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ephemeral storage&lt;/td&gt;
&lt;td&gt;Request what you write to local disk (logs, temp files, emptyDir)&lt;/td&gt;
&lt;td&gt;Pods that exceed an ephemeral-storage limit are evicted&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are starting points, not laws. The point is to have a rule, apply it consistently, and revisit it on a schedule, instead of tuning each service from memory during an incident.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 7: Roll out like any other risky change
&lt;/h3&gt;

&lt;p&gt;Changing resources on a Deployment triggers a rollout. Lowering memory is the dangerous direction, so lower it in steps (no more than 20% to 25% at a time), canary it on one service first, and watch OOM kills and restarts for a full traffic cycle. Make sure the rollout cannot take capacity below what you need: a sensible &lt;code&gt;maxUnavailable&lt;/code&gt;, a PodDisruptionBudget, and readiness probes that do not flap under the new CPU budget (see &lt;a href="https://meteorops.com/blog/how-to-configure-kubernetes-liveness-probes-without-causing-restart-loops" rel="noopener noreferrer"&gt;how to configure liveness probes without causing restart loops&lt;/a&gt;, because a CPU-starved pod failing its liveness probe is a classic self-inflicted outage).&lt;/p&gt;
&lt;h2&gt;
  
  
  Copy-paste YAML for common workloads
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Latency-sensitive stateless service (Burstable, no CPU limit)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
        - name: api
          image: registry.example.com/api:1.42.0
          resources:
            requests:
              cpu: 500m
              memory: 768Mi
            limits:
              memory: 768Mi   # memory limit = request; no CPU limit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Same service with a runaway guard on CPU
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;          resources:
            requests:
              cpu: 500m
              memory: 768Mi
            limits:
              cpu: "2"        # 4x the request: protects the node, rarely throttles
              memory: 768Mi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Guaranteed pod for a database or pinned workload
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;          resources:
            requests:
              cpu: "4"        # integer CPUs are required for exclusive cores with the static CPU manager
              memory: 16Gi
            limits:
              cpu: "4"
              memory: 16Gi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Batch job that should not hog the node
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: batch/v1
kind: Job
metadata:
  name: nightly-export
spec:
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: export
          image: registry.example.com/export:2.3.1
          resources:
            requests:
              cpu: "1"
              memory: 2Gi
            limits:
              cpu: "2"
              memory: 2Gi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  LimitRange and ResourceQuota: namespace defaults and guardrails
&lt;/h2&gt;

&lt;p&gt;Individual manifests drift. Namespace policies make sure nothing reaches production as BestEffort and that one team cannot reserve the whole cluster.&lt;/p&gt;

&lt;p&gt;A LimitRange fills in defaults for containers that omit requests or limits, and rejects values outside a min and max:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: v1
kind: LimitRange
metadata:
  name: container-defaults
  namespace: production
spec:
  limits:
    - type: Container
      defaultRequest:      # applied when a container sets no request
        cpu: 100m
        memory: 256Mi
      default:             # applied when a container sets no limit
        memory: 256Mi      # no CPU default limit on purpose
      min:
        cpu: 10m
        memory: 32Mi
      max:
        cpu: "8"
        memory: 16Gi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A ResourceQuota caps the total a namespace can request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-quota
  namespace: production
spec:
  hard:
    requests.cpu: "40"
    requests.memory: 80Gi
    limits.memory: 80Gi
    pods: "200"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what is missing: &lt;code&gt;limits.cpu&lt;/code&gt;. Once a quota tracks a resource, every new pod in the namespace must declare that value or the API server may reject it. A quota on &lt;code&gt;limits.cpu&lt;/code&gt; therefore forces CPU limits on every pod, which quietly undoes a "no CPU limits" policy. Quota on CPU requests, and on memory requests and limits, is usually what you want.&lt;/p&gt;

&lt;p&gt;Keep LimitRange defaults small and treat them as a safety net. A default of 100m CPU for a service that really needs two cores makes it schedulable and then slow; the real fix is still a measured request in the manifest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vertical Pod Autoscaler and in-place pod resize
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Use VPA in recommendation mode first
&lt;/h3&gt;

&lt;p&gt;The Vertical Pod Autoscaler (VPA) watches usage and computes recommended requests. Its &lt;code&gt;updateMode&lt;/code&gt; controls what it does with them:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;updateMode&lt;/th&gt;
&lt;th&gt;Behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Off&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Only writes recommendations to the VPA status. Nothing changes on pods&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Initial&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sets requests when pods are created, never afterwards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Recreate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Evicts pods whose requests differ significantly from the recommendation (respects PodDisruptionBudgets)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;InPlaceOrRecreate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Resizes running pods in place, falls back to eviction if that fails&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;InPlace&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Only resizes in place and never evicts; retries later if a resize is not possible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Auto&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deprecated; currently behaves like &lt;code&gt;Recreate&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Running VPA with &lt;code&gt;updateMode: "Off"&lt;/code&gt; across a namespace is the cheapest way to get a second opinion on every workload, and it feeds the sizing method above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: api
  namespace: production
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api
  updatePolicy:
    updateMode: "Off"
  resourcePolicy:
    containerPolicies:
      - containerName: "*"
        minAllowed:
          cpu: 50m
          memory: 64Mi
        maxAllowed:
          cpu: "4"
          memory: 8Gi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl describe vpa api -n production   # see Target, Lower Bound and Upper Bound per container
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not let VPA and the Horizontal Pod Autoscaler (HPA) act on the same metric. The VPA project explicitly warns against using both on CPU or memory for the same workload; they fight each other. VPA on memory with HPA on CPU, or HPA on custom metrics, is supported.&lt;/p&gt;

&lt;h3&gt;
  
  
  In-place pod resize is now stable
&lt;/h3&gt;

&lt;p&gt;In-place pod resize lets you change CPU and memory on a running pod without recreating it. It was introduced as alpha in Kubernetes v1.27, became beta (enabled by default) in v1.33, and is &lt;strong&gt;stable since v1.35&lt;/strong&gt;. Resizes go through the pod's &lt;code&gt;resize&lt;/code&gt; subresource, which needs kubectl v1.32 or later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl patch pod api-7d9c6b5f4-x2k8p -n production --subresource resize --patch \
  '{"spec":{"containers":[{"name":"api","resources":{"requests":{"cpu":"800m"}}}]}}'

# Check progress: look for PodResizePending (Infeasible or Deferred) or PodResizeInProgress
kubectl get pod api-7d9c6b5f4-x2k8p -n production -o jsonpath='{.status.conditions}'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Per container, &lt;code&gt;resizePolicy&lt;/code&gt; decides whether a change needs a restart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;          resizePolicy:
            - resourceName: cpu
              restartPolicy: NotRequired
            - resourceName: memory
              restartPolicy: RestartContainer   # most runtimes size their heap at startup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Limits worth knowing before you build automation on it: the QoS class cannot change through a resize; decreasing a memory limit without a restart is best effort and is skipped if current usage is above the new limit; Windows pods, non-restartable init containers, and pods under the static CPU or memory manager cannot be resized in place. Also remember that resizing a pod does not change its Deployment: the next rollout brings back the template values. Use resize for emergencies and for VPA's in-place modes, and keep the source of truth in Git.&lt;/p&gt;

&lt;h2&gt;
  
  
  Requests and HPA: utilization is a ratio of the request
&lt;/h2&gt;

&lt;p&gt;An HPA with a CPU utilization target computes usage divided by request. That has two consequences that cause real incidents.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;HPA without resource requests does nothing.&lt;/strong&gt; If any container in the pod lacks a request for the metric, the utilization is undefined and the HPA takes no action for it. Either set requests on every container (sidecars included), use a &lt;code&gt;ContainerResource&lt;/code&gt; metric that targets only the application container, or switch to an &lt;code&gt;AverageValue&lt;/code&gt; target expressed in absolute cores.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Changing requests changes scaling.&lt;/strong&gt; If you cut a CPU request in half during a cost cleanup, the same traffic now reads as double the utilization, and the HPA adds replicas. Retune the HPA target in the same change as the request.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api
  minReplicas: 3
  maxReplicas: 20
  metrics:
    - type: ContainerResource
      containerResource:
        name: cpu
        container: api          # ignores sidecars with missing or odd requests
        target:
          type: Utilization
          averageUtilization: 70
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the scaling side of this, see &lt;a href="https://meteorops.com/blog/how-to-configure-kubernetes-hpa-without-causing-scaling-flaps" rel="noopener noreferrer"&gt;how to configure HPA without scaling flaps&lt;/a&gt; and &lt;a href="https://meteorops.com/blog/how-to-set-up-kubernetes-autoscaling-without-creating-cost-surprises" rel="noopener noreferrer"&gt;how to set up Kubernetes autoscaling without cost surprises&lt;/a&gt;. Node autoscalers such as Cluster Autoscaler and Karpenter also provision nodes from pending pods' requests, so inflated requests turn directly into extra nodes on the bill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Runtime settings: JVM, Go and Node.js inside resource limits
&lt;/h2&gt;

&lt;p&gt;Containers do not hide the host from the process. Each runtime decides how much heap and how many threads to use, and if it guesses from the node instead of the container, your carefully chosen limits will not hold.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Runtime&lt;/th&gt;
&lt;th&gt;What it does by default&lt;/th&gt;
&lt;th&gt;What to set&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;JVM (JDK 10+ and 8u191+)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;UseContainerSupport&lt;/code&gt; is on, so the JVM reads the container memory and CPU limits. The default max heap is only 25% of the container memory (&lt;code&gt;MaxRAMPercentage=25&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;-XX:MaxRAMPercentage=70&lt;/code&gt; to 75 for heap-heavy services, leaving the rest for metaspace, thread stacks, direct buffers and the code cache. Without a CPU limit, consider &lt;code&gt;-XX:ActiveProcessorCount&lt;/code&gt; so thread pools match the CPU request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go 1.25+&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;GOMAXPROCS&lt;/code&gt; defaults to the cgroup CPU limit when it is lower than the node's CPU count, and updates if the limit changes. CPU requests are ignored. The heap has no limit by default&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;GOMEMLIMIT&lt;/code&gt; to about 90% of the memory limit so the GC works harder before the OOM killer acts. Without a CPU limit, set &lt;code&gt;GOMAXPROCS&lt;/code&gt; from the CPU request. On Go older than 1.25, use &lt;code&gt;go.uber.org/automaxprocs&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node.js&lt;/td&gt;
&lt;td&gt;Recent versions size the default V8 heap from the container memory limit, but the fraction and the cap vary by version&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;--max-old-space-size&lt;/code&gt; explicitly (in MiB) to about 75% of the memory limit. Size &lt;code&gt;UV_THREADPOOL_SIZE&lt;/code&gt; to your CPU request if you lean on the libuv pool&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The downward API lets you feed the container's own requests and limits into these settings so they never drift from the manifest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;          env:
            - name: GOMAXPROCS
              valueFrom:
                resourceFieldRef:
                  resource: requests.cpu   # rounded up to a whole number of CPUs
                  divisor: "1"
            - name: GOMEMLIMIT
              value: "690MiB"              # about 90% of a 768Mi memory limit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A Go service on a 64-core node with no CPU limit and no &lt;code&gt;GOMAXPROCS&lt;/code&gt; will run 64 scheduler threads against a 500m request. It works until the node is busy, then latency falls apart. A JVM with no heap setting in a 4Gi container uses a 1Gi heap and gets OOMKilled only if off-heap usage grows, which makes the failure look random. Both are configuration problems, not sizing problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting: OOMKilled, CPU throttling, Pending pods and evictions
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Likely cause&lt;/th&gt;
&lt;th&gt;How to confirm&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Container restarts with &lt;code&gt;OOMKilled&lt;/code&gt;, exit code 137&lt;/td&gt;
&lt;td&gt;Memory limit below real peak, a leak, or a runtime heap larger than the limit&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;kubectl describe pod &amp;amp;lt;pod&amp;amp;gt;&lt;/code&gt; (Last State: Terminated, Reason: OOMKilled); &lt;code&gt;container_oom_events_total&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Raise memory request and limit together; set runtime heap limits; if usage climbs steadily between restarts, fix the leak&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High p99 latency, low average CPU&lt;/td&gt;
&lt;td&gt;CFS throttling from a tight CPU limit&lt;/td&gt;
&lt;td&gt;Throttled periods ratio query above; &lt;code&gt;kubectl top pod --containers&lt;/code&gt; looks fine&lt;/td&gt;
&lt;td&gt;Remove the CPU limit or raise it to 2x to 4x the request; reduce thread count to match CPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pod stuck &lt;code&gt;Pending&lt;/code&gt; with &lt;code&gt;0/12 nodes are available: Insufficient cpu&lt;/code&gt; (or memory)&lt;/td&gt;
&lt;td&gt;Sum of requests on every node leaves no room, even if real usage is low&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;kubectl describe node &amp;amp;lt;node&amp;amp;gt;&lt;/code&gt; and read "Allocated resources"&lt;/td&gt;
&lt;td&gt;Right-size inflated requests elsewhere, let the node autoscaler add capacity, or lower this pod's request if it is overestimated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pods &lt;code&gt;Evicted&lt;/code&gt; with "The node was low on resource: memory"&lt;/td&gt;
&lt;td&gt;Node memory pressure from Burstable pods using more than their requests&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;kubectl get events -A --field-selector reason=Evicted&lt;/code&gt;; node conditions show &lt;code&gt;MemoryPressure&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Set memory request = limit on the pods that grow; reserve system memory on nodes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Noisy neighbor: one service slows when another deploys or bursts&lt;/td&gt;
&lt;td&gt;Neighbors with no or tiny CPU requests using idle CPU, or BestEffort pods on the node&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;kubectl top pod -A --sort-by=cpu&lt;/code&gt;; compare usage to requests&lt;/td&gt;
&lt;td&gt;Enforce requests with a LimitRange, add CPU limits to batch workloads, isolate them on separate node pools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HPA shows &lt;code&gt;&amp;amp;lt;unknown&amp;amp;gt;&lt;/code&gt; for CPU and never scales&lt;/td&gt;
&lt;td&gt;A container in the pod has no CPU request, or metrics-server is missing&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;kubectl describe hpa &amp;amp;lt;name&amp;amp;gt;&lt;/code&gt; shows "missing request for cpu"&lt;/td&gt;
&lt;td&gt;Add requests to all containers or use a &lt;code&gt;ContainerResource&lt;/code&gt; metric&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cluster is "full" at 20% real utilization&lt;/td&gt;
&lt;td&gt;Requests set from guesses or old peaks&lt;/td&gt;
&lt;td&gt;Step 5 utilization ratio per container&lt;/td&gt;
&lt;td&gt;Apply the sizing method; run VPA in &lt;code&gt;Off&lt;/code&gt; mode to confirm&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few commands that answer most questions quickly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Requests and limits for every container in a namespace
kubectl get pods -n production -o custom-columns='POD:.metadata.name,CONTAINER:.spec.containers[*].name,CPU_REQ:.spec.containers[*].resources.requests.cpu,CPU_LIM:.spec.containers[*].resources.limits.cpu,MEM_REQ:.spec.containers[*].resources.requests.memory,MEM_LIM:.spec.containers[*].resources.limits.memory'

# QoS class of each pod
kubectl get pods -n production -o custom-columns='POD:.metadata.name,QOS:.status.qosClass'

# How much of a node is already reserved by requests
kubectl describe node &amp;amp;lt;node&amp;amp;gt; | grep -A 8 "Allocated resources"

# Live usage per container (needs metrics-server)
kubectl top pod -n production --containers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When an eviction storm is caused by a node drain rather than pressure, the playbook is different: see &lt;a href="https://meteorops.com/blog/how-to-drain-kubernetes-nodes-without-evicting-critical-workloads" rel="noopener noreferrer"&gt;how to drain Kubernetes nodes without evicting critical workloads&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kubernetes resource requests and limits checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Every container, including sidecars and init containers, has a CPU request and a memory request.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Memory limit equals memory request for nearly every workload.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CPU limits are absent or generous for latency-sensitive services, and present for batch, multi-tenant and benchmark workloads.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Requests come from at least 7 to 14 days of metrics: p95 CPU, peak memory working set plus headroom.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A dashboard or alert tracks throttled periods, OOM kills, restarts, and request-to-usage ratio per workload.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;JVM, Go and Node.js settings are aligned with the container limits (heap, &lt;code&gt;GOMAXPROCS&lt;/code&gt;, &lt;code&gt;GOMEMLIMIT&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Every production namespace has a LimitRange with sane defaults and a ResourceQuota on requests (not on &lt;code&gt;limits.cpu&lt;/code&gt; unless you want CPU limits everywhere).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;VPA runs in &lt;code&gt;Off&lt;/code&gt; mode as a second opinion, and never on the same metric as an HPA.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;HPA targets are retuned whenever CPU requests change.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Resource changes roll out gradually, memory decreases in small steps, with a PodDisruptionBudget in place.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sizing is reviewed on a schedule (monthly or quarterly), not only during incidents.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the difference between Kubernetes requests and limits?
&lt;/h3&gt;

&lt;p&gt;A request is what the scheduler reserves for the container on a node and what determines its share of CPU under contention. A limit is a runtime ceiling enforced by the kernel: CPU above the limit is throttled, memory above the limit gets the container OOMKilled. Requests decide placement, limits decide behavior at the edge.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I set CPU limits in Kubernetes?
&lt;/h3&gt;

&lt;p&gt;For latency-sensitive services on a cluster where every pod has accurate CPU requests, usually not, because CFS quotas throttle bursty and multi-threaded applications even when the node has idle CPU. Keep CPU limits for batch jobs, multi-tenant clusters, Guaranteed pods that need dedicated cores, and benchmarks. A limit at 2x to 4x the request is a reasonable compromise.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is my pod CPU throttled when CPU usage is low?
&lt;/h3&gt;

&lt;p&gt;Throttling is measured per 100 ms period, while dashboards average over much longer windows. A multi-threaded process can use its whole quota in a few milliseconds and then wait for the rest of the period. Check &lt;code&gt;container_cpu_cfs_throttled_periods_total&lt;/code&gt; against &lt;code&gt;container_cpu_cfs_periods_total&lt;/code&gt;, then raise or remove the CPU limit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should memory requests equal memory limits?
&lt;/h3&gt;

&lt;p&gt;In most cases, yes. Memory cannot be reclaimed from a running process, so a gap between request and limit is overcommit that ends in an OOM kill or a node eviction, often of a different pod. Setting them equal keeps failures local to the container that grew.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I adjust Kubernetes resources safely?
&lt;/h3&gt;

&lt;p&gt;Measure first (p95 CPU and peak memory over one to two weeks), change one workload at a time, lower memory in steps of 20% to 25% or less, retune any HPA targets in the same change, and watch restarts, OOM kills and latency for a full traffic cycle. On Kubernetes v1.33 and later you can also resize CPU on a running pod in place for urgent fixes, then commit the value to the manifest.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can HPA work without resource requests?
&lt;/h3&gt;

&lt;p&gt;Not with a utilization target: if any container in the pod lacks a request for the metric, utilization is undefined and the HPA takes no action. Use an &lt;code&gt;AverageValue&lt;/code&gt; target in absolute units, a &lt;code&gt;ContainerResource&lt;/code&gt; metric for the main container, or custom metrics instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  What does exit code 137 OOMKilled mean?
&lt;/h3&gt;

&lt;p&gt;Exit code 137 means the process received SIGKILL (128 + 9). With the reason &lt;code&gt;OOMKilled&lt;/code&gt;, the kernel killed the container because it exceeded its memory limit or the node ran out of memory. Compare the peak working set to the limit and check whether the runtime's heap setting fits inside it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is in-place pod resize production ready?
&lt;/h3&gt;

&lt;p&gt;Yes. In-place resize of CPU and memory is stable since Kubernetes v1.35 (beta and on by default since v1.33), and VPA can use it through the &lt;code&gt;InPlaceOrRecreate&lt;/code&gt; and &lt;code&gt;InPlace&lt;/code&gt; update modes. It cannot change a pod's QoS class, and memory decreases without a restart are best effort.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting resource sizing right across a whole cluster
&lt;/h2&gt;

&lt;p&gt;Requests and limits look like a per-service setting, but the payoff is cluster-wide: accurate requests let the scheduler pack nodes tightly, let autoscalers make the right call, and turn OOM kills and throttling into rare, explainable events. Start with the services that page you most or cost the most, apply the measurement method above, and put the guardrails in place so new services start from good defaults. If you want senior engineers to review your cluster's sizing, autoscaling and node pools with you, &lt;a href="https://meteorops.com/devops-setup-for-production-free-consultation" rel="noopener noreferrer"&gt;book a free production DevOps consultation&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>sre</category>
      <category>performance</category>
    </item>
    <item>
      <title>Why &amp; When to use GitOps: Use-Cases &amp; Principles</title>
      <dc:creator>Michael Zion</dc:creator>
      <pubDate>Tue, 11 Mar 2025 21:43:41 +0000</pubDate>
      <link>https://dev.to/meteorops/why-when-to-use-gitops-use-cases-principles-4e64</link>
      <guid>https://dev.to/meteorops/why-when-to-use-gitops-use-cases-principles-4e64</guid>
      <description>&lt;p&gt;&lt;strong&gt;Optimizing Kubernetes Management with GitOps and CD Tools.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We’ve come a long way from the days of deploying releases to individual machines, whether manually or by an automated process. &lt;/p&gt;

&lt;p&gt;The de-facto standard today is to deploy and manage the application stack using an orchestrator, usually &lt;a href="https://www.meteorops.com/glossary/kubernetes" rel="noopener noreferrer"&gt;Kubernetes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, &lt;a href="https://www.meteorops.com/blog/practical-tips-for-kubernetes-upgrades-for-startups" rel="noopener noreferrer"&gt;Kubernetes carries its own challenges&lt;/a&gt;. Although you can manage your stack in one centralized place, you still have to manage deployments, updates, rollbacks, and the overall infra.&lt;/p&gt;

&lt;p&gt;When Kubernetes was in its early years, services were deployed either manually or through CI/CD using &lt;code&gt;kubectl&lt;/code&gt; or Helm.&lt;/p&gt;

&lt;p&gt;There was nothing to describe the desired state and no easy way to keep track of changes or revert them. To solve these issues, &lt;a href="https://www.meteorops.com/glossary/gitops" rel="noopener noreferrer"&gt;GitOps&lt;/a&gt; was added to the mix.&lt;/p&gt;




&lt;h2&gt;
  
  
  Single Source of Truth with Increased Visibility and Operability
&lt;/h2&gt;

&lt;p&gt;The idea behind GitOps is a git repo that describes the desired state of the infrastructure and the application stack that runs on it. &lt;/p&gt;

&lt;p&gt;Combined with CI/CD, GitOps allows for fast and efficient infra provisioning and application lifecycle management. &lt;/p&gt;

&lt;p&gt;In the context of GitOps and Kubernetes, any change to the source code triggers a change in the cluster or application stack. &lt;/p&gt;

&lt;p&gt;Changes are tracked and releases are tagged with a version and can be rolled back as fast as they can be deployed. &lt;/p&gt;

&lt;p&gt;Best of all, no one is running manual commands which can be very error prone. In essence, GitOps centralises all operations related to deployments and infra management while allowing various teams to both collaborate and work independently. &lt;/p&gt;




&lt;h2&gt;
  
  
  Continuous Delivery Tools to Enhance GitOps
&lt;/h2&gt;

&lt;p&gt;However, GitOps and CI/CD for Kubernetes still lack something in terms of really being able to manage the infra and application stack.&lt;/p&gt;

&lt;p&gt;Managing deployments and infra becomes easier with GitOps thanks to declarative continuous delivery tools for Kubernetes such as &lt;strong&gt;&lt;a href="https://www.meteorops.com/glossary/argo-cd" rel="noopener noreferrer"&gt;Argo CD&lt;/a&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;a href="https://www.meteorops.com/glossary/flux-cd" rel="noopener noreferrer"&gt;Flux CD&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;These tools act as a wrapper around your codebase which translates everything declarative into actual state. Your codebase holds all of your Kubernetes manifests, Helm charts, and configuration files. &lt;strong&gt;Argo CD&lt;/strong&gt; then takes it, evaluates it, and applies it to the cluster.&lt;/p&gt;

&lt;p&gt;Such tools also offer intuitive and convenient UI to visualize and manage everything that is going on in the cluster.&lt;/p&gt;

&lt;p&gt;It’s good to be able to revert a deployment or fix a configuration issue through the UI and on the spot rather than going through the process of committing code and waiting for it to sync. This is especially true in case of an emergency or when developing and debugging.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Benefits of Combining GitOps and CD Tools
&lt;/h2&gt;

&lt;p&gt;When put into the context of everyday work of developers and &lt;a href="https://www.meteorops.com/glossary/devops" rel="noopener noreferrer"&gt;DevOps&lt;/a&gt;, the real benefits of GitOps and CD tools  come into light. What GitOps and CD tools offer is a nice package of intuitive and secure easy-to-use framework for management and collaboration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ease of Use and Visibility into the State of the Application and Cluster
&lt;/h3&gt;

&lt;p&gt;The truth is that although not too difficult, setting up a GitOps codebase with CD tools can be a complex task. However, once in place, they grant ease of use with increased security.&lt;/p&gt;

&lt;p&gt;Reading Kubernetes manifests to understand the complex relationship of services and components is not easy. It’s even harder to debug and troubleshoot issues with no centralized control dashboard.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It’s easier with CD tools because they visualize everything that the code represents.&lt;/li&gt;
&lt;li&gt;It’s easier to discern the components of a service or the various CRDs that have been added to the cluster when they appear in front of you with all their dependencies and associations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CD tools have robust control, logging, and debugging  features which allow developers and DevOps to quickly assess the status of the cluster and respond accordingly. There’s no need to run &lt;code&gt;kubectl&lt;/code&gt; commands or tail pod logs. Everything is intuitively visualized in the CD tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  Secure Operations
&lt;/h3&gt;

&lt;p&gt;Having everything visualised and controllable from outside the cluster, does away with the need to access the cluster itself. &lt;/p&gt;

&lt;p&gt;‍&lt;a href="https://www.meteorops.com/blog/from-gatekeepers-to-service-providers-reimagining-devops-relationship-with-developers#the-relationship-between-devops-and-developers" rel="noopener noreferrer"&gt;Developers and DevOps&lt;/a&gt; can be given roles through RBAC to determine which resources they have access to and what they can do in the CD tool itself rather than in the cluster. &lt;/p&gt;

&lt;p&gt;Although access is ultimately given to components of the cluster and application, it’s still better than having to manage service accounts and having dozens of developers running exec into pods. &lt;/p&gt;

&lt;p&gt;For additional security, CD tools sit inside the cluster and communicate with native API so their function takes place inside the cluster and not outside. There’s no need to open the cluster to external CI/CD pipelines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick Recovery, Consistency, and Guardrails
&lt;/h3&gt;

&lt;p&gt;Recovery is built into CD Tools. CD tools continuously monitor the state of the cluster and compare with what the code says. &lt;/p&gt;

&lt;p&gt;In case of a mismatch, the CD tool attempts to reconcile between the two. If it fails, it will retry several times before sending an alert and even pausing sync. &lt;/p&gt;

&lt;p&gt;Retries are especially important in complex systems with vast networks of dependencies between services and components. &lt;/p&gt;

&lt;p&gt;Even if the code dictates service dependencies and the order of deployment, dependencies could still fail. &lt;/p&gt;

&lt;p&gt;By retry, CD tools give all components of the system more time and more chances to go online before failing the deployment and pausing progress. &lt;/p&gt;

&lt;p&gt;Since there are no imperative commands running without a state, GitOps prevents the occurrence of hanging resources. If you remove a manifest file or parts of it, the CD tools will sync immediately, without any CI/CD pipeline, and remove the corresponding resources and components.&lt;/p&gt;

&lt;h3&gt;
  
  
  Management of Multiple Environments and Applications
&lt;/h3&gt;

&lt;p&gt;With inherent security, ease of use, and built-in recovery, GitOps and CD tools are easily expanded to support the configuration and management of multi-env, multi-app stacks. It becomes much easier to separate environments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.meteorops.com/blog/one-click-environment-the-ultimate-devops-goal#why-it-improves-the-system%E2%80%99s-recoverability" rel="noopener noreferrer"&gt;Different environments for development and production&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Regional stacks for compliance or disaster recovery.&lt;/li&gt;
&lt;li&gt;Separate applications for business units in the organization.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since the initial setup is most of what it takes to get GitOps and CD tools going, it’s incredibly easy to set up multiple stacks and manage both separately, as they are different stacks, and side-by-side in one place, as they are centrally managed. &lt;/p&gt;




&lt;h2&gt;
  
  
  The Application Needs GitOps and CD Tools
&lt;/h2&gt;

&lt;p&gt;Keeping the application stack and cluster in order and in the desired state is the main function of GitOps and CD tools. There's no guessing how the cluster should be or what should be in and who or what put it there. Everything is conveniently visualized with a vast array of actions that can be performed right from within the UI. GitOps and CD tools give developers and DevOps the ability to work independently in a secure and safe way. Secure in terms of access that is restricted to the CD tools itself. Safe in terms of the ability to quickly revert changes and the self-healing properties of CD tools. It’s not the easiest task to set up GitOps with CD tools, but the agility, convenience, productivity and security that they offer allow for well-managed operations. Well-managed operations then allow for faster and safer development pipelines to further advance your application and product.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>gitops</category>
      <category>argocd</category>
    </item>
    <item>
      <title>How to build a DevOps team</title>
      <dc:creator>Michael Zion</dc:creator>
      <pubDate>Tue, 01 Oct 2024 22:27:55 +0000</pubDate>
      <link>https://dev.to/meteorops/how-to-build-a-devops-team-99</link>
      <guid>https://dev.to/meteorops/how-to-build-a-devops-team-99</guid>
      <description>&lt;p&gt;If you're considering how to build a DevOps team the best way possible, this one's for you.&lt;br&gt;&lt;br&gt;
This blog post is the result of my advice for people building DevOps teams: CTOs, VPs of R&amp;amp;D, Team Leaders, and more.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
I'll give you the bottom line. &lt;br&gt;
These are the steps for building your DevOps team:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Polish your DevOps Philosophy&lt;/li&gt;
&lt;li&gt;Understand the DevOps Lead responsibilities: Product, Project, People, Service, Architecture&lt;/li&gt;
&lt;li&gt;Define your Team's Mission Statement&lt;/li&gt;
&lt;li&gt;Set useful DevOps Goals &amp;amp; Practices&lt;/li&gt;
&lt;li&gt;Set Guiding DevOps Principles&lt;/li&gt;
&lt;li&gt;Define the DevOps team's roadmap &amp;amp; strategy&lt;/li&gt;
&lt;li&gt;Set a definition of done&lt;/li&gt;
&lt;li&gt;Common DevOps teams pitfalls&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.meteorops.com/blog/calculating-your-companys-required-devops-capacity" rel="noopener noreferrer"&gt;Calculate your team's required capacity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Hire the right DevOps Engineers&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can also apply these to existing DevOps teams and level-up your organization. &lt;br&gt;
Let's get started!&lt;/p&gt;




&lt;h2&gt;
  
  
  Polish your DevOps Philosophy
&lt;/h2&gt;

&lt;p&gt;You must first understand why organizations need DevOps, and how it can work in practice.  &lt;/p&gt;

&lt;p&gt;DevOps is an enabler role. &lt;/p&gt;

&lt;p&gt;It's meant to &lt;strong&gt;enable the developers to build, improve, and take ownership over the system&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let's break it down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Build&lt;/strong&gt; = Create something new. Could be a new microservice, a new database, or a new monitoring dashboard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improve&lt;/strong&gt; = Introduce a change into something that exists. Could be fixing a bug, changing a database schema, or changing an alert threshold.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Take ownership&lt;/strong&gt; = Take charge of problems that arise with what you built and improved. This means when something needs improvement, the owner is the one who does it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The above are what the developers should do.&lt;/p&gt;

&lt;p&gt;Now, &lt;strong&gt;DevOps should enable it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Note, to enable &lt;strong&gt;is not&lt;/strong&gt; to give full permissions and let them have fun.&lt;/p&gt;

&lt;p&gt;To enable is to give developers what they need to build, improve, and own the system.&lt;/p&gt;

&lt;p&gt;But, do it in a way that focuses the developers' energy in the right direction, and in a safe way.&lt;/p&gt;




&lt;h2&gt;
  
  
  Understand the DevOps Lead Responsibilities
&lt;/h2&gt;

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

&lt;p&gt;You have 5 hats as a DevOps Lead:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Product (Platform)&lt;/strong&gt;&lt;br&gt;
Your clients are your company's developers.&lt;br&gt;
Provide them with tools, knowledge, and automations.&lt;br&gt;
&lt;strong&gt;Tools&lt;/strong&gt; = Polished automations.&lt;br&gt;
&lt;strong&gt;Automations&lt;/strong&gt; = Automated knowledge.&lt;br&gt;
&lt;strong&gt;Knowledge&lt;/strong&gt; = Hard-earned information &amp;amp; insights.&lt;br&gt;
Understand their requirements, and use Tools, Automations, and Knowledge to fulfill them.&lt;br&gt;
Understand why they want what they want: Is it because of an underlying issue with the system? If yes, solve it before building anything.&lt;br&gt;
If a tool or automation will save time, consider building or implementing it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Service&lt;/strong&gt;&lt;br&gt;
Don't let your developers wait until you do something "the right way".&lt;br&gt;
Sometimes they need immediate help to complete something.&lt;br&gt;
Help them first, and invest time later in automation and tooling.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Project&lt;/strong&gt;&lt;br&gt;
Managing your DevOps team requires managing the work it does.&lt;br&gt;
Turn the team's philosophy into a mission.&lt;br&gt;
Turn the team's mission into goals.&lt;br&gt;
Turn the team's goals into a roadmap.&lt;br&gt;
Turn the team's roadmap into tasks.&lt;br&gt;
Prioritize the tasks.&lt;br&gt;
Set simple roles and responsibilities.&lt;br&gt;
Hold your team members accountable to progression.&lt;br&gt;
Make it easy to inform relevant team members on updates.&lt;br&gt;
Make it easy to consult with a teammate on a subject of their expertise.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;People&lt;/strong&gt;&lt;br&gt;
Each DevOps team member has type of work and tasks they enjoy more.&lt;br&gt;
One enjoys sharing knowledge, another enjoys being of service, and some enjoy building tools.&lt;br&gt;
Different teammates are also interested in different subjects: Monitoring, Infrastructure, CI/CD, etc.&lt;br&gt;
A team member that's working on what they enjoy is more fulfilled and productive.&lt;br&gt;
Strive to overlap each teammate's goals with the company's goals.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Architecture&lt;/strong&gt;&lt;br&gt;
You have 2 architectures to worry about.&lt;br&gt;
1) The company's product architecture (built by the developers).&lt;br&gt;
2) The DevOps platform's architecture (built by the DevOps team).&lt;br&gt;
Help the developers understand and control the application's effect on the infrastructure.&lt;br&gt;
Enable the developers to make informed decisions by providing context.&lt;br&gt;
Finally, build the platform to support the developers requirements.&lt;br&gt;
At every step, only limit decisions that damage the company.&lt;br&gt;
Examples: High cost, Stability impairment, Restricts observability, etc.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Define Your DevOps Team's Mission Statement
&lt;/h2&gt;

&lt;p&gt;Here's one for you:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Enable the developers to build, improve, and own the system".&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's pretty minimal, so it's going to help your team stay focused.&lt;/p&gt;

&lt;p&gt;A healthy sign it catches on: When the team debates a decision regarding a task, they ask &lt;em&gt;"Does it help us enable the developers to build, improve, or own the system?"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That's when you win.&lt;/p&gt;




&lt;h2&gt;
  
  
  Set Useful DevOps Goals &amp;amp; Practices
&lt;/h2&gt;

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

&lt;p&gt;Your company's success will be determined by its speed and its product's quality.&lt;/p&gt;

&lt;p&gt;To make it happen, you should know programming is science, not maths - I'll explain.&lt;/p&gt;

&lt;p&gt;People used to think programming will be a mathematical discipline.&lt;br&gt;
They thought programmers will write functions and mathematically prove them.&lt;/p&gt;

&lt;p&gt;Not what happened - Programming is a scientific discipline.&lt;/p&gt;

&lt;p&gt;You write code, your test it, and you assume it's good - until one test fails.&lt;/p&gt;

&lt;p&gt;In essence, you're experimenting.&lt;/p&gt;

&lt;p&gt;You might ask: &lt;em&gt;"wtf? how's this related to setting useful goals?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The answer is that the first thing you want to enable is running experiments easily.&lt;/p&gt;

&lt;h3&gt;
  
  
  So here are some useful goals:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Developers can easily test their code in a consistent manner&lt;/li&gt;
&lt;li&gt;Production and testing environments are identical (Production will benefit from the quality of the tests)&lt;/li&gt;
&lt;li&gt;Developers can easily collaborate&lt;/li&gt;
&lt;li&gt;Developers can understand the state of the system and the impact of changes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Let's translate those goals into smaller goals or practices that will achieve the goals:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Developers can &lt;a href="https://www.meteorops.com/blog/one-click-environment-the-ultimate-devops-goal" rel="noopener noreferrer"&gt;create a testing/production environment with "One-Click"&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;There's a continuous integration process that enables the developers to collaborate by agreeing on the current up-to-date version of the system&lt;/li&gt;
&lt;li&gt;Auto-create dashboards and alerts for new services&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Set Guiding DevOps Principles
&lt;/h2&gt;

&lt;p&gt;Some useful DevOps principles that help save your team time, improve the speed of delivery, and keep the system healthy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;PoC before doing things "the right way"&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Make it work, then make it better&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;The entire system should be fully recoverable from Git&lt;/li&gt;
&lt;li&gt;Use tools with a big community and well-documented interface&lt;/li&gt;
&lt;li&gt;Equip key-developers with DevOps knowledge to be the first point-of-contact for their team members (super users)&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Define the DevOps Team's Roadmap &amp;amp; Strategy
&lt;/h2&gt;

&lt;p&gt;Roadmap = Goals * Strategy.&lt;br&gt;&lt;br&gt;
Once you set the goals (as mentioned above), you can start prioritizing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;♯1 - The DevOps Categories&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Enabling developers requires a DevOps Engineer to handle and enable the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Provision infrastructure&lt;/li&gt;
&lt;li&gt;Deploy workloads&lt;/li&gt;
&lt;li&gt;Monitor the system&lt;/li&gt;
&lt;li&gt;Recover from issues&lt;/li&gt;
&lt;li&gt;Scale up and down&lt;/li&gt;
&lt;li&gt;Track &amp;amp; test changes (Codebase Management)&lt;/li&gt;
&lt;li&gt;Secure the system&lt;/li&gt;
&lt;li&gt;Store &amp;amp; retrieve data&lt;/li&gt;
&lt;li&gt;Configure the system&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;♯2 - Examine each goal through each category&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every DevOps goal you set should be examined through the lens of each category.&lt;/p&gt;

&lt;p&gt;The reason is that together the categories cover each aspect of the building, improving, and owning of a software operation.&lt;/p&gt;

&lt;p&gt;Let's do an example:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Goal: Create a "One-Click Environment Automation"&lt;/li&gt;
&lt;li&gt;Categories to address:

&lt;ul&gt;
&lt;li&gt;How should its infrastructure be provisioned?&lt;/li&gt;
&lt;li&gt;How should its workloads be deployed?&lt;/li&gt;
&lt;li&gt;How should its metrics and logs be sent, stored, and queried?&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;♯3 - Strategic Principles&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reach at least 50% capacity of working on the DevOps goals as soon as you responsibly can -
Support the developers and teach them how to self-support to achieve that&lt;/li&gt;
&lt;li&gt;Easy to modify &amp;gt; Perfect -
When you do something that isn't perfect due to a lack of time, do it in such a way that modifying it later on to improve it is easy&lt;/li&gt;
&lt;li&gt;Prerequisites first:
Codebase Management -&amp;gt; Infrastructure -&amp;gt; Deployment -&amp;gt; Configuration -&amp;gt; Data Management.&lt;/li&gt;
&lt;li&gt;At least moderate foundations quickly, reinforce later, but never weak foundations:
If you don't use any boilerplates, or if you are not proficient in something early on, and it's a foundation (like infrastructure), then don't give up and build weak foundations, but also don't over-invest and build strong ones if it's too time consuming.
Instead, moderately invest in the foundation, and revisit it later.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Set a Definition of Done
&lt;/h2&gt;

&lt;p&gt;Also known as a Definition of done.&lt;/p&gt;

&lt;p&gt;Ask the following questions for every single component in your system:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;em&gt;&lt;strong&gt;Monitoring&lt;/strong&gt;&lt;/em&gt;: Are there metrics, logs, traces, and alerts setup in an actionable way?&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;&lt;strong&gt;Availability&lt;/strong&gt;&lt;/em&gt;: Is there a mechanism to keep it alive during incidents?&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;&lt;strong&gt;Resiliency&lt;/strong&gt;&lt;/em&gt;: Can it recover from an error quickly?&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;&lt;strong&gt;Recovery&lt;/strong&gt;&lt;/em&gt;: Can it be fully restored to a previous state?&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;&lt;strong&gt;Testability&lt;/strong&gt;&lt;/em&gt;: Is it possible to test changes to it?&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;&lt;strong&gt;Deliverability&lt;/strong&gt;&lt;/em&gt;: Is there a process to release changes to it?&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;&lt;strong&gt;Persistency&lt;/strong&gt;&lt;/em&gt;: Will its data persist if the system is hindered?&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;&lt;strong&gt;Integrable&lt;/strong&gt;&lt;/em&gt;: Does it have a consistent and predictable interface allowing integration with it?&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;&lt;strong&gt;Security&lt;/strong&gt;&lt;/em&gt;: Is it accessible only by the parts of the system that absolutely need it?&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;&lt;strong&gt;Dependencies&lt;/strong&gt;&lt;/em&gt;: Are its dependencies fully tracked and managed?&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Common DevOps Team Pitfalls
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Pitfall&lt;/strong&gt;&lt;/em&gt;: DevOps work blocks developers work&lt;br&gt;
&lt;em&gt;&lt;strong&gt;Indicator&lt;/strong&gt;&lt;/em&gt;: Developers need to wait for DevOps team changes to complete before continuing work&lt;br&gt;
&lt;em&gt;&lt;strong&gt;Cause&lt;/strong&gt;&lt;/em&gt;: The DevOps team doesn't utilize its own practices (AKA shoemaker's son always goes barefoot)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Pitfall&lt;/strong&gt;&lt;/em&gt;: Only support developers and maintain the system&lt;br&gt;
&lt;em&gt;&lt;strong&gt;Indicator&lt;/strong&gt;&lt;/em&gt;: No progress on any DevOps goal or task&lt;br&gt;
&lt;em&gt;&lt;strong&gt;Cause&lt;/strong&gt;&lt;/em&gt;: Either there are no clear DevOps goals or repeating developers requests haven't been automated&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Pitfall&lt;/strong&gt;&lt;/em&gt;: Adopting 'Best' practices instead of 'Suitable' practices&lt;br&gt;
&lt;em&gt;&lt;strong&gt;Indicator&lt;/strong&gt;&lt;/em&gt;: Introducing methodologies and adhering to principles that go against the company's goals&lt;br&gt;
&lt;em&gt;&lt;strong&gt;Cause&lt;/strong&gt;&lt;/em&gt;: Prioritizing methodology over company goals, usually because of a disconnect from the company goals or due to lack of DevOps experience&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Calculate Your DevOps Team's Required Capacity
&lt;/h2&gt;

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

&lt;p&gt;Required DevOps Capacity = &lt;a href="https://www.meteorops.com/blog/calculating-your-companys-required-devops-capacity" rel="noopener noreferrer"&gt;(Scale * Complexity) / Leverage&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Leverage = Level of DevOps Engineers * Company Resources * Team Focus&lt;/li&gt;
&lt;li&gt;Scale = Number of instances of each component * Number of people in the organization&lt;/li&gt;
&lt;li&gt;Complexity = Number of components * Number of teams&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Hire the Right DevOps Engineers
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsk1skmru0gl08c1sy7a6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsk1skmru0gl08c1sy7a6.png" alt="devops engineers feedback" width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The types of DevOps Engineers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Barrels vs. Ammo&lt;/strong&gt;&lt;br&gt;
~ Ammo = People who can complete tasks but won't initiate them.&lt;br&gt;
~ Barrel = People who understand what tasks are needed next but won't complete them.&lt;br&gt;
Interviewer Tip: During the technical interview with a candidate ask about past DevOps accomplishments, and ask how the tasks were created and who did them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Aspiration-Oriented vs. Prevention-Oriented&lt;/strong&gt;&lt;br&gt;
~ Aspiration-Oriented = Has goals, positive feedback encourages and focuses them while negative feedback discourages them and kicks them off track.&lt;br&gt;
~ Prevention-Oriented = Avoids problems, positive feedback makes them lay back and reduce capacity while negative feedback focuses them and keeps them on track.&lt;br&gt;
Interviewer tip: See if there's a common theme for projects the candidate did in the past. People focused on security tasks and deep attention to specific details are more likely to be prevention-oriented, while people who initiated many projects spanning multiple (DevOps) categories are more likely to be aspiration-oriented.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://www.meteorops.com/blog/devops-agency-vs-devops-consultancy-vs-devops-services-company" rel="noopener noreferrer"&gt;Working with service providers&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Set clear desired results&lt;/strong&gt;&lt;br&gt;
And let the DevOps service provider assist in exploring the goal and provide perspective from other companies.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Expect transparency on progress&lt;/strong&gt;&lt;br&gt;
And judge it against the latest plan.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Expect clear planning&lt;/strong&gt;&lt;br&gt;
And make sure it has clear goals, takes into consideration the risks, and has a strategy that adheres to your DevOps principles.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Lots of stuff covered over this one-pager, and still much was left outside.&lt;/p&gt;

&lt;p&gt;If you take away 1 thing from it, let it be this: A simple DevOps team mission statement is the most significant thing you need.&lt;/p&gt;

&lt;p&gt;It sounds over-simplistic and abstract, but without it there is not guiding principle for how DevOps in the organization should look like.&lt;/p&gt;

&lt;p&gt;Hope you enjoyed, and send me an email at &lt;a href="mailto:michael@meteorops.com"&gt;michael@meteorops.com&lt;/a&gt; if there's anything else you'd like to see in here.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>teamwork</category>
      <category>infrastructureascode</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Don't waste 6 months on hiring DevOps, take the 10 Hours DevOps Pill</title>
      <dc:creator>Michael Zion</dc:creator>
      <pubDate>Thu, 04 Jan 2024 14:55:06 +0000</pubDate>
      <link>https://dev.to/meteorops/dont-waste-6-months-on-hiring-devops-take-the-10-hours-devops-pill-40f9</link>
      <guid>https://dev.to/meteorops/dont-waste-6-months-on-hiring-devops-take-the-10-hours-devops-pill-40f9</guid>
      <description>&lt;p&gt;A practical playbook for founders and engineering managers to stop wasting time trying to get started with DevOps the right way&lt;/p&gt;




&lt;h2&gt;
  
  
  Is this article for you?
&lt;/h2&gt;

&lt;p&gt;Maybe you're a CTO.&lt;br&gt;
Maybe you're an engineering manager.&lt;br&gt;
If you think about hiring you first DevOps Engineer - this article is for you.&lt;br&gt;
It will save you 6 months of wasted mental capacity.&lt;/p&gt;

&lt;p&gt;How? By dedicating 10 hours to planning.&lt;br&gt;
I'll share with you here exactly what to do during those 10 hours.  &lt;/p&gt;

&lt;h2&gt;
  
  
  You might know this story TOO well
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ~ "I need to focus on the product"
&lt;/h3&gt;

&lt;p&gt;You are building your company’s product.&lt;br&gt;
You start thinking about the development process and building or improving production.&lt;br&gt;
Then, you read about DevOps somewhere and think to yourself: “This seems like what I need”.&lt;/p&gt;

&lt;h3&gt;
  
  
  ~ "DevOps is expensive"
&lt;/h3&gt;

&lt;p&gt;You start looking for a DevOps Engineer, but you find out the standard DevOps salary - “Oh, that’s pretty expensive for our budget”.&lt;br&gt;
So you decide to wait. “Hiring a developer is more important right now” you tell yourself.  &lt;/p&gt;

&lt;p&gt;A month goes by, and you and your team are grinding away, building a kickass product.&lt;br&gt;
But alas! The missing development automation hurts collaboration, the cloud infrastructure is a collection of manual changes - and you’re not sure if the system is built well.&lt;/p&gt;

&lt;p&gt;So, you make a decision, and you post your first “DevOps Position”, congrats!&lt;/p&gt;

&lt;h3&gt;
  
  
  ~ "I'll interview some candidates"
&lt;/h3&gt;

&lt;p&gt;Candidates start streaming in, and you start interviewing.&lt;br&gt;
After interviewing the 3rd candidate, you think to yourself “this is not it”.&lt;br&gt;
Every candidate is missing a different piece of the required knowledge.&lt;br&gt;
One doesn’t know AWS, the other isn’t familiar with "", and the last one only worked with on-prem before.&lt;/p&gt;

&lt;p&gt;And so, you re-evaluate.&lt;/p&gt;

&lt;h3&gt;
  
  
  ~ "I'll do it myself"
&lt;/h3&gt;

&lt;p&gt;The realization is this: You can’t hire on knowledge for this position, you have to hire on skill and character.&lt;br&gt;
BUT, you feel like the knowledge is crucial, at least at this point.&lt;br&gt;
So you make another decision - I’ll invest some more time in this “DevOps” thing - I’ll learn.&lt;br&gt;
You watch DevOps tutorials on Youtube.&lt;br&gt;
You even subscribe to various DevOps Newsletters, something you didn’t do before.&lt;br&gt;
Another month goes by.&lt;/p&gt;

&lt;h3&gt;
  
  
  ~ "What do other startups do?"
&lt;/h3&gt;

&lt;p&gt;After learning and implementing DevOps bits and pieces all over the place, it doesn’t feel right.&lt;br&gt;
“Am I re-inventing the wheel and complicating the future?” you might ask yourself.&lt;br&gt;
You take a step back, zoom-out, and ask other startup founders what they did at this stage.&lt;/p&gt;

&lt;p&gt;That’s when you find out you’re not alone - “Oh! So it’s tough for other founders as well!”&lt;br&gt;
But, each founder tells you something different.&lt;br&gt;
One founder tells you to hire a freelancer - but that seems like just another hiring model and doesn’t solve the problem.&lt;br&gt;
Another founder tells you to just hire a full-time DevOps Engineer - but your resources are limited, and you’d rather hire more developers to build the product.&lt;br&gt;
Until one founder tells you to hire an agency: “Instead of a freelancer, get a company that’s built to get your DevOps right”.&lt;/p&gt;

&lt;h3&gt;
  
  
  ~ "Maybe an agency?"
&lt;/h3&gt;

&lt;p&gt;You’re convinced, and you start looking for agencies.&lt;br&gt;
You get recommendations, you search online, and you start booking calls.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;1st call with a DevOps agency&lt;br&gt;
*&lt;/em&gt;“Hi there! Yes we’re the solution for you! Let’s get started!”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2nd call with a DevOps agency&lt;/strong&gt;&lt;br&gt;
“Yes! We’ll provide you with a strong DevOps Engineer! Let’s get started!”&lt;/p&gt;

&lt;h3&gt;
  
  
  ~ "I'll do it myself"
&lt;/h3&gt;

&lt;p&gt;You choose not to go on the 3rd call and you pause the process.&lt;/p&gt;

&lt;p&gt;Why? Because you realize: “These guys didn’t remove any uncertainty for me - how are they different from a freelancer?”&lt;br&gt;
So you make another decision.&lt;br&gt;
You’ll continue to do DevOps yourself, and maybe delegate some tasks to other team members.&lt;/p&gt;

&lt;p&gt;That’s when you find out it was a trap! You got yourself into a loop!&lt;br&gt;
‍&lt;/p&gt;

&lt;h3&gt;
  
  
  ~ "Oh no! it's a loop!"
&lt;/h3&gt;

&lt;p&gt;After just one month you realize it’s not sustainable, and you repeat the same process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open DevOps position&lt;/li&gt;
&lt;li&gt;Realize each candidate is missing something else&lt;/li&gt;
&lt;li&gt;Fear for the budget&lt;/li&gt;
&lt;li&gt;Stop the hiring&lt;/li&gt;
&lt;li&gt;Complete DevOps tasks yourself&lt;/li&gt;
&lt;li&gt;Consider a freelancer&lt;/li&gt;
&lt;li&gt;Drop the idea because it's similar hiring&lt;/li&gt;
&lt;li&gt;Consider an agency&lt;/li&gt;
&lt;li&gt;Interview a bunch of agencies&lt;/li&gt;
&lt;li&gt;No extra certainty or confidence from an agency&lt;/li&gt;
&lt;li&gt;Do DevOps work yourself&lt;/li&gt;
&lt;li&gt;Repeat&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This loop is sometimes 2 months long, and sometimes 12 months long.&lt;/p&gt;

&lt;p&gt;Either way, it slows down your company!&lt;br&gt;
It occupies your thoughts.&lt;br&gt;
It takes valuable time from you.&lt;br&gt;
It speeds up global warming.&lt;/p&gt;

&lt;p&gt;Maybe the global warming part was an exaggeration.&lt;/p&gt;

&lt;p&gt;It’s amazing how many founders went through this exact same process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Break the loop
&lt;/h2&gt;

&lt;p&gt;I’m here to tell you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;You can break the DevOps hiring loop&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You can promote DevOps efforts quickly AND accurately&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You can save 6-12 months of stumbling around&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  You need just 10 hours with a DevOps Expert
&lt;/h2&gt;

&lt;p&gt;You need just 10 hours with a DevOps expert.&lt;br&gt;
What you need most is perspective from an experienced individual.&lt;br&gt;
You don’t yet have the DevOps perspective or experience? No problem.&lt;br&gt;
Get a DevOps Expert to consult you on a hourly basis.&lt;br&gt;
I call it "The 10 Hours DevOps Pill" 💊&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements for the DevOps expert
&lt;/h2&gt;

&lt;p&gt;Not anyone will fit as your trusted DevOps expert.&lt;/p&gt;

&lt;p&gt;Use someone that answers these criteria:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Helped 10+ companies with DevOps -
Has a wide perspective&lt;/li&gt;
&lt;li&gt;Consulted CTOs and VPs of R&amp;amp;D before -
Understands the weight of culture in adopting DevOps&lt;/li&gt;
&lt;li&gt;Consulted DevOps Group and Team Leaders before -
Understands how to manage the work&lt;/li&gt;
&lt;li&gt;Did lots of DevOps hands-on work before -
Understands how to do the work&lt;/li&gt;
&lt;li&gt;You understand them -
They know where you stand and have simple explanations. (Run away from the buzzwords throwers)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Spoiler: The contents of the 10 hours
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;2h - Share everything about your startup&lt;/li&gt;
&lt;li&gt;2h - Ask what’s possible + What others did&lt;/li&gt;
&lt;li&gt;2h - Set DevOps goals&lt;/li&gt;
&lt;li&gt;2h - Choose a strategy&lt;/li&gt;
&lt;li&gt;2h - Create a DevOps roadmap
Let’s dive into the playbook for each meeting!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1st + 2nd Consultation Hours: Share, share, share
&lt;/h2&gt;

&lt;p&gt;Share the state of your company with the DevOps expert.&lt;br&gt;
Share your infrastructure, your code, your work process, everything!&lt;br&gt;
Don’t be shy, and don’t justify - simply explain your startup’s goals and your reasoning.&lt;br&gt;
(Sign an NDA first, of course)&lt;/p&gt;

&lt;h2&gt;
  
  
  3rd + 4th Consultation Hours: Understand what’s possible + Gain perspective
&lt;/h2&gt;

&lt;p&gt;Take notes or record this call!&lt;/p&gt;

&lt;p&gt;Ask the DevOps expert: &lt;strong&gt;What’s possible?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How fast can changes reach production?&lt;/li&gt;
&lt;li&gt;How fast can you create new environments?&lt;/li&gt;
&lt;li&gt;How easily can your developers collaborate?&lt;/li&gt;
&lt;li&gt;How confidently can you change the system?&lt;/li&gt;
&lt;li&gt;What uptime can the system have?&lt;/li&gt;
&lt;li&gt;How aware can your team be of the system’s state?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ask the DevOps expert: &lt;strong&gt;What did other startups do at this stage?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What goals did they have?&lt;/li&gt;
&lt;li&gt;What DevOps efforts did they promote?&lt;/li&gt;
&lt;li&gt;Why did they decide those DevOps efforts?&lt;/li&gt;
&lt;li&gt;Which startups performed best? Why?&lt;/li&gt;
&lt;li&gt;Are there industry-unique considerations?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5th + 6th Consultation Hours: Set DevOps goals
&lt;/h2&gt;

&lt;p&gt;You now know what’s possible, and what other companies do.&lt;br&gt;
Time to set short-term, mid-term, and long term goals.&lt;br&gt;
If you want to dive deep into useful DevOps principles, check out &lt;a href="https://www.meteorops.com/blog/the-cto-devops-handbook-simple-principles-and-examples"&gt;the CTO DevOps Handbook here 👈🏼&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  7th + 8th Consultation Hours: Strategize
&lt;/h2&gt;

&lt;p&gt;Based on your goals, strengths, and restrictions, choose a strategy.&lt;br&gt;
Discuss the following options with your DevOps expert.&lt;/p&gt;

&lt;h3&gt;
  
  
  Possible Strengths
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Developers with DevOps background&lt;/li&gt;
&lt;li&gt;High budget&lt;/li&gt;
&lt;li&gt;Available time to manage the DevOps efforts&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Possible Restrictions
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;No budget&lt;/li&gt;
&lt;li&gt;Low budget&lt;/li&gt;
&lt;li&gt;DevOps needs are temporary for now&lt;/li&gt;
&lt;li&gt;No time to manage the DevOps efforts&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;DevOps Engineer&lt;/th&gt;
&lt;th&gt;Freelancer&lt;/th&gt;
&lt;th&gt;Agency&lt;/th&gt;
&lt;th&gt;Winner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Budget&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Freelancer / Agency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Management Effort Required&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Low-Medium&lt;/td&gt;
&lt;td&gt;Agency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hourly Flexibility&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Agency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ease of bridging knowledge gaps&lt;/td&gt;
&lt;td&gt;Low-Medium&lt;/td&gt;
&lt;td&gt;Low-Medium&lt;/td&gt;
&lt;td&gt;Medium-High&lt;/td&gt;
&lt;td&gt;Agency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ease of building trust&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Medium-High&lt;/td&gt;
&lt;td&gt;Low-Medium&lt;/td&gt;
&lt;td&gt;DevOps Engineer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The available strategies
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Hire a DevOps Engineer, Boost with the DevOps expert&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a DevOps roadmap with the DevOps Expert before hiring&lt;/li&gt;
&lt;li&gt;Get the DevOps expert to interview the candidates&lt;/li&gt;
&lt;li&gt;Get advice from the DevOps expert on what to examine during your interview with the candidates&lt;/li&gt;
&lt;li&gt;Prioritize the DevOps efforts with the DevOps Expert&lt;/li&gt;
&lt;li&gt;Plan the onboarding of the DevOps Engineer with the 6. DevOps expert&lt;/li&gt;
&lt;li&gt;Hire the DevOps Engineer&lt;/li&gt;
&lt;li&gt;Get advice on managing DevOps projects from the DevOps expert&lt;/li&gt;
&lt;li&gt;Discuss the DevOps tasks implementation with the DevOps Engineer and DevOps expert&lt;/li&gt;
&lt;li&gt;Occasionally or routinely consult with the DevOps expert on priorities, management, and engineering&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Hire a DevOps freelancer, Boost with the DevOps expert&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Exactly as the above strategy&lt;/li&gt;
&lt;li&gt;Take advantage of the hourly flexibility when needed&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Hire a DevOps agency, Boost with the DevOps expert&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a DevOps roadmap with the DevOps Expert before choosing an agency&lt;/li&gt;
&lt;li&gt;Share your requirements with the agency&lt;/li&gt;
&lt;li&gt;Filter out any agency that doesn’t provide you with a plan BEFORE work starts&lt;/li&gt;
&lt;li&gt;Get the plan reviewed by your DevOps expert: Does it make sense? Is the planned result desired?&lt;/li&gt;
&lt;li&gt;Make sure the DevOps agency’s ability to provide support after the work is done&lt;/li&gt;
&lt;li&gt;Asses the level of the DevOps agency’s DevOps Engineers with the help of the DevOps expert&lt;/li&gt;
&lt;li&gt;Make sure the DevOps agency is flexible with its hourly capacity&lt;/li&gt;
&lt;li&gt;Occasionally or routinely consult with the DevOps expert on priorities, management, and engineering&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Advantages and disadvantages of the strategies
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Good when&lt;/th&gt;
&lt;th&gt;Bad when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Full-time + Expert&lt;/td&gt;
&lt;td&gt;- Your budget is high - You need flexible DevOps capacity - Your team has DevOps background&lt;/td&gt;
&lt;td&gt;- Your budget is low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Freelancer + Expert&lt;/td&gt;
&lt;td&gt;- Your budget is low - You need flexible DevOps capacity - You can't risk losing DevOps capacity short-mid-term&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agency + Expert&lt;/td&gt;
&lt;td&gt;- Your budget is low - You need flexible DevOps capacity - You want an accurate plan before work starts - You frequently need to support new tools and technologies&lt;/td&gt;
&lt;td&gt;- The agency doesn’t have flexible plans - The agency’s team isn’t experienced - The agency doesn’t create clarity before work starts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  9th + 10th Consultation Hours: Create a DevOps roadmap + Execute your strategy
&lt;/h2&gt;

&lt;p&gt;You’re ready to create a DevOps roadmap and start executing your strategy!&lt;br&gt;
Creating a DevOps roadmap is the first step of each of the strategies described above.&lt;/p&gt;

&lt;h3&gt;
  
  
  You had to do a bunch of things first:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;You needed a DevOps expert to help you break the vicious DevOps loop - &lt;strong&gt;Done&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;You needed to set DevOps goals rooted in a deep DevOps understanding and perspective - &lt;strong&gt;Done&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;You needed to get familiar with different strategies and choose the best one for you - &lt;strong&gt;Done!&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  You’re left with 2 things to do:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1 - Create the DevOps roadmap based on the goals and strategy you’ve set&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;2 - Start executing the strategy:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Share the DevOps goals with the team&lt;/li&gt;
&lt;li&gt;Schedule the interviews with the Engineers/Freelancers/Agencies&lt;/li&gt;
&lt;li&gt;Use the DevOps expert to stay on track&lt;/li&gt;
&lt;li&gt;Set the milestones for the DevOps goals&lt;/li&gt;
&lt;li&gt;Create the tasks&lt;/li&gt;
&lt;li&gt;Complete the tasks&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Prepare before starting the process
&lt;/h2&gt;

&lt;p&gt;You want to do two things before jumping into the 10 hours:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Before the first 2 hours&lt;/strong&gt;:
Perfect your &lt;a href="https://www.meteorops.com/blog/the-cto-devops-handbook-simple-principles-and-examples#devops-dictionary"&gt;DevOps terminology&lt;/a&gt; (will support rich discussions)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Before the last 2 hours&lt;/strong&gt;:
List existing efforts that might slow down the DevOps roadmap (will help prioritizing)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Ask me anything
&lt;/h2&gt;

&lt;p&gt;If you’re thinking about taking this “10-hours DevOps Pill”, feel free to &lt;a href="https://www.meteorops.com/10-hours-devops-pill"&gt;reach out here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do it yourself
&lt;/h2&gt;

&lt;p&gt;You now know the general process for breaking the DevOps loop.&lt;br&gt;
You don’t NEED a DevOps expert.&lt;br&gt;
Feel free to subscribe to my Newsletter and &lt;a href="https://meteorops.beehiiv.com/subscribe"&gt;learn how to do it yourself here 👈🏼&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>cto</category>
      <category>webdev</category>
      <category>startup</category>
    </item>
    <item>
      <title>The CTO DevOps Handbook: Simple Principles and Examples</title>
      <dc:creator>Michael Zion</dc:creator>
      <pubDate>Mon, 25 Dec 2023 21:28:13 +0000</pubDate>
      <link>https://dev.to/meteorops/the-cto-devops-handbook-simple-principles-and-examples-2jcb</link>
      <guid>https://dev.to/meteorops/the-cto-devops-handbook-simple-principles-and-examples-2jcb</guid>
      <description>&lt;p&gt;Nail the DevOps part as your company's CTO&lt;/p&gt;




&lt;p&gt;The goal of this handbook is to give you clarity on DevOps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Understand what’s DevOps (in simple words)&lt;/li&gt;
&lt;li&gt;Know what’s possible with DevOps (in simple goals)&lt;/li&gt;
&lt;li&gt;Get simple “when-to-do-what” DevOps guidelines
‍&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I added a bonus at the bottom of the article.&lt;br&gt;
It's a production-ready setup example you could take inspiration from.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who this article is for
&lt;/h2&gt;

&lt;p&gt;You might be a founder who wishes to get started with DevOps the right way.&lt;/p&gt;

&lt;p&gt;You might be a CTO of a 1,000 employees company who wishes to get simple principles.&lt;/p&gt;

&lt;p&gt;Or, maybe you’re a Software Engineer, and you want to understand if your company’s DevOps approach is good.&lt;/p&gt;

&lt;p&gt;If you’re looking for a simple DevOps playbook, this is it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understand the desired result
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Two things your company needs to be able to do
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Serve its product to customers&lt;/li&gt;
&lt;li&gt;Build and improve the product&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Abilities you need to build, improve, and serve software
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Run experiments and test changes&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  DevOps has a simple meaning
&lt;/h2&gt;

&lt;p&gt;Developers and Operators have shared responsibility for building and improving the system.&lt;/p&gt;

&lt;p&gt;In practice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Developers are responsible to “Operate”&lt;/li&gt;
&lt;li&gt;DevOps Engineers are responsible to enable to “Operate” AND do some of it themselves&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Operate&lt;/strong&gt; = provision, monitor, secure, configure, deploy, scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose a balance: Enabler, Doer, or Automator
&lt;/h2&gt;

&lt;p&gt;The DevOps role will end up as a balance between:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enabler: Provides the tools and knowledge to fulfill the DevOps goals&lt;/li&gt;
&lt;li&gt;Doer: Does the tasks that fulfill the DevOps goals&lt;/li&gt;
&lt;li&gt;Automator: Automates any repeating operation&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Know what things you should enable, do, or automate
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Provision infrastructure&lt;/li&gt;
&lt;li&gt;Secure the system&lt;/li&gt;
&lt;li&gt;Deploy workloads&lt;/li&gt;
&lt;li&gt;Monitor the system&lt;/li&gt;
&lt;li&gt;Recover from issues&lt;/li&gt;
&lt;li&gt;Scale up or down&lt;/li&gt;
&lt;li&gt;Track &amp;amp; test changes&lt;/li&gt;
&lt;li&gt;Automate processes&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Choose the right tools
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Has state management = Saves time automating state-aware processes (e.g., Terraform)&lt;/li&gt;
&lt;li&gt;Has a big community &amp;amp; good docs = Saves time dealing with common issues (e.g., Kubernetes)&lt;/li&gt;
&lt;li&gt;Has multiple interface types: API, CLI, UI = Saves time integrating with the existing system (e.g., Vault)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also read about choosing tools &lt;a href="https://www.meteorops.com/blog/one-click-environment-the-ultimate-devops-goal#tools-choosing-principles"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set useful goals
&lt;/h2&gt;

&lt;p&gt;There are DevOps goals that adopting them will focus you on the right direction:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.meteorops.com/blog/one-click-environment-the-ultimate-devops-goal"&gt;One-Click Environments&lt;/a&gt;&lt;/strong&gt;: makes e2e tests easy and quick&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Atomic Commits&lt;/strong&gt;: provides confidence that a tested change will work in production&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separate the Shared &amp;amp; Env-Specific Parts&lt;/strong&gt;: enables e2e tests as the company scales up&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you want to learn about more useful DevOps goals, feel free to book a free consultation &lt;a href="https://www.meteorops.com/personal-devops-consultation"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enablers: Choose the Tools-to-Knowledge Balance
&lt;/h2&gt;

&lt;p&gt;Developers can either have the knowledge or the tools to do something.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;More knowledge-reliance&lt;/strong&gt;: if you want the developers to contribute to the DevOps efforts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More tools-reliance&lt;/strong&gt;: if you want to abstract the operations from the developers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the balance between the two is not intentional, it’s accidental.&lt;/p&gt;

&lt;h2&gt;
  
  
  Doers: Have a good reason to do it
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Is it a one-time task?&lt;/li&gt;
&lt;li&gt;Does it teach you how the developers work?&lt;/li&gt;
&lt;li&gt;Are you directly accountable for the results of the task?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you answered “no” to the above questions, enable or automate it instead.&lt;/p&gt;

&lt;p&gt;Doing more = Learning the system's use-cases&lt;/p&gt;

&lt;p&gt;Doing too much = Not scalable, too-much knowledge-reliance&lt;/p&gt;

&lt;h2&gt;
  
  
  Automators: Have a good reason to automate it
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Did it happen before?&lt;/li&gt;
&lt;li&gt;Is it likely to happen again?&lt;/li&gt;
&lt;li&gt;Will automating it take less time than doing it?&lt;/li&gt;
&lt;li&gt;Will automating it teach you an important company process?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you answered “yes” to 2 out of the 4 questions - automate it!&lt;/p&gt;

&lt;p&gt;More automations = Less reliance on knowledge to operate the system.&lt;/p&gt;

&lt;p&gt;Too much automations = No system awareness.&lt;/p&gt;

&lt;p&gt;P.S. - you can also enable developers to automate it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create available DevOps Capacity
&lt;/h2&gt;

&lt;p&gt;The DevOps needs of a company have spikes.&lt;/p&gt;

&lt;p&gt;One month you need 2 DevOps Engineers, and half of that the next month.&lt;/p&gt;

&lt;p&gt;Switchovers between big efforts and small tasks are common.&lt;/p&gt;

&lt;p&gt;This is true, especially for new companies.&lt;/p&gt;

&lt;p&gt;Break the assumption: “DevOps tasks must be done by a DevOps Engineer”.&lt;/p&gt;

&lt;h2&gt;
  
  
  There are 3 types of DevOps capacity
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Non-Flexible&lt;/strong&gt;: A full-time DevOps Engineer on the team&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semi-Flexible&lt;/strong&gt;: Key developers that can contribute to the DevOps goals&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fully-Flexible&lt;/strong&gt;: A flexible DevOps Services company or freelancer&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can read more about calculating the DevOps capacity your company needs &lt;a href="https://www.meteorops.com/blog/calculating-your-companys-required-devops-capacity"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to focus on what: Common Dilemmas
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;When&lt;/strong&gt;: You work alone, and the system is simple&lt;/p&gt;

&lt;p&gt;‍&lt;strong&gt;Focus&lt;/strong&gt;: On simplifying the development - Dockerize your apps, Create a post-commit pipeline that runs tests&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When&lt;/strong&gt;: You need to be able to create new environments quickly (for development, or for clients)&lt;/p&gt;

&lt;p&gt;‍ &lt;strong&gt;Focus&lt;/strong&gt;: On implementing “One-Click Environments”: Using IaC (e.g., Terraform) + Deployment tool (Depends on the platform).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When&lt;/strong&gt;: You want to e2e test every code modification, but there are many code modifications&lt;/p&gt;

&lt;p&gt;‍&amp;gt; &lt;strong&gt;Focus&lt;/strong&gt;: On splitting the “One-Click Env” into a “base” with shared resources, and “env” with env-specific resources&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When&lt;/strong&gt;: You want to unify &amp;amp; standardize how you deploy, monitor, scale, configure, and secure your workloads&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Focus&lt;/strong&gt;: On implementing an orchestrator such as Kubernetes&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When&lt;/strong&gt;: You want you have many moving parts and wish to be certain a tested change will work&lt;/p&gt;

&lt;p&gt;‍ &lt;strong&gt;Focus&lt;/strong&gt;: On implementing GitOps and consider a Monorepo (the sooner the better)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When&lt;/strong&gt;: You want the DevOps efforts to be done by the dev team&lt;/p&gt;

&lt;p&gt;‍ &lt;strong&gt;Focus&lt;/strong&gt;: On using “actual” IaC tools (Pulumi Typescript/Python), Full “how to operate” (see above) documentation‍&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Never&lt;/strong&gt;: Invest lots of time in new tech without a strong reason&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;ul&gt;
&lt;li&gt;Have your code in Git&lt;/li&gt;
&lt;li&gt;Monitor the basic stuff: CPU, Memory, Disk, Network, App Logs, Cloud Costs&lt;/li&gt;
&lt;li&gt;Architect for high-availability&lt;/li&gt;
&lt;li&gt;Test before you deploy&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  BONUS: An example setup for a CTO approaching Production
&lt;/h2&gt;

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

&lt;h3&gt;
  
  
  2 AWS Accounts
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;One for development and staging&lt;/li&gt;
&lt;li&gt;Another for production&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Monorepo in Github
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Docker-Compose for local development&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2 Infrastructure-as-Code projects: 'base' &amp;amp; 'apps'
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;base = shared resources (e.g., VPC, RDS, ECS Cluster, EKS Cluster)&lt;/li&gt;
&lt;li&gt;apps = env-specific resources (e.g., Lambda Functions, ECS Services, Kubernetes Namespaces)&lt;/li&gt;
&lt;li&gt;config file per environment&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Github Actions Workflow: Development workflow
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Checkout branch and locally develop + test changes&lt;/li&gt;
&lt;li&gt;Create a Pull Request: Deploys a Pull-Request ‘apps’ environment on the ‘development’ environment ‘base’&lt;/li&gt;
&lt;li&gt;On merge to main: Deploys from the ‘main’ branch an ‘apps’ environment onto the ‘development’ environment ‘base’&lt;/li&gt;
&lt;li&gt;Manual: Deploy from the ‘main’ branch onto the ‘staging’ / ‘production’ environment ‘base’&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Setup Notes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Avoid mentioning an environmnent's name in the code for conditional resources deployment&lt;/li&gt;
&lt;li&gt;Use each environment’s config file to declare if a resource should be created&lt;/li&gt;
&lt;li&gt;Could be implemented using Terraform, Terragrunt, Pulumi, CDK, and other IaC tools&lt;/li&gt;
&lt;li&gt;Production should have 2-instances of every workload for high-availability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’d like to see this setup in your startup, &lt;a href="https://www.meteorops.com/devops-setup-for-production-free-consultation"&gt;click here to book a call 👈🏼&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;P.S. - I'll be updating this page occasionally, so you might want to visit again&lt;/p&gt;




&lt;h2&gt;
  
  
  Another Bonus: DevOps Dictionary for Human Beings
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Term&lt;/th&gt;
&lt;th&gt;Definition&lt;/th&gt;
&lt;th&gt;Tools&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Environment&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A working instance of the entire system&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CI (Continuous Integration)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Enable developers to collaborate by agreeing on a single source-of-truth (master/main)&lt;/td&gt;
&lt;td&gt;Jenkins, Github Actions, GitlabCI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CD (Continuous Delivery)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Create an artifact that’s ready for production (tested, tagged)&lt;/td&gt;
&lt;td&gt;JFrog Artifactory, Nexus, AWS ECR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CD (Continuous Deployment)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Every available deliverable (artifact) gets deployed automatically&lt;/td&gt;
&lt;td&gt;ArgoCD, Jenkins, AWS CodeDeploy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monitoring / Observability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Collect metrics/traces/logs from apps and infrastructure, analyze them, and display them, and setup alerts&lt;/td&gt;
&lt;td&gt;Prometheus, Jaeger, Elasticsearch, Fluentd, OpenTelemetry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Infrastructure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The resources on which the workloads run, in which the data is stored, and through which the network flows&lt;/td&gt;
&lt;td&gt;Servers, Databases, Network Routers &amp;amp; Switches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cloud Infrastructure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Same as the above, but specifically in the cloud&lt;/td&gt;
&lt;td&gt;AWS EC2, AWS RDS, GCP Compute Engine, Azure Virtual Machines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cloud&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Computing &amp;amp; Data services served from remote locations for you to build your system&lt;/td&gt;
&lt;td&gt;AWS, Azure, GCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Containerization &amp;amp; Virtualization&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Technologies utilizing Kernel &amp;amp; OS features to create virtual machines, or isolate process (AKA run containers)&lt;/td&gt;
&lt;td&gt;Docker, vSphere, KVM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Secrets Management&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Storing and retrieving sensitive configurations (e.g., tokens, passwords)&lt;/td&gt;
&lt;td&gt;Hashicorp Vault, AWS Secrets Manager, SealedSecrets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Configuration Management&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Usually refers to preparing servers for workloads (e.g., creating directories &amp;amp; files, starting processes)&lt;/td&gt;
&lt;td&gt;Ansible, Chef, Puppet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Version Control&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Saving the code in a versioned way (Git)&lt;/td&gt;
&lt;td&gt;Github, Gitlab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitOps&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Making the system is the same as it’s described in Git&lt;/td&gt;
&lt;td&gt;Flux, ArgoCD, Jenkins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monorepo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;All of the company’s code is in one Git Repository&lt;/td&gt;
&lt;td&gt;NX, Turborepo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Polyrepo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Multiple Git repositories for different components&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;IaC (Infrastructure-as-Code)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Creating Cloud infrastructure with &lt;strong&gt;idempotent&lt;/strong&gt; code and state management&lt;/td&gt;
&lt;td&gt;Terraform, Pulumi, CDK, Crossplane&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deployment&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Execute, serve, or install the artifacts&lt;/td&gt;
&lt;td&gt;ArgoCD, Jenkins, AWS CodeDeploy, Scripts (Bash, Python, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Orchestrator&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Dynamically allocating workloads to a pool of nodes&lt;/td&gt;
&lt;td&gt;Kubernetes, Nomad, AWS ECS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Authentication &amp;amp; Authorization&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Making sure each person, workload, or resource, has access only to what’s necessary (other workloads and resources)&lt;/td&gt;
&lt;td&gt;AWS IAM, OpenID, OpenVPN, Twingate, Istio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Service Discovery&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Exposing available workloads using DNS&lt;/td&gt;
&lt;td&gt;Consul, CoreDNS&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Get more practical advice
&lt;/h2&gt;

&lt;p&gt;I post small nuggets of practical advice on the "MeteorOps Newsletter".&lt;br&gt;
You can &lt;a href="https://meteorops.beehiiv.com/subscribe"&gt;subscribe here 👈🏼&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>cto</category>
      <category>softwareengineering</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
