DatadogPodAutoscaler manifest reference

This product is not supported for your selected Datadog site. ().

The DatadogPodAutoscaler (DPA) custom resource defines autoscaling behavior for a single Kubernetes workload. The Autoscaling UI with Export Recommendation is a good place to start: configure a workload, then copy the generated manifest. Editing the manifest directly gives you access to every field in the custom resource definition (CRD) and makes the DPA a normal part of your GitOps workflow, where the manifest is the reviewed, versioned source of truth.

This page covers the configuration options available in the manifest. Examples on this page use API version datadoghq.com/v1alpha2.

For setup and prerequisites, see Kubernetes Autoscaling. That page covers enabling Workload Autoscaling and the Admission Controller on the Datadog Cluster Agent, required Agent versions, and enabling in-place vertical scaling.

Anatomy of a manifest

The following annotated skeleton shows the structure of a DatadogPodAutoscaler. Every field is optional except targetRef.

apiVersion: datadoghq.com/v1alpha2
kind: DatadogPodAutoscaler
metadata:
  name: my-app                      # required: conventionally the workload name
  namespace: my-namespace           # required: must match the target workload
  annotations:                      # optional
    ad.datadoghq.com/tags: '{"team": "my-team"}'   # optional: tags on this DPA's telemetry
spec:
  owner: Local                      # optional: Local = this manifest is the source of truth (use for GitOps)
                                    # Remote = created and managed from the Datadog UI

  targetRef:                        # required: the workload being autoscaled - one DPA per workload
    apiVersion: apps/v1
    kind: Deployment
    name: my-app

  applyPolicy:                      # optional
    mode: Apply                     # Apply | Preview (Preview = compute recommendations, change nothing)

    scaleUp:                        # optional: horizontal, upward
      strategy: Max                 # Max | Min | Disabled
      stabilizationWindowSeconds: 600
      rules:
        - type: Percent             # Percent | Pods
          value: 50
          periodSeconds: 120        # 1..3600

    scaleDown:                      # optional: horizontal, downward
      strategy: Max
      stabilizationWindowSeconds: 600
      rules:
        - type: Percent
          value: 10
          periodSeconds: 1800

    update:                         # optional: vertical
      strategy: Auto                # Auto | Disabled | TriggerRollout
      # resizePendingPeriod: 600    # see Vertical rollout timing
      # rolloutFallbackDelay: 900   # see Vertical rollout timing

  constraints:                      # optional
    minReplicas: 3
    maxReplicas: 100
    containers:                     # optional: per-container vertical configuration
      - name: "*"                   # "*" matches all containers
        enabled: true
        controlledResources: [cpu, memory]
        controlledValues: RequestsAndLimits   # RequestsAndLimits | RequestsOnly
        minAllowed:
          cpu: "500m"
          memory: 1Gi
        maxAllowed:
          cpu: "4"
          memory: 8Gi

  objectives:                       # optional: configures horizontal scaling (also used by multidimensional). Exactly one entry.
    - type: ContainerResource       # PodResource | ContainerResource | CustomQuery
      containerResource:
        container: my-app
        name: cpu                   # cpu | memory
        value:
          type: Utilization         # Utilization | AbsoluteValue
          utilization: 65

  fallback:                         # optional: in-cluster horizontal fallback if recommendations go stale
    horizontal:
      enabled: true
      direction: ScaleUp            # ScaleUp | ScaleDown | All (default ScaleUp)
      triggers:
        staleRecommendationThresholdSeconds: 600   # 100..3600, default 600

  options:                          # optional
    burstable: false                # true = remove CPU limits, keep CPU request recommendations
    outOfMemory:
      bumpUpRatio: "1.2"            # +20% memory limit after an OOMKill (default)

Supported target workloads

targetRef.kindapiVersionStatus
Deploymentapps/v1Supported
Rollout (Argo Rollouts)argoproj.io/v1alpha1Supported
StatefulSetapps/v1Supported

For an Argo Rollout, point targetRef at the Rollout itself rather than at any Deployment it manages:

  targetRef:
    apiVersion: argoproj.io/v1alpha1
    kind: Rollout
    name: my-app

Choose a scaling mode

The combination of objectives and applyPolicy.update.strategy determines whether a DPA scales horizontally, vertically, or both:

objectives setapplyPolicy.update.strategyResulting mode
yesDisabled (or unset)Horizontal only
noAutoVertical only
yesAutoMultidimensional (both)

Container constraints

Most vertical options are expressed through spec.constraints.containers[]:

FieldTypeDefaultMeaning
namestring, required-Container name, or "*" to match every container that has no entry of its own (see Exclude a container)
enabledbooltruefalse disables resource autoscaling for this container
controlledResourceslist of cpu, memory[cpu, memory]Which resources receive vertical recommendations. An empty list is equivalent to enabled: false
controlledValuesenumRequestsAndLimitsWhether recommendations write both requests and limits, or requests only
minAllowedresource map-Lower bound for the container’s requests
maxAllowedresource map-Upper bound for the container’s requests

If constraints.containers is omitted entirely, resource scaling is enabled for all containers, with no bounds.

Right-size CPU and memory

When a DPA combines horizontal scaling (objectives) with vertical scaling (update.strategy: Auto), the default behavior is to produce vertical recommendations for memory only. CPU requests and limits are left untouched, and the VerticalAbleToRecommend condition may show as Unknown.

If a DPA right-sizes memory while leaving CPU unchanged, this is the reason. It is not related to Quality of Service (QoS) class preservation.

Use controlledResources to declare which resources receive vertical recommendations:

controlledResourcesVertical recommendations produced
unsetmemory only (default)
[memory]memory only, same as unset
[cpu, memory]memory and CPU
[cpu]CPU only

Full example:

apiVersion: datadoghq.com/v1alpha2
kind: DatadogPodAutoscaler
metadata:
  name: my-app
  namespace: my-namespace
spec:
  owner: Local
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-app
  applyPolicy:
    mode: Apply
    update:
      strategy: Auto              # required - without it, nothing vertical is applied
  constraints:
    minReplicas: 3
    maxReplicas: 60
    containers:
      - name: "*"
        controlledResources:
          - cpu                   # opts CPU into vertical rightsizing
          - memory
        controlledValues: RequestsAndLimits
  objectives:
    - type: ContainerResource
      containerResource:
        container: my-app
        name: cpu
        value:
          type: Utilization
          utilization: 65

This feature requires Datadog Cluster Agent 7.78.0+. On older versions, the controlledResources field is accepted by the CRD but has no effect.

Remove CPU limits with burstable mode

CPU limit recommendations are derived from sustained usage percentiles over a multi-day window. A short warm-up spike (for example, during JVM startup) has little effect on those percentiles, so the recommended CPU limit can be too low and cause the application to be throttled at the wrong moment. Memory is not affected in the same way because peak memory usage provides a reliable ceiling.

Burstable mode removes CPU limits entirely while still applying CPU request recommendations:

apiVersion: datadoghq.com/v1alpha2
kind: DatadogPodAutoscaler
metadata:
  name: my-java-app
  namespace: my-namespace
spec:
  owner: Local
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-java-app
  applyPolicy:
    mode: Apply
    update:
      strategy: Auto
  options:
    burstable: true

If options.burstable is left unset, the Cluster Agent’s cluster-wide default applies. Set it explicitly to false to opt a single workload out of the default behavior.

Effect on the pod:

BeforeAfter
CPU request1400m (recommendation)
CPU limit2removed
Memory request500Mi450Mi (recommendation)
Memory limit2Gi2Gi, preserved

Before enabling it:

  • Pods that were Guaranteed QoS become Burstable QoS. This changes their eviction priority under node pressure.
  • Without a CPU limit, a container can consume available node CPU. Kernel CPU shares still apply.
  • Burstable mode takes precedence over controlledValues for CPU limits. If both are set, burstable wins.

Tune the OOMKill memory bump

After an OOMKill, the memory limit is raised by 20% relative to the limit in force at the time. The increase is applied immediately and repeated after each subsequent OOMKill until the workload stabilizes.

To change the ratio:

spec:
  options:
    outOfMemory:
      bumpUpRatio: "1.5"      # 1.2 = +20% (default), 1.5 = +50%

Quote the value: it is a Kubernetes quantity, not a floating-point number.

When to raise it. Raise the ratio for workloads whose memory usage can peak sharply above previous peaks. A larger bump reaches the right memory limit faster and avoids several successive bumps before the workload stabilizes. When you raise it, also set a minAllowed memory floor (see Set per-container bounds) so the limit cannot fall back below a safe value between recommendation cycles.

Right-size requests only

If you have deliberately tuned limits (for burst headroom, a platform requirement, or a QoS guarantee) and want Datadog to right-size only requests, use controlledValues: RequestsOnly.

spec:
  constraints:
    containers:
      - name: my-app
        controlledResources: [cpu, memory]
        controlledValues: RequestsOnly     # limits are not right-sized
controlledValuesRequestsLimits
RequestsAndLimits (default)recommendedrecommended
RequestsOnlyrecommendednot right-sized, except where a limit must move to keep the pod spec valid (see below)

Interactions to be aware of:

  • On a container where request == limit, lowering the request breaks the Guaranteed QoS class. If you need Guaranteed, keep RequestsAndLimits; the recommender handles request == limit containers explicitly.
  • Burstable mode overrides this for CPU limits (see Remove CPU limits with burstable mode).
  • OOMKill handling still adjusts the memory limit. RequestsOnly does not suppress the memory bump. After an OOMKill, the memory limit is raised, and the request is potentially raised with it (Kubernetes rejects any pod whose request exceeds its limit). Read RequestsOnly as “limits are not right-sized”, not “limits are never modified”. See Tune the OOMKill memory bump.

Choosing a combination:

GoalConfiguration
Right-size everythingcontrolledResources: [cpu, memory] + controlledValues: RequestsAndLimits
Right-size requests, leave limits as writtencontrolledValues: RequestsOnly
Right-size memory only, leave CPU alonecontrolledResources: [memory]
Right-size CPU requests, no CPU limit at alloptions.burstable: true
Leave a container entirely aloneenabled: false

Set per-container bounds

minAllowed and maxAllowed constrain the resource requests that the recommender can produce. They are recommended for latency-sensitive workloads. They are also recommended when you change the OOM bump ratio (see Tune the OOMKill memory bump) to prevent memory requests from falling below a safe minimum between recommendation cycles.

spec:
  constraints:
    minReplicas: 2
    maxReplicas: 100
    containers:
      - name: api
        enabled: true
        minAllowed:
          cpu: "1"
          memory: 1Gi
        maxAllowed:
          cpu: "4"
          memory: 5Gi
      - name: worker
        enabled: true          # no bounds - recommendations are unconstrained

Exclude a container

You can exclude a container from vertical recommendations, the horizontal signal, or both.

Exclude a container from vertical recommendations

spec:
  constraints:
    containers:
      - name: my-app
        enabled: true
      - name: istio-proxy
        enabled: false          # resources for this container are never modified

You can also specify the equivalent configuration explicitly:

      - name: istio-proxy
        controlledResources: []   # empty list is equivalent to enabled: false

A common pattern is to autoscale everything except a known sidecar:

spec:
  constraints:
    containers:
      - name: "*"
        enabled: true
        controlledResources: [cpu, memory]
      - name: istio-proxy
        enabled: false

How "*" and named entries combine: the "*" entry applies to every container that does not have a named entry. A container with its own named entry takes only the settings declared under that name. The two are not merged, so the wildcard contributes nothing to it.

In the example above, istio-proxy is governed solely by enabled: false and does not inherit controlledResources from the wildcard. Every other container in the pod uses the wildcard entry.

Note: If you add a named entry only to set a bound, repeat any wildcard settings you still want. In the example below, my-app falls back to the default RequestsAndLimits rather than the RequestsOnly set on the wildcard:

      - name: "*"
        controlledValues: RequestsOnly
      - name: my-app
        maxAllowed:
          memory: 8Gi          # controlledValues is NOT inherited - repeat it if you want it

Exclude a container from the horizontal signal

enabled: false governs vertical behavior only. The horizontal objective is chosen separately, and this is where sidecars most often distort scaling decisions:

  objectives:
    # Recommended: scale on the application container's CPU
    - type: ContainerResource
      containerResource:
        container: my-app
        name: cpu
        value:
          type: Utilization
          utilization: 65

The following pod-level configuration can produce misleading results when sidecars are present:

  objectives:
    # Risky when sidecars are present: pod-level utilization is diluted by
    # sidecar requests, so a busy application container can appear idle.
    - type: PodResource
      podResource:
        name: cpu
        value:
          type: Utilization
          utilization: 65

Recommendation: If the pod has any sidecar, use ContainerResource scoped to the main container. Reserve PodResource for single-container pods.

Configure sidecars

Ordinary sidecars (spec.containers)

Nothing special is required. They can be bounded, excluded, or targeted like any other container; see Exclude a container.

Native sidecars (spec.initContainers with restartPolicy: Always)

Kubernetes 1.29+ supports long-running sidecars declared in initContainers with restartPolicy: Always, known as the native sidecar pattern. A native sidecar runs for the pod’s entire lifetime.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  template:
    spec:
      initContainers:
        - name: log-shipper
          image: log-shipper:1.2
          restartPolicy: Always      # this is what makes it a native sidecar
          resources:
            requests: {cpu: 100m, memory: 128Mi}
            limits:   {memory: 256Mi}
      containers:
        - name: my-app
          image: my-app:4.5
          resources:
            requests: {cpu: "1", memory: 2Gi}
            limits:   {cpu: "2", memory: 4Gi}

Native sidecars are fully supported. They are treated as ordinary containers: recommendations are produced and applied for them, and they appear in the workload’s container list where they can be configured or excluded. Reference them in constraints.containers[] by name, exactly like any other container. There is no separate initContainers block in the DPA spec, and a "*" entry covers them too.

spec:
  constraints:
    containers:
      - name: my-app
        controlledResources: [cpu, memory]
        controlledValues: RequestsAndLimits
      - name: log-shipper        # native sidecar, referenced by name
        enabled: false

Points to be aware of:

  • restartPolicy: Always is what distinguishes them. Ordinary init containers (those that run to completion before the application starts) are not native sidecars and are not managed by a DPA.
  • Cost and savings figures may under-count native sidecars. Their resource requests are reported under a separate aggregation, so the cost figures shown for a workload with native sidecars can look inconsistent with its observed usage. This is a known limitation that affects the cost display only; recommendations are unaffected.
  • Injected sidecars (such as Istio’s) are added by a mutating admission webhook at pod level and never appear in the Deployment manifest. They are still picked up, because the container list is reconciled from running pods rather than from the workload manifest alone.
  • Do not exclude autoscaled containers from Agent collection. A DatadogPodAutoscaler relies on the metrics the Agent collects for the containers it manages. If a container in an autoscaled workload is filtered out through the Agent’s container discovery configuration, no metrics exist for it and it cannot be right-sized. Confirm that no container in an autoscaled workload is excluded from collection. For how inclusion and exclusion rules work, see Container Discovery Management.

Additional manifest options

Preview (dry-run) mode

spec:
  applyPolicy:
    mode: Preview     # recommendations are computed and visible in .status, but nothing is applied

Useful as a temporary stop switch. If a horizontal configuration is invalid, vertical rightsizing continues to run; setting mode: Preview freezes both while you correct it.

Disable one scaling direction

spec:
  applyPolicy:
    scaleUp:
      strategy: Max
    scaleDown:
      strategy: Disabled     # never scale down
    update:
      strategy: Auto

For vertical-only scaling, omit objectives and set update.strategy: Auto, as in the Choose a scaling mode table. With no objectives, horizontal scaling has no target to act on, so you do not also need to set scaleUp and scaleDown to Disabled.

Vertical rollout timing

The controller applies a vertical change by the least disruptive route available and escalates if that route stalls. Two fields control how long it waits at each step:

spec:
  applyPolicy:
    update:
      strategy: Auto
      resizePendingPeriod: 600      # 1..3600 seconds
      rolloutFallbackDelay: 900     # 1..3600 seconds
FieldControls
resizePendingPeriodHow long to wait before evicting a pod when the kubelet reports the resize as pending (accepted but not progressing, often because the node lacks headroom)
rolloutFallbackDelayHow long to wait before falling back to a full rollout when evictions are blocked, typically by a PodDisruptionBudget

Both are optional and accept 1 to 3600 seconds. Leaving them unset uses the controller’s built-in defaults.

  • Raise resizePendingPeriod where eviction is expensive (long warm-up, large caches, slow drain). You trade a longer period at the old size for fewer restarts.
  • Raise rolloutFallbackDelay on workloads with a tight PodDisruptionBudget, so a temporary budget constraint does not immediately escalate to a full rollout.
  • Lower either where restarts are cheap and you want recommendations to take effect faster.

These control how a change is delivered, not whether one happens. To stop changes entirely, use update.strategy: Disabled or applyPolicy.mode: Preview. These fields apply to in-place vertical scaling; see the overview for cluster-level enablement and Kubernetes requirements.

Local fallback tuning

spec:
  fallback:
    horizontal:
      enabled: true
      direction: ScaleUp          # default; use All to allow fallback scale-in as well
      triggers:
        staleRecommendationThresholdSeconds: 600    # 100..3600

Fallback recommendations are computed inside the cluster from Agent-collected metrics, so scaling continues if Datadog cannot deliver a recommendation within the threshold.

This feature also requires cluster-side configuration on both the Cluster Agent and the node Agents. See Kubernetes Autoscaling or contact Datadog Support.

Absolute-value objectives

Instead of a utilization percentage, target an absolute value:

  objectives:
    - type: ContainerResource
      containerResource:
        container: my-app
        name: cpu
        value:
          type: AbsoluteValue
          absoluteValue: "1.5"     # target cores per pod

Custom query objectives

Scale on any Datadog metric rather than CPU or memory:

  objectives:
    - type: CustomQuery
      customQuery:
        window: 5m
        request:
          queries:
            - name: a
              source: Metrics
              metrics:
                query: "avg:my.queue.depth{service:my-app}"
        value:
          type: AbsoluteValue
          absoluteValue: "100"

source may also be ApmMetrics, with fields such as service, resourceName, operationName, and stat.

Custom queries are supported for horizontal scaling only. Combining a custom query with vertical scaling is not supported, because the autoscaler cannot infer which dimension an arbitrary query should act on.

Tag a DPA’s telemetry

metadata:
  annotations:
    ad.datadoghq.com/tags: '{"team": "my-team", "tier": "critical"}'

This adds the tags to the autoscaling telemetry emitted for this DPA. For the list of metrics the Cluster Agent emits, see the Datadog Cluster Agent integration.

Further reading