Running Kubernetes workloads continuously means paying for compute even when an application has little or no traffic. For services that receive requests only occasionally, keeping multiple Pods running can be inefficient.

Kubernetes 1.37 introduces an important improvement to the Horizontal Pod Autoscaler (HPA): scale-to-zero support graduates to Beta and is enabled by default. It allows workloads using object or external metrics to scale down to zero Pods when there is no work and automatically scale back up when demand returns.

This is particularly useful for queue consumers, batch-processing services, and workloads that use expensive resources such as GPUs.

For .NET developers, the feature provides an interesting way to reduce idle compute while introducing an important trade-off: when the application reaches zero replicas, the next request or piece of work has to wait for a new Pod to start.

What Is HPA Scale-to-Zero?

The Kubernetes Horizontal Pod Autoscaler normally adjusts the number of Pods according to observed metrics.

For example:

minReplicas: 1
maxReplicas: 10

means the workload will normally keep at least one Pod running.

With Kubernetes 1.37, an HPA using an object or external metric can instead use:

minReplicas: 0

When the metric indicates that no replicas are required, Kubernetes can scale the workload to zero.

When the metric later indicates that work is available, the HPA can increase the replica count again.

The important limitation is that CPU and memory resource metrics cannot be used to trigger scale-from-zero. Those metrics depend on running Pods, so Kubernetes requires an object or external metric for this behavior.

Why Scale-to-Zero Matters for .NET Applications

Consider a .NET worker that processes messages from a queue.

Without scale-to-zero:

Queue empty
    ↓
.NET Pod remains running
    ↓
CPU and memory continue to be allocated
    ↓
Compute cost continues

With scale-to-zero:

Queue empty
    ↓
HPA scales workload to 0
    ↓
No application Pods running
    ↓
New work arrives
    ↓
External metric increases
    ↓
HPA scales workload up
    ↓
.NET Pod starts processing

This can be useful when workloads spend significant periods idle.

Typical examples include:

The Kubernetes project specifically identifies occasionally used queue consumers and GPU workloads as use cases for scale-to-zero.

Configuring a .NET Worker for Scale-to-Zero

Suppose a .NET worker processes messages from an external queue. The HPA could be configured using an external metric representing the amount of pending work.

A simplified HPA configuration looks like this:

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: order-worker
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: order-worker
  minReplicas: 0
  maxReplicas: 10
  metrics:
    - type: External
      external:
        metric:
          name: orders_pending
        target:
          type: Value
          value: "10"

The exact external metric configuration depends on the metrics adapter and monitoring system used by the cluster.

The important parts are:

minReplicas: 0

and:

type: External

Kubernetes requires at least one object or external metric when minReplicas is zero.

A Simple .NET Worker Example

The application itself does not need special Kubernetes-specific code just to support scale-to-zero.

A typical .NET worker can continue processing messages normally:

public class OrderWorker : BackgroundService
{
    private readonly ILogger<OrderWorker> _logger;

    public OrderWorker(ILogger<OrderWorker> logger)
    {
        _logger = logger;
    }

    protected override async Task ExecuteAsync(
        CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            var message = await GetNextMessageAsync(stoppingToken);

            if (message is null)
            {
                await Task.Delay(
                    TimeSpan.FromSeconds(5),
                    stoppingToken);

                continue;
            }

            await ProcessOrderAsync(message, stoppingToken);
        }
    }

    private Task<OrderMessage?> GetNextMessageAsync(
        CancellationToken cancellationToken)
    {
        // Read from the configured message broker.
        return Task.FromResult<OrderMessage?>(null);
    }

    private Task ProcessOrderAsync(
        OrderMessage message,
        CancellationToken cancellationToken)
    {
        // Process the order.
        return Task.CompletedTask;
    }
}

public record OrderMessage(int Id);

When the Deployment has zero replicas, this code is not running. Kubernetes starts the Pod again when the external metric causes the HPA to scale the workload up.

That makes startup behavior an important part of the architecture.

Cold Starts: The Main Trade-Off

Scale-to-zero reduces idle compute, but it does not make the workload instantly available.

Suppose the following happens:

No workload
    ↓
0 Pods
    ↓
Message arrives
    ↓
Metric changes
    ↓
HPA calculates desired replicas
    ↓
Deployment creates Pod
    ↓
Container image starts
    ↓
.NET runtime starts
    ↓
Application initializes
    ↓
Readiness probe succeeds
    ↓
Work can be processed

Every step introduces some latency.

For a .NET application, startup can include:

Therefore, scale-to-zero is not automatically appropriate for latency-sensitive workloads.

Cost vs Cold Start

The trade-off can be summarized as follows:

FactorKeep 1+ Pods RunningScale to Zero
Idle compute costHigherLower
First-request latencyLowerHigher
Application availability while idleImmediateRequires startup
Configuration complexityLowerHigher
Suitable for continuous trafficYesUsually unnecessary
Suitable for intermittent workLess efficientStrong fit
Cold-start impactMinimalImportant
Operational considerationsSimplerRequires careful testing

The right choice depends on how frequently the workload is used and how much startup latency the application can tolerate.

Designing .NET Containers for Faster Recovery

If scale-to-zero is used, container startup should be predictable.

Keep Startup Work Reasonable

Avoid performing unnecessary expensive operations during application startup.

For example, avoid loading large datasets into memory before the application becomes ready unless the workload actually requires it.

Use Readiness Probes

A readiness probe prevents Kubernetes from treating a newly started Pod as ready before the application can process traffic.

For an ASP.NET Core application, the configuration might look like:

readinessProbe:
  httpGet:
    path: /health/ready
    port: 8080
  initialDelaySeconds: 2
  periodSeconds: 5

The exact values should be based on the application's actual startup characteristics rather than copied blindly.

Keep Container Images Efficient

A smaller container image can reduce the amount of data that needs to be downloaded when a new Pod starts.

For a .NET application, multi-stage Docker builds are a common approach:

FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build

WORKDIR /src
COPY . .

RUN dotnet restore
RUN dotnet publish -c Release -o /app/publish

FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime

WORKDIR /app
COPY --from=build /app/publish .

ENTRYPOINT ["dotnet", "OrderWorker.dll"]

The runtime image contains the published application instead of the complete SDK environment.

The exact .NET version should match the application's supported runtime.

Choosing the Right Metric

The metric is one of the most important design decisions.

For a queue-processing application, queue depth is generally more meaningful than CPU utilization because the workload may have zero Pods when idle.

For example:

orders_pending = 0
        ↓
0 Pods

orders_pending = 50
        ↓
Scale workers

orders_pending = 0
        ↓
Scale back to 0

This model directly represents business workload.

By contrast, CPU utilization cannot provide a meaningful scale-from-zero signal because there are no running Pods from which to collect CPU usage. Kubernetes therefore limits scale-to-zero to object and external metrics.

Monitoring Scale-to-Zero Behavior

Kubernetes 1.37 records a ScaledToZero condition on the HPA while the workload is being held at zero replicas. When the workload scales back up, the condition changes accordingly.

Start troubleshooting with:

kubectl get hpa

Then inspect the HPA:

kubectl describe hpa order-worker

Check the Deployment:

kubectl get deployment order-worker

And inspect Pods:

kubectl get pods

For a workload that should currently be idle, you may see:

NAME           READY   UP-TO-DATE   AVAILABLE
order-worker   0/0     0            0

When external demand appears, watch the workload:

kubectl get pods -w

This helps identify whether the delay is coming from metric collection, HPA scaling, scheduling, image pulling, application startup, or readiness.

Common Mistakes

Using CPU Metrics for Scale-to-Zero

This is not supported for the scale-to-zero scenario. Use an object or external metric instead.

Ignoring Cold Starts

Saving compute cost is not useful if the application has a strict response-time requirement and takes too long to become ready.

Measure the complete startup path before enabling scale-to-zero for latency-sensitive workloads.

Using an Unreliable External Metric

If the metric disappears or cannot be retrieved, scaling behavior can become difficult to reason about.

The external metric pipeline should therefore be monitored as part of the production system.

Keeping spec.replicas in the Deployment

When HPA manages a Deployment, Kubernetes recommends removing spec.replicas from the Deployment manifest. Applying a manifest containing a fixed replica count can interfere with HPA-managed scaling.

Best Practices

  1. Use external or object metrics that represent actual workload demand.

  2. Test scale-up behavior before using scale-to-zero in production.

  3. Measure .NET application startup time.

  4. Keep container images reasonably small.

  5. Use readiness probes.

  6. Monitor HPA conditions and external metrics.

  7. Test what happens when multiple requests or messages arrive while the workload is starting.

  8. Keep human operators aware that zero replicas can be intentional rather than an application failure.

  9. Use scale-to-zero primarily for workloads where idle periods justify the cold-start trade-off.

  10. Document expected scale-from-zero behavior in the application's operational runbook.

Advantages and Disadvantages

Advantages

Disadvantages

Conclusion

Kubernetes 1.37 makes HPA scale-to-zero more practical by graduating the capability to Beta and enabling it by default. For workloads using object or external metrics, minReplicas: 0 can allow Kubernetes to remove all application Pods during idle periods and bring them back when demand returns.

For .NET workloads, the main architectural decision is not simply whether scale-to-zero saves money. The real question is whether the workload can tolerate the startup latency associated with creating a new Pod.

Queue consumers, batch processors, intermittent background services, and expensive compute workloads are strong candidates. Always-on APIs with strict latency requirements may benefit less.

The best implementation starts with the metric, validates cold-start behavior, monitors the complete scale-up path, and measures the operational trade-off before applying the pattern broadly.