Configure AWS CloudFront Continuous Deployment Policies

The aws:cloudfront/continuousDeploymentPolicy:ContinuousDeploymentPolicy resource, part of the Pulumi AWS provider, defines how CloudFront routes production traffic between primary and staging distributions for gradual rollouts. This guide focuses on three capabilities: percentage-based traffic splitting, session stickiness for consistent user experience, and header-based routing for selective testing.

Continuous deployment policies require both a staging distribution (marked with staging: true) and a production distribution that references the policy ID. The examples are intentionally small. Combine them with your own CloudFront distributions and monitoring infrastructure.

Route a percentage of traffic to staging

Teams testing CDN changes often route a small percentage of production traffic to staging to validate behavior under real load.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const staging = new aws.cloudfront.Distribution("staging", {
    enabled: true,
    staging: true,
});
const example = new aws.cloudfront.ContinuousDeploymentPolicy("example", {
    enabled: true,
    stagingDistributionDnsNames: {
        items: [staging.domainName],
        quantity: 1,
    },
    trafficConfig: {
        type: "SingleWeight",
        singleWeightConfig: {
            weight: 0.01,
        },
    },
});
const production = new aws.cloudfront.Distribution("production", {
    enabled: true,
    continuousDeploymentPolicyId: example.id,
});
import pulumi
import pulumi_aws as aws

staging = aws.cloudfront.Distribution("staging",
    enabled=True,
    staging=True)
example = aws.cloudfront.ContinuousDeploymentPolicy("example",
    enabled=True,
    staging_distribution_dns_names={
        "items": [staging.domain_name],
        "quantity": 1,
    },
    traffic_config={
        "type": "SingleWeight",
        "single_weight_config": {
            "weight": 0.01,
        },
    })
production = aws.cloudfront.Distribution("production",
    enabled=True,
    continuous_deployment_policy_id=example.id)
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/cloudfront"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		staging, err := cloudfront.NewDistribution(ctx, "staging", &cloudfront.DistributionArgs{
			Enabled: pulumi.Bool(true),
			Staging: pulumi.Bool(true),
		})
		if err != nil {
			return err
		}
		example, err := cloudfront.NewContinuousDeploymentPolicy(ctx, "example", &cloudfront.ContinuousDeploymentPolicyArgs{
			Enabled: pulumi.Bool(true),
			StagingDistributionDnsNames: &cloudfront.ContinuousDeploymentPolicyStagingDistributionDnsNamesArgs{
				Items: pulumi.StringArray{
					staging.DomainName,
				},
				Quantity: pulumi.Int(1),
			},
			TrafficConfig: &cloudfront.ContinuousDeploymentPolicyTrafficConfigArgs{
				Type: pulumi.String("SingleWeight"),
				SingleWeightConfig: &cloudfront.ContinuousDeploymentPolicyTrafficConfigSingleWeightConfigArgs{
					Weight: pulumi.Float64(0.01),
				},
			},
		})
		if err != nil {
			return err
		}
		_, err = cloudfront.NewDistribution(ctx, "production", &cloudfront.DistributionArgs{
			Enabled:                      pulumi.Bool(true),
			ContinuousDeploymentPolicyId: example.ID(),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var staging = new Aws.CloudFront.Distribution("staging", new()
    {
        Enabled = true,
        Staging = true,
    });

    var example = new Aws.CloudFront.ContinuousDeploymentPolicy("example", new()
    {
        Enabled = true,
        StagingDistributionDnsNames = new Aws.CloudFront.Inputs.ContinuousDeploymentPolicyStagingDistributionDnsNamesArgs
        {
            Items = new[]
            {
                staging.DomainName,
            },
            Quantity = 1,
        },
        TrafficConfig = new Aws.CloudFront.Inputs.ContinuousDeploymentPolicyTrafficConfigArgs
        {
            Type = "SingleWeight",
            SingleWeightConfig = new Aws.CloudFront.Inputs.ContinuousDeploymentPolicyTrafficConfigSingleWeightConfigArgs
            {
                Weight = 0.01,
            },
        },
    });

    var production = new Aws.CloudFront.Distribution("production", new()
    {
        Enabled = true,
        ContinuousDeploymentPolicyId = example.Id,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.cloudfront.Distribution;
import com.pulumi.aws.cloudfront.DistributionArgs;
import com.pulumi.aws.cloudfront.ContinuousDeploymentPolicy;
import com.pulumi.aws.cloudfront.ContinuousDeploymentPolicyArgs;
import com.pulumi.aws.cloudfront.inputs.ContinuousDeploymentPolicyStagingDistributionDnsNamesArgs;
import com.pulumi.aws.cloudfront.inputs.ContinuousDeploymentPolicyTrafficConfigArgs;
import com.pulumi.aws.cloudfront.inputs.ContinuousDeploymentPolicyTrafficConfigSingleWeightConfigArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var staging = new Distribution("staging", DistributionArgs.builder()
            .enabled(true)
            .staging(true)
            .build());

        var example = new ContinuousDeploymentPolicy("example", ContinuousDeploymentPolicyArgs.builder()
            .enabled(true)
            .stagingDistributionDnsNames(ContinuousDeploymentPolicyStagingDistributionDnsNamesArgs.builder()
                .items(staging.domainName())
                .quantity(1)
                .build())
            .trafficConfig(ContinuousDeploymentPolicyTrafficConfigArgs.builder()
                .type("SingleWeight")
                .singleWeightConfig(ContinuousDeploymentPolicyTrafficConfigSingleWeightConfigArgs.builder()
                    .weight(0.01)
                    .build())
                .build())
            .build());

        var production = new Distribution("production", DistributionArgs.builder()
            .enabled(true)
            .continuousDeploymentPolicyId(example.id())
            .build());

    }
}
resources:
  staging:
    type: aws:cloudfront:Distribution
    properties:
      enabled: true
      staging: true # ... other configuration ...
  example:
    type: aws:cloudfront:ContinuousDeploymentPolicy
    properties:
      enabled: true
      stagingDistributionDnsNames:
        items:
          - ${staging.domainName}
        quantity: 1
      trafficConfig:
        type: SingleWeight
        singleWeightConfig:
          weight: '0.01'
  production:
    type: aws:cloudfront:Distribution
    properties:
      enabled: true # NOTE: A continuous deployment policy cannot be associated to distribution
      #   # on creation. Set this argument once the resource exists.
      continuousDeploymentPolicyId: ${example.id}

When the policy is enabled, CloudFront splits traffic according to the weight property (0.01 = 1% of requests). The staging distribution must have staging: true set, and the production distribution references the policy via continuousDeploymentPolicyId. The trafficConfig type of “SingleWeight” tells CloudFront to use percentage-based splitting rather than header-based routing.

Maintain user sessions during traffic splitting

Applications with user sessions need to ensure users stay on the same distribution throughout their session to avoid inconsistent experiences.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.cloudfront.ContinuousDeploymentPolicy("example", {
    enabled: true,
    stagingDistributionDnsNames: {
        items: [staging.domainName],
        quantity: 1,
    },
    trafficConfig: {
        type: "SingleWeight",
        singleWeightConfig: {
            weight: 0.01,
            sessionStickinessConfig: {
                idleTtl: 300,
                maximumTtl: 600,
            },
        },
    },
});
import pulumi
import pulumi_aws as aws

example = aws.cloudfront.ContinuousDeploymentPolicy("example",
    enabled=True,
    staging_distribution_dns_names={
        "items": [staging["domainName"]],
        "quantity": 1,
    },
    traffic_config={
        "type": "SingleWeight",
        "single_weight_config": {
            "weight": 0.01,
            "session_stickiness_config": {
                "idle_ttl": 300,
                "maximum_ttl": 600,
            },
        },
    })
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/cloudfront"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := cloudfront.NewContinuousDeploymentPolicy(ctx, "example", &cloudfront.ContinuousDeploymentPolicyArgs{
			Enabled: pulumi.Bool(true),
			StagingDistributionDnsNames: &cloudfront.ContinuousDeploymentPolicyStagingDistributionDnsNamesArgs{
				Items: pulumi.StringArray{
					staging.DomainName,
				},
				Quantity: pulumi.Int(1),
			},
			TrafficConfig: &cloudfront.ContinuousDeploymentPolicyTrafficConfigArgs{
				Type: pulumi.String("SingleWeight"),
				SingleWeightConfig: &cloudfront.ContinuousDeploymentPolicyTrafficConfigSingleWeightConfigArgs{
					Weight: pulumi.Float64(0.01),
					SessionStickinessConfig: &cloudfront.ContinuousDeploymentPolicyTrafficConfigSingleWeightConfigSessionStickinessConfigArgs{
						IdleTtl:    pulumi.Int(300),
						MaximumTtl: pulumi.Int(600),
					},
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.CloudFront.ContinuousDeploymentPolicy("example", new()
    {
        Enabled = true,
        StagingDistributionDnsNames = new Aws.CloudFront.Inputs.ContinuousDeploymentPolicyStagingDistributionDnsNamesArgs
        {
            Items = new[]
            {
                staging.DomainName,
            },
            Quantity = 1,
        },
        TrafficConfig = new Aws.CloudFront.Inputs.ContinuousDeploymentPolicyTrafficConfigArgs
        {
            Type = "SingleWeight",
            SingleWeightConfig = new Aws.CloudFront.Inputs.ContinuousDeploymentPolicyTrafficConfigSingleWeightConfigArgs
            {
                Weight = 0.01,
                SessionStickinessConfig = new Aws.CloudFront.Inputs.ContinuousDeploymentPolicyTrafficConfigSingleWeightConfigSessionStickinessConfigArgs
                {
                    IdleTtl = 300,
                    MaximumTtl = 600,
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.cloudfront.ContinuousDeploymentPolicy;
import com.pulumi.aws.cloudfront.ContinuousDeploymentPolicyArgs;
import com.pulumi.aws.cloudfront.inputs.ContinuousDeploymentPolicyStagingDistributionDnsNamesArgs;
import com.pulumi.aws.cloudfront.inputs.ContinuousDeploymentPolicyTrafficConfigArgs;
import com.pulumi.aws.cloudfront.inputs.ContinuousDeploymentPolicyTrafficConfigSingleWeightConfigArgs;
import com.pulumi.aws.cloudfront.inputs.ContinuousDeploymentPolicyTrafficConfigSingleWeightConfigSessionStickinessConfigArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new ContinuousDeploymentPolicy("example", ContinuousDeploymentPolicyArgs.builder()
            .enabled(true)
            .stagingDistributionDnsNames(ContinuousDeploymentPolicyStagingDistributionDnsNamesArgs.builder()
                .items(staging.domainName())
                .quantity(1)
                .build())
            .trafficConfig(ContinuousDeploymentPolicyTrafficConfigArgs.builder()
                .type("SingleWeight")
                .singleWeightConfig(ContinuousDeploymentPolicyTrafficConfigSingleWeightConfigArgs.builder()
                    .weight(0.01)
                    .sessionStickinessConfig(ContinuousDeploymentPolicyTrafficConfigSingleWeightConfigSessionStickinessConfigArgs.builder()
                        .idleTtl(300)
                        .maximumTtl(600)
                        .build())
                    .build())
                .build())
            .build());

    }
}
resources:
  example:
    type: aws:cloudfront:ContinuousDeploymentPolicy
    properties:
      enabled: true
      stagingDistributionDnsNames:
        items:
          - ${staging.domainName}
        quantity: 1
      trafficConfig:
        type: SingleWeight
        singleWeightConfig:
          weight: '0.01'
          sessionStickinessConfig:
            idleTtl: 300
            maximumTtl: 600

The sessionStickinessConfig controls how long CloudFront keeps a user on the same distribution. The idleTtl sets the maximum idle time (300 seconds), while maximumTtl caps the total session duration (600 seconds). Once a user is routed to staging, they continue hitting staging until their session expires.

Route traffic based on request headers

Development teams often test staging distributions without affecting regular users by routing only requests with specific headers.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.cloudfront.ContinuousDeploymentPolicy("example", {
    enabled: true,
    stagingDistributionDnsNames: {
        items: [staging.domainName],
        quantity: 1,
    },
    trafficConfig: {
        type: "SingleHeader",
        singleHeaderConfig: {
            header: "aws-cf-cd-example",
            value: "example",
        },
    },
});
import pulumi
import pulumi_aws as aws

example = aws.cloudfront.ContinuousDeploymentPolicy("example",
    enabled=True,
    staging_distribution_dns_names={
        "items": [staging["domainName"]],
        "quantity": 1,
    },
    traffic_config={
        "type": "SingleHeader",
        "single_header_config": {
            "header": "aws-cf-cd-example",
            "value": "example",
        },
    })
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/cloudfront"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := cloudfront.NewContinuousDeploymentPolicy(ctx, "example", &cloudfront.ContinuousDeploymentPolicyArgs{
			Enabled: pulumi.Bool(true),
			StagingDistributionDnsNames: &cloudfront.ContinuousDeploymentPolicyStagingDistributionDnsNamesArgs{
				Items: pulumi.StringArray{
					staging.DomainName,
				},
				Quantity: pulumi.Int(1),
			},
			TrafficConfig: &cloudfront.ContinuousDeploymentPolicyTrafficConfigArgs{
				Type: pulumi.String("SingleHeader"),
				SingleHeaderConfig: &cloudfront.ContinuousDeploymentPolicyTrafficConfigSingleHeaderConfigArgs{
					Header: pulumi.String("aws-cf-cd-example"),
					Value:  pulumi.String("example"),
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.CloudFront.ContinuousDeploymentPolicy("example", new()
    {
        Enabled = true,
        StagingDistributionDnsNames = new Aws.CloudFront.Inputs.ContinuousDeploymentPolicyStagingDistributionDnsNamesArgs
        {
            Items = new[]
            {
                staging.DomainName,
            },
            Quantity = 1,
        },
        TrafficConfig = new Aws.CloudFront.Inputs.ContinuousDeploymentPolicyTrafficConfigArgs
        {
            Type = "SingleHeader",
            SingleHeaderConfig = new Aws.CloudFront.Inputs.ContinuousDeploymentPolicyTrafficConfigSingleHeaderConfigArgs
            {
                Header = "aws-cf-cd-example",
                Value = "example",
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.cloudfront.ContinuousDeploymentPolicy;
import com.pulumi.aws.cloudfront.ContinuousDeploymentPolicyArgs;
import com.pulumi.aws.cloudfront.inputs.ContinuousDeploymentPolicyStagingDistributionDnsNamesArgs;
import com.pulumi.aws.cloudfront.inputs.ContinuousDeploymentPolicyTrafficConfigArgs;
import com.pulumi.aws.cloudfront.inputs.ContinuousDeploymentPolicyTrafficConfigSingleHeaderConfigArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new ContinuousDeploymentPolicy("example", ContinuousDeploymentPolicyArgs.builder()
            .enabled(true)
            .stagingDistributionDnsNames(ContinuousDeploymentPolicyStagingDistributionDnsNamesArgs.builder()
                .items(staging.domainName())
                .quantity(1)
                .build())
            .trafficConfig(ContinuousDeploymentPolicyTrafficConfigArgs.builder()
                .type("SingleHeader")
                .singleHeaderConfig(ContinuousDeploymentPolicyTrafficConfigSingleHeaderConfigArgs.builder()
                    .header("aws-cf-cd-example")
                    .value("example")
                    .build())
                .build())
            .build());

    }
}
resources:
  example:
    type: aws:cloudfront:ContinuousDeploymentPolicy
    properties:
      enabled: true
      stagingDistributionDnsNames:
        items:
          - ${staging.domainName}
        quantity: 1
      trafficConfig:
        type: SingleHeader
        singleHeaderConfig:
          header: aws-cf-cd-example
          value: example

The singleHeaderConfig routes requests to staging only when they contain the specified header and value. The type property switches from “SingleWeight” to “SingleHeader”, changing the routing strategy entirely. This enables selective testing: add the header to your requests to hit staging, omit it to hit production.

Beyond these examples

These snippets focus on specific continuous deployment policy features: percentage-based traffic splitting, session stickiness, and header-based routing. They’re intentionally minimal rather than full deployment pipelines.

The examples require pre-existing infrastructure such as a staging CloudFront distribution (marked with staging: true) and a production CloudFront distribution that references the policy. They focus on configuring the policy rather than provisioning the distributions themselves.

To keep things focused, common deployment patterns are omitted, including:

  • Policy lifecycle management (enabling/disabling)
  • Multiple staging distributions
  • Traffic ramp-up strategies
  • Monitoring and rollback procedures

These omissions are intentional: the goal is to illustrate how each traffic routing feature is wired, not provide drop-in deployment pipelines. See the CloudFront Continuous Deployment Policy resource reference for all available configuration options.

Let's configure AWS CloudFront Continuous Deployment Policies

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Setup & Configuration
How do I set up continuous deployment with staging and production distributions?
Create a staging distribution with staging: true, then create a production distribution that references the continuous deployment policy via continuousDeploymentPolicyId. The policy connects the two distributions for traffic routing.
What's required to create a continuous deployment policy?
You must specify enabled (boolean), stagingDistributionDnsNames (the staging distribution’s domain), and trafficConfig (routing parameters).
Traffic Routing
What traffic routing options are available?
Two routing types: SingleWeight for percentage-based traffic splitting, and SingleHeader for header-based routing to staging.
How do I route a percentage of traffic to my staging distribution?
Use type: "SingleWeight" in trafficConfig with a weight value between 0 and 1 (e.g., 0.01 routes 1% of traffic to staging).
How do I test staging with specific request headers?
Use type: "SingleHeader" in trafficConfig with a custom header name and value. Requests matching that header/value pair route to staging.
Session Management
What does session stickiness do in continuous deployment?
Session stickiness keeps users on the same distribution version (staging or production) using cookies. Configure sessionStickinessConfig with idleTtl (idle timeout) and maximumTtl (maximum session duration) in seconds.

Using a different cloud?

Explore networking guides for other cloud providers: