The aws:elasticache/cluster:Cluster resource, part of the Pulumi AWS provider, provisions ElastiCache clusters: Memcached clusters for distributed caching, single-node Redis instances for data structures and persistence, or Redis read replicas in replication groups. This guide focuses on three capabilities: engine configuration, log delivery, and Outpost deployment.
ElastiCache clusters run in VPCs with security groups and subnets. Log delivery requires CloudWatch log groups or Kinesis Firehose streams. The examples are intentionally small. Combine them with your own VPC configuration, security groups, and monitoring infrastructure.
Create a Memcached cluster for distributed caching
Applications that cache session data or frequently accessed objects across multiple servers often use Memcached, which distributes data automatically across nodes.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const example = new aws.elasticache.Cluster("example", {
clusterId: "cluster-example",
engine: "memcached",
nodeType: "cache.m4.large",
numCacheNodes: 2,
parameterGroupName: "default.memcached1.4",
port: 11211,
});
import pulumi
import pulumi_aws as aws
example = aws.elasticache.Cluster("example",
cluster_id="cluster-example",
engine="memcached",
node_type="cache.m4.large",
num_cache_nodes=2,
parameter_group_name="default.memcached1.4",
port=11211)
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/elasticache"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := elasticache.NewCluster(ctx, "example", &elasticache.ClusterArgs{
ClusterId: pulumi.String("cluster-example"),
Engine: pulumi.String("memcached"),
NodeType: pulumi.String("cache.m4.large"),
NumCacheNodes: pulumi.Int(2),
ParameterGroupName: pulumi.String("default.memcached1.4"),
Port: pulumi.Int(11211),
})
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.ElastiCache.Cluster("example", new()
{
ClusterId = "cluster-example",
Engine = "memcached",
NodeType = "cache.m4.large",
NumCacheNodes = 2,
ParameterGroupName = "default.memcached1.4",
Port = 11211,
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.elasticache.Cluster;
import com.pulumi.aws.elasticache.ClusterArgs;
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 Cluster("example", ClusterArgs.builder()
.clusterId("cluster-example")
.engine("memcached")
.nodeType("cache.m4.large")
.numCacheNodes(2)
.parameterGroupName("default.memcached1.4")
.port(11211)
.build());
}
}
resources:
example:
type: aws:elasticache:Cluster
properties:
clusterId: cluster-example
engine: memcached
nodeType: cache.m4.large
numCacheNodes: 2
parameterGroupName: default.memcached1.4
port: 11211
When you create a Memcached cluster, data is distributed across all nodes automatically. The engine property selects Memcached, nodeType determines instance size, and numCacheNodes sets the node count (between 1 and 40). The parameterGroupName controls Memcached behavior like memory allocation and eviction policies.
Deploy a single-node Redis instance
Redis provides in-memory data structures with persistence, making it suitable for caching, session storage, and real-time analytics.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const example = new aws.elasticache.Cluster("example", {
clusterId: "cluster-example",
engine: "redis",
nodeType: "cache.m4.large",
numCacheNodes: 1,
parameterGroupName: "default.redis3.2",
engineVersion: "3.2.10",
port: 6379,
});
import pulumi
import pulumi_aws as aws
example = aws.elasticache.Cluster("example",
cluster_id="cluster-example",
engine="redis",
node_type="cache.m4.large",
num_cache_nodes=1,
parameter_group_name="default.redis3.2",
engine_version="3.2.10",
port=6379)
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/elasticache"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := elasticache.NewCluster(ctx, "example", &elasticache.ClusterArgs{
ClusterId: pulumi.String("cluster-example"),
Engine: pulumi.String("redis"),
NodeType: pulumi.String("cache.m4.large"),
NumCacheNodes: pulumi.Int(1),
ParameterGroupName: pulumi.String("default.redis3.2"),
EngineVersion: pulumi.String("3.2.10"),
Port: pulumi.Int(6379),
})
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.ElastiCache.Cluster("example", new()
{
ClusterId = "cluster-example",
Engine = "redis",
NodeType = "cache.m4.large",
NumCacheNodes = 1,
ParameterGroupName = "default.redis3.2",
EngineVersion = "3.2.10",
Port = 6379,
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.elasticache.Cluster;
import com.pulumi.aws.elasticache.ClusterArgs;
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 Cluster("example", ClusterArgs.builder()
.clusterId("cluster-example")
.engine("redis")
.nodeType("cache.m4.large")
.numCacheNodes(1)
.parameterGroupName("default.redis3.2")
.engineVersion("3.2.10")
.port(6379)
.build());
}
}
resources:
example:
type: aws:elasticache:Cluster
properties:
clusterId: cluster-example
engine: redis
nodeType: cache.m4.large
numCacheNodes: 1
parameterGroupName: default.redis3.2
engineVersion: 3.2.10
port: 6379
For standalone Redis, numCacheNodes must be 1. The engineVersion property controls which Redis version runs; ElastiCache returns the actual version in engineVersionActual since it may apply minor patches automatically. Redis supports richer data types than Memcached, including lists, sets, and sorted sets.
Stream Redis logs to CloudWatch and Kinesis
Production Redis deployments capture slow queries and engine events for troubleshooting and performance analysis.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const test = new aws.elasticache.Cluster("test", {
clusterId: "mycluster",
engine: "redis",
nodeType: "cache.t3.micro",
numCacheNodes: 1,
port: 6379,
applyImmediately: true,
logDeliveryConfigurations: [
{
destination: example.name,
destinationType: "cloudwatch-logs",
logFormat: "text",
logType: "slow-log",
},
{
destination: exampleAwsKinesisFirehoseDeliveryStream.name,
destinationType: "kinesis-firehose",
logFormat: "json",
logType: "engine-log",
},
],
});
import pulumi
import pulumi_aws as aws
test = aws.elasticache.Cluster("test",
cluster_id="mycluster",
engine="redis",
node_type="cache.t3.micro",
num_cache_nodes=1,
port=6379,
apply_immediately=True,
log_delivery_configurations=[
{
"destination": example["name"],
"destination_type": "cloudwatch-logs",
"log_format": "text",
"log_type": "slow-log",
},
{
"destination": example_aws_kinesis_firehose_delivery_stream["name"],
"destination_type": "kinesis-firehose",
"log_format": "json",
"log_type": "engine-log",
},
])
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/elasticache"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := elasticache.NewCluster(ctx, "test", &elasticache.ClusterArgs{
ClusterId: pulumi.String("mycluster"),
Engine: pulumi.String("redis"),
NodeType: pulumi.String("cache.t3.micro"),
NumCacheNodes: pulumi.Int(1),
Port: pulumi.Int(6379),
ApplyImmediately: pulumi.Bool(true),
LogDeliveryConfigurations: elasticache.ClusterLogDeliveryConfigurationArray{
&elasticache.ClusterLogDeliveryConfigurationArgs{
Destination: pulumi.Any(example.Name),
DestinationType: pulumi.String("cloudwatch-logs"),
LogFormat: pulumi.String("text"),
LogType: pulumi.String("slow-log"),
},
&elasticache.ClusterLogDeliveryConfigurationArgs{
Destination: pulumi.Any(exampleAwsKinesisFirehoseDeliveryStream.Name),
DestinationType: pulumi.String("kinesis-firehose"),
LogFormat: pulumi.String("json"),
LogType: pulumi.String("engine-log"),
},
},
})
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 test = new Aws.ElastiCache.Cluster("test", new()
{
ClusterId = "mycluster",
Engine = "redis",
NodeType = "cache.t3.micro",
NumCacheNodes = 1,
Port = 6379,
ApplyImmediately = true,
LogDeliveryConfigurations = new[]
{
new Aws.ElastiCache.Inputs.ClusterLogDeliveryConfigurationArgs
{
Destination = example.Name,
DestinationType = "cloudwatch-logs",
LogFormat = "text",
LogType = "slow-log",
},
new Aws.ElastiCache.Inputs.ClusterLogDeliveryConfigurationArgs
{
Destination = exampleAwsKinesisFirehoseDeliveryStream.Name,
DestinationType = "kinesis-firehose",
LogFormat = "json",
LogType = "engine-log",
},
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.elasticache.Cluster;
import com.pulumi.aws.elasticache.ClusterArgs;
import com.pulumi.aws.elasticache.inputs.ClusterLogDeliveryConfigurationArgs;
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 test = new Cluster("test", ClusterArgs.builder()
.clusterId("mycluster")
.engine("redis")
.nodeType("cache.t3.micro")
.numCacheNodes(1)
.port(6379)
.applyImmediately(true)
.logDeliveryConfigurations(
ClusterLogDeliveryConfigurationArgs.builder()
.destination(example.name())
.destinationType("cloudwatch-logs")
.logFormat("text")
.logType("slow-log")
.build(),
ClusterLogDeliveryConfigurationArgs.builder()
.destination(exampleAwsKinesisFirehoseDeliveryStream.name())
.destinationType("kinesis-firehose")
.logFormat("json")
.logType("engine-log")
.build())
.build());
}
}
resources:
test:
type: aws:elasticache:Cluster
properties:
clusterId: mycluster
engine: redis
nodeType: cache.t3.micro
numCacheNodes: 1
port: 6379
applyImmediately: true
logDeliveryConfigurations:
- destination: ${example.name}
destinationType: cloudwatch-logs
logFormat: text
logType: slow-log
- destination: ${exampleAwsKinesisFirehoseDeliveryStream.name}
destinationType: kinesis-firehose
logFormat: json
logType: engine-log
The logDeliveryConfigurations array defines where logs go and how they’re formatted. Each configuration specifies a destinationType (CloudWatch Logs or Kinesis Firehose), a destination name, a logType (slow-log or engine-log), and a logFormat (text or json). Slow logs capture queries exceeding execution time thresholds; engine logs record Redis operational events.
Deploy ElastiCache on AWS Outposts
Applications on AWS Outposts can run ElastiCache locally to minimize latency and keep data on-premises.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const example = aws.outposts.getOutposts({});
const exampleGetOutpost = example.then(example => aws.outposts.getOutpost({
id: example.ids?.[0],
}));
const exampleVpc = new aws.ec2.Vpc("example", {cidrBlock: "10.0.0.0/16"});
const exampleSubnet = new aws.ec2.Subnet("example", {
vpcId: exampleVpc.id,
cidrBlock: "10.0.1.0/24",
tags: {
Name: "my-subnet",
},
});
const exampleSubnetGroup = new aws.elasticache.SubnetGroup("example", {
name: "my-cache-subnet",
subnetIds: [exampleSubnet.id],
});
const exampleCluster = new aws.elasticache.Cluster("example", {
clusterId: "cluster-example",
outpostMode: "single-outpost",
preferredOutpostArn: exampleGetOutpost.then(exampleGetOutpost => exampleGetOutpost.arn),
engine: "memcached",
nodeType: "cache.r5.large",
numCacheNodes: 2,
parameterGroupName: "default.memcached1.4",
port: 11211,
subnetGroupName: exampleSubnetGroup.name,
});
import pulumi
import pulumi_aws as aws
example = aws.outposts.get_outposts()
example_get_outpost = aws.outposts.get_outpost(id=example.ids[0])
example_vpc = aws.ec2.Vpc("example", cidr_block="10.0.0.0/16")
example_subnet = aws.ec2.Subnet("example",
vpc_id=example_vpc.id,
cidr_block="10.0.1.0/24",
tags={
"Name": "my-subnet",
})
example_subnet_group = aws.elasticache.SubnetGroup("example",
name="my-cache-subnet",
subnet_ids=[example_subnet.id])
example_cluster = aws.elasticache.Cluster("example",
cluster_id="cluster-example",
outpost_mode="single-outpost",
preferred_outpost_arn=example_get_outpost.arn,
engine="memcached",
node_type="cache.r5.large",
num_cache_nodes=2,
parameter_group_name="default.memcached1.4",
port=11211,
subnet_group_name=example_subnet_group.name)
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/elasticache"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/outposts"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
example, err := outposts.GetOutposts(ctx, &outposts.GetOutpostsArgs{}, nil)
if err != nil {
return err
}
exampleGetOutpost, err := outposts.GetOutpost(ctx, &outposts.GetOutpostArgs{
Id: pulumi.StringRef(example.Ids[0]),
}, nil)
if err != nil {
return err
}
exampleVpc, err := ec2.NewVpc(ctx, "example", &ec2.VpcArgs{
CidrBlock: pulumi.String("10.0.0.0/16"),
})
if err != nil {
return err
}
exampleSubnet, err := ec2.NewSubnet(ctx, "example", &ec2.SubnetArgs{
VpcId: exampleVpc.ID(),
CidrBlock: pulumi.String("10.0.1.0/24"),
Tags: pulumi.StringMap{
"Name": pulumi.String("my-subnet"),
},
})
if err != nil {
return err
}
exampleSubnetGroup, err := elasticache.NewSubnetGroup(ctx, "example", &elasticache.SubnetGroupArgs{
Name: pulumi.String("my-cache-subnet"),
SubnetIds: pulumi.StringArray{
exampleSubnet.ID(),
},
})
if err != nil {
return err
}
_, err = elasticache.NewCluster(ctx, "example", &elasticache.ClusterArgs{
ClusterId: pulumi.String("cluster-example"),
OutpostMode: pulumi.String("single-outpost"),
PreferredOutpostArn: pulumi.String(exampleGetOutpost.Arn),
Engine: pulumi.String("memcached"),
NodeType: pulumi.String("cache.r5.large"),
NumCacheNodes: pulumi.Int(2),
ParameterGroupName: pulumi.String("default.memcached1.4"),
Port: pulumi.Int(11211),
SubnetGroupName: exampleSubnetGroup.Name,
})
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 = Aws.Outposts.GetOutposts.Invoke();
var exampleGetOutpost = Aws.Outposts.GetOutpost.Invoke(new()
{
Id = example.Apply(getOutpostsResult => getOutpostsResult.Ids[0]),
});
var exampleVpc = new Aws.Ec2.Vpc("example", new()
{
CidrBlock = "10.0.0.0/16",
});
var exampleSubnet = new Aws.Ec2.Subnet("example", new()
{
VpcId = exampleVpc.Id,
CidrBlock = "10.0.1.0/24",
Tags =
{
{ "Name", "my-subnet" },
},
});
var exampleSubnetGroup = new Aws.ElastiCache.SubnetGroup("example", new()
{
Name = "my-cache-subnet",
SubnetIds = new[]
{
exampleSubnet.Id,
},
});
var exampleCluster = new Aws.ElastiCache.Cluster("example", new()
{
ClusterId = "cluster-example",
OutpostMode = "single-outpost",
PreferredOutpostArn = exampleGetOutpost.Apply(getOutpostResult => getOutpostResult.Arn),
Engine = "memcached",
NodeType = "cache.r5.large",
NumCacheNodes = 2,
ParameterGroupName = "default.memcached1.4",
Port = 11211,
SubnetGroupName = exampleSubnetGroup.Name,
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.outposts.OutpostsFunctions;
import com.pulumi.aws.outposts.inputs.GetOutpostsArgs;
import com.pulumi.aws.outposts.inputs.GetOutpostArgs;
import com.pulumi.aws.ec2.Vpc;
import com.pulumi.aws.ec2.VpcArgs;
import com.pulumi.aws.ec2.Subnet;
import com.pulumi.aws.ec2.SubnetArgs;
import com.pulumi.aws.elasticache.SubnetGroup;
import com.pulumi.aws.elasticache.SubnetGroupArgs;
import com.pulumi.aws.elasticache.Cluster;
import com.pulumi.aws.elasticache.ClusterArgs;
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) {
final var example = OutpostsFunctions.getOutposts(GetOutpostsArgs.builder()
.build());
final var exampleGetOutpost = OutpostsFunctions.getOutpost(GetOutpostArgs.builder()
.id(example.ids()[0])
.build());
var exampleVpc = new Vpc("exampleVpc", VpcArgs.builder()
.cidrBlock("10.0.0.0/16")
.build());
var exampleSubnet = new Subnet("exampleSubnet", SubnetArgs.builder()
.vpcId(exampleVpc.id())
.cidrBlock("10.0.1.0/24")
.tags(Map.of("Name", "my-subnet"))
.build());
var exampleSubnetGroup = new SubnetGroup("exampleSubnetGroup", SubnetGroupArgs.builder()
.name("my-cache-subnet")
.subnetIds(exampleSubnet.id())
.build());
var exampleCluster = new Cluster("exampleCluster", ClusterArgs.builder()
.clusterId("cluster-example")
.outpostMode("single-outpost")
.preferredOutpostArn(exampleGetOutpost.arn())
.engine("memcached")
.nodeType("cache.r5.large")
.numCacheNodes(2)
.parameterGroupName("default.memcached1.4")
.port(11211)
.subnetGroupName(exampleSubnetGroup.name())
.build());
}
}
resources:
exampleVpc:
type: aws:ec2:Vpc
name: example
properties:
cidrBlock: 10.0.0.0/16
exampleSubnet:
type: aws:ec2:Subnet
name: example
properties:
vpcId: ${exampleVpc.id}
cidrBlock: 10.0.1.0/24
tags:
Name: my-subnet
exampleSubnetGroup:
type: aws:elasticache:SubnetGroup
name: example
properties:
name: my-cache-subnet
subnetIds:
- ${exampleSubnet.id}
exampleCluster:
type: aws:elasticache:Cluster
name: example
properties:
clusterId: cluster-example
outpostMode: single-outpost
preferredOutpostArn: ${exampleGetOutpost.arn}
engine: memcached
nodeType: cache.r5.large
numCacheNodes: 2
parameterGroupName: default.memcached1.4
port: 11211
subnetGroupName: ${exampleSubnetGroup.name}
variables:
example:
fn::invoke:
function: aws:outposts:getOutposts
arguments: {}
exampleGetOutpost:
fn::invoke:
function: aws:outposts:getOutpost
arguments:
id: ${example.ids[0]}
The outpostMode property must be “single-outpost” (AWS currently supports only this mode). The preferredOutpostArn identifies which Outpost hosts the cluster. You must also specify a subnetGroupName that references subnets configured for the Outpost.
Beyond these examples
These snippets focus on specific cluster-level features: Memcached and Redis engine configuration, log delivery to CloudWatch and Kinesis, and Outpost deployment. They’re intentionally minimal rather than full caching solutions.
The examples may reference pre-existing infrastructure such as VPC, subnets, and security groups (most use defaults), CloudWatch log groups and Kinesis Firehose streams for log delivery, and AWS Outposts infrastructure for Outpost deployment. They focus on configuring the cluster rather than provisioning everything around it.
To keep things focused, common cluster patterns are omitted, including:
- VPC networking and security group configuration
- Multi-AZ placement (azMode, preferredAvailabilityZones)
- Snapshot and backup configuration (snapshotRetentionLimit, snapshotWindow)
- Encryption in transit (transitEncryptionEnabled)
- Replication group integration for Redis clustering
- Maintenance windows and immediate application (applyImmediately)
These omissions are intentional: the goal is to illustrate how each cluster feature is wired, not provide drop-in caching modules. See the ElastiCache Cluster resource reference for all available configuration options.
Let's create AWS ElastiCache Clusters
Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.
Try Pulumi Cloud for FREEFrequently Asked Questions
Configuration & Updates
num_cache_nodes apply during the next maintenance window. Set apply_immediately to true to apply changes immediately, though this can cause brief downtime due to server reboots.cluster_id, engine, or port) apply immediately regardless of the apply_immediately setting. Only in-place updates respect the maintenance window.cluster_id, engine, availability_zone, port, replication_group_id, subnet_group_name, transit_encryption_enabled, network_type, preferred_outpost_arn, outpost_mode, snapshot_arns, and snapshot_name. For Memcached, node_type is also immutable.Engine & Versioning
memcached, redis, and valkey. The engine choice is immutable after creation.7.2). For Redis 6, you can specify 6.2 or use 6.x for the latest 6.x version. For earlier versions, specify the full version (e.g., 5.0.6). The actual version used is returned in engine_version_actual.auto_minor_version_upgrade defaults to true, applying minor version upgrades during maintenance windows. This setting only applies to Redis.Scaling & High Availability
num_cache_nodes, the highest numbered nodes are removed.availability_zone. For multiple AZs, use preferred_availability_zones (list must match num_cache_nodes count). For Memcached, set az_mode to cross-az (requires num_cache_nodes > 1).preferred_availability_zones to migrate existing nodes will show a perpetual difference. Use this property only during initial creation.replication_group_id to the ID of an existing replication group. The cluster becomes a read replica and inherits settings from the replication group.Networking & Security
network_type to ipv6 or dual_stack.Snapshots & Backups
snapshot_retention_limit isn’t supported on cache.t1.micro nodes. Use a different node type if snapshot retention is required.