The aws:opensearch/domain:Domain resource, part of the Pulumi AWS provider, provisions an Amazon OpenSearch Service domain: its engine version, cluster topology, networking, and access controls. This guide focuses on three capabilities: engine version and instance type selection, access policies and fine-grained access control, and VPC networking and CloudWatch log publishing.
OpenSearch domains may reference VPC infrastructure, CloudWatch log groups, IAM policies, and service-linked roles that must exist separately. The examples are intentionally small. Combine them with your own networking, logging, and authentication infrastructure.
Create a domain with engine version and instance type
Most deployments start by choosing an engine version and instance type that matches your search workload and data volume.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const example = new aws.opensearch.Domain("example", {
domainName: "example",
engineVersion: "Elasticsearch_7.10",
clusterConfig: {
instanceType: "r4.large.search",
},
tags: {
Domain: "TestDomain",
},
});
import pulumi
import pulumi_aws as aws
example = aws.opensearch.Domain("example",
domain_name="example",
engine_version="Elasticsearch_7.10",
cluster_config={
"instance_type": "r4.large.search",
},
tags={
"Domain": "TestDomain",
})
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/opensearch"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := opensearch.NewDomain(ctx, "example", &opensearch.DomainArgs{
DomainName: pulumi.String("example"),
EngineVersion: pulumi.String("Elasticsearch_7.10"),
ClusterConfig: &opensearch.DomainClusterConfigArgs{
InstanceType: pulumi.String("r4.large.search"),
},
Tags: pulumi.StringMap{
"Domain": pulumi.String("TestDomain"),
},
})
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.OpenSearch.Domain("example", new()
{
DomainName = "example",
EngineVersion = "Elasticsearch_7.10",
ClusterConfig = new Aws.OpenSearch.Inputs.DomainClusterConfigArgs
{
InstanceType = "r4.large.search",
},
Tags =
{
{ "Domain", "TestDomain" },
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.opensearch.Domain;
import com.pulumi.aws.opensearch.DomainArgs;
import com.pulumi.aws.opensearch.inputs.DomainClusterConfigArgs;
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 Domain("example", DomainArgs.builder()
.domainName("example")
.engineVersion("Elasticsearch_7.10")
.clusterConfig(DomainClusterConfigArgs.builder()
.instanceType("r4.large.search")
.build())
.tags(Map.of("Domain", "TestDomain"))
.build());
}
}
resources:
example:
type: aws:opensearch:Domain
properties:
domainName: example
engineVersion: Elasticsearch_7.10
clusterConfig:
instanceType: r4.large.search
tags:
Domain: TestDomain
The engineVersion property specifies either OpenSearch or Elasticsearch (up to 7.10). The clusterConfig block sets the instance type; note that OpenSearch instance types end in .search rather than .elasticsearch. The domainName must be globally unique within your AWS account and region.
Control access with IP-based policies
OpenSearch domains are publicly accessible by default. Teams restrict access to specific IP ranges or AWS principals using IAM policy documents.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const config = new pulumi.Config();
const domain = config.get("domain") || "tf-test";
const current = aws.getRegion({});
const currentGetCallerIdentity = aws.getCallerIdentity({});
const example = Promise.all([current, currentGetCallerIdentity]).then(([current, currentGetCallerIdentity]) => aws.iam.getPolicyDocument({
statements: [{
effect: "Allow",
principals: [{
type: "*",
identifiers: ["*"],
}],
actions: ["es:*"],
resources: [`arn:aws:es:${current.region}:${currentGetCallerIdentity.accountId}:domain/${domain}/*`],
conditions: [{
test: "IpAddress",
variable: "aws:SourceIp",
values: ["66.193.100.22/32"],
}],
}],
}));
const exampleDomain = new aws.opensearch.Domain("example", {
domainName: domain,
accessPolicies: example.then(example => example.json),
});
import pulumi
import pulumi_aws as aws
config = pulumi.Config()
domain = config.get("domain")
if domain is None:
domain = "tf-test"
current = aws.get_region()
current_get_caller_identity = aws.get_caller_identity()
example = aws.iam.get_policy_document(statements=[{
"effect": "Allow",
"principals": [{
"type": "*",
"identifiers": ["*"],
}],
"actions": ["es:*"],
"resources": [f"arn:aws:es:{current.region}:{current_get_caller_identity.account_id}:domain/{domain}/*"],
"conditions": [{
"test": "IpAddress",
"variable": "aws:SourceIp",
"values": ["66.193.100.22/32"],
}],
}])
example_domain = aws.opensearch.Domain("example",
domain_name=domain,
access_policies=example.json)
package main
import (
"fmt"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/iam"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/opensearch"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi/config"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
cfg := config.New(ctx, "")
domain := "tf-test"
if param := cfg.Get("domain"); param != "" {
domain = param
}
current, err := aws.GetRegion(ctx, &aws.GetRegionArgs{}, nil)
if err != nil {
return err
}
currentGetCallerIdentity, err := aws.GetCallerIdentity(ctx, &aws.GetCallerIdentityArgs{}, nil)
if err != nil {
return err
}
example, err := iam.GetPolicyDocument(ctx, &iam.GetPolicyDocumentArgs{
Statements: []iam.GetPolicyDocumentStatement{
{
Effect: pulumi.StringRef("Allow"),
Principals: []iam.GetPolicyDocumentStatementPrincipal{
{
Type: "*",
Identifiers: []string{
"*",
},
},
},
Actions: []string{
"es:*",
},
Resources: []string{
fmt.Sprintf("arn:aws:es:%v:%v:domain/%v/*", current.Region, currentGetCallerIdentity.AccountId, domain),
},
Conditions: []iam.GetPolicyDocumentStatementCondition{
{
Test: "IpAddress",
Variable: "aws:SourceIp",
Values: []string{
"66.193.100.22/32",
},
},
},
},
},
}, nil)
if err != nil {
return err
}
_, err = opensearch.NewDomain(ctx, "example", &opensearch.DomainArgs{
DomainName: pulumi.String(domain),
AccessPolicies: pulumi.String(example.Json),
})
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 config = new Config();
var domain = config.Get("domain") ?? "tf-test";
var current = Aws.GetRegion.Invoke();
var currentGetCallerIdentity = Aws.GetCallerIdentity.Invoke();
var example = Aws.Iam.GetPolicyDocument.Invoke(new()
{
Statements = new[]
{
new Aws.Iam.Inputs.GetPolicyDocumentStatementInputArgs
{
Effect = "Allow",
Principals = new[]
{
new Aws.Iam.Inputs.GetPolicyDocumentStatementPrincipalInputArgs
{
Type = "*",
Identifiers = new[]
{
"*",
},
},
},
Actions = new[]
{
"es:*",
},
Resources = new[]
{
$"arn:aws:es:{current.Apply(getRegionResult => getRegionResult.Region)}:{currentGetCallerIdentity.Apply(getCallerIdentityResult => getCallerIdentityResult.AccountId)}:domain/{domain}/*",
},
Conditions = new[]
{
new Aws.Iam.Inputs.GetPolicyDocumentStatementConditionInputArgs
{
Test = "IpAddress",
Variable = "aws:SourceIp",
Values = new[]
{
"66.193.100.22/32",
},
},
},
},
},
});
var exampleDomain = new Aws.OpenSearch.Domain("example", new()
{
DomainName = domain,
AccessPolicies = example.Apply(getPolicyDocumentResult => getPolicyDocumentResult.Json),
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.AwsFunctions;
import com.pulumi.aws.inputs.GetRegionArgs;
import com.pulumi.aws.inputs.GetCallerIdentityArgs;
import com.pulumi.aws.iam.IamFunctions;
import com.pulumi.aws.iam.inputs.GetPolicyDocumentArgs;
import com.pulumi.aws.opensearch.Domain;
import com.pulumi.aws.opensearch.DomainArgs;
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 config = ctx.config();
final var domain = config.get("domain").orElse("tf-test");
final var current = AwsFunctions.getRegion(GetRegionArgs.builder()
.build());
final var currentGetCallerIdentity = AwsFunctions.getCallerIdentity(GetCallerIdentityArgs.builder()
.build());
final var example = IamFunctions.getPolicyDocument(GetPolicyDocumentArgs.builder()
.statements(GetPolicyDocumentStatementArgs.builder()
.effect("Allow")
.principals(GetPolicyDocumentStatementPrincipalArgs.builder()
.type("*")
.identifiers("*")
.build())
.actions("es:*")
.resources(String.format("arn:aws:es:%s:%s:domain/%s/*", current.region(),currentGetCallerIdentity.accountId(),domain))
.conditions(GetPolicyDocumentStatementConditionArgs.builder()
.test("IpAddress")
.variable("aws:SourceIp")
.values("66.193.100.22/32")
.build())
.build())
.build());
var exampleDomain = new Domain("exampleDomain", DomainArgs.builder()
.domainName(domain)
.accessPolicies(example.json())
.build());
}
}
configuration:
domain:
type: string
default: tf-test
resources:
exampleDomain:
type: aws:opensearch:Domain
name: example
properties:
domainName: ${domain}
accessPolicies: ${example.json}
variables:
current:
fn::invoke:
function: aws:getRegion
arguments: {}
currentGetCallerIdentity:
fn::invoke:
function: aws:getCallerIdentity
arguments: {}
example:
fn::invoke:
function: aws:iam:getPolicyDocument
arguments:
statements:
- effect: Allow
principals:
- type: '*'
identifiers:
- '*'
actions:
- es:*
resources:
- arn:aws:es:${current.region}:${currentGetCallerIdentity.accountId}:domain/${domain}/*
conditions:
- test: IpAddress
variable: aws:SourceIp
values:
- 66.193.100.22/32
The accessPolicies property accepts an IAM policy document that controls who can access the domain. This example uses IP-based conditions to allow access only from a specific CIDR block. The policy applies to the domain’s public endpoint; VPC-based domains use security groups instead.
Publish slow query logs to CloudWatch
OpenSearch streams index slow logs, search slow logs, and application logs to CloudWatch for centralized monitoring.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const exampleLogGroup = new aws.cloudwatch.LogGroup("example", {name: "example"});
const example = aws.iam.getPolicyDocument({
statements: [{
effect: "Allow",
principals: [{
type: "Service",
identifiers: ["es.amazonaws.com"],
}],
actions: [
"logs:PutLogEvents",
"logs:PutLogEventsBatch",
"logs:CreateLogStream",
],
resources: ["arn:aws:logs:*"],
}],
});
const exampleLogResourcePolicy = new aws.cloudwatch.LogResourcePolicy("example", {
policyName: "example",
policyDocument: example.then(example => example.json),
});
const exampleDomain = new aws.opensearch.Domain("example", {logPublishingOptions: [{
cloudwatchLogGroupArn: exampleLogGroup.arn,
logType: "INDEX_SLOW_LOGS",
}]});
import pulumi
import pulumi_aws as aws
example_log_group = aws.cloudwatch.LogGroup("example", name="example")
example = aws.iam.get_policy_document(statements=[{
"effect": "Allow",
"principals": [{
"type": "Service",
"identifiers": ["es.amazonaws.com"],
}],
"actions": [
"logs:PutLogEvents",
"logs:PutLogEventsBatch",
"logs:CreateLogStream",
],
"resources": ["arn:aws:logs:*"],
}])
example_log_resource_policy = aws.cloudwatch.LogResourcePolicy("example",
policy_name="example",
policy_document=example.json)
example_domain = aws.opensearch.Domain("example", log_publishing_options=[{
"cloudwatch_log_group_arn": example_log_group.arn,
"log_type": "INDEX_SLOW_LOGS",
}])
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/cloudwatch"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/iam"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/opensearch"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
exampleLogGroup, err := cloudwatch.NewLogGroup(ctx, "example", &cloudwatch.LogGroupArgs{
Name: pulumi.String("example"),
})
if err != nil {
return err
}
example, err := iam.GetPolicyDocument(ctx, &iam.GetPolicyDocumentArgs{
Statements: []iam.GetPolicyDocumentStatement{
{
Effect: pulumi.StringRef("Allow"),
Principals: []iam.GetPolicyDocumentStatementPrincipal{
{
Type: "Service",
Identifiers: []string{
"es.amazonaws.com",
},
},
},
Actions: []string{
"logs:PutLogEvents",
"logs:PutLogEventsBatch",
"logs:CreateLogStream",
},
Resources: []string{
"arn:aws:logs:*",
},
},
},
}, nil)
if err != nil {
return err
}
_, err = cloudwatch.NewLogResourcePolicy(ctx, "example", &cloudwatch.LogResourcePolicyArgs{
PolicyName: pulumi.String("example"),
PolicyDocument: pulumi.String(example.Json),
})
if err != nil {
return err
}
_, err = opensearch.NewDomain(ctx, "example", &opensearch.DomainArgs{
LogPublishingOptions: opensearch.DomainLogPublishingOptionArray{
&opensearch.DomainLogPublishingOptionArgs{
CloudwatchLogGroupArn: exampleLogGroup.Arn,
LogType: pulumi.String("INDEX_SLOW_LOGS"),
},
},
})
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 exampleLogGroup = new Aws.CloudWatch.LogGroup("example", new()
{
Name = "example",
});
var example = Aws.Iam.GetPolicyDocument.Invoke(new()
{
Statements = new[]
{
new Aws.Iam.Inputs.GetPolicyDocumentStatementInputArgs
{
Effect = "Allow",
Principals = new[]
{
new Aws.Iam.Inputs.GetPolicyDocumentStatementPrincipalInputArgs
{
Type = "Service",
Identifiers = new[]
{
"es.amazonaws.com",
},
},
},
Actions = new[]
{
"logs:PutLogEvents",
"logs:PutLogEventsBatch",
"logs:CreateLogStream",
},
Resources = new[]
{
"arn:aws:logs:*",
},
},
},
});
var exampleLogResourcePolicy = new Aws.CloudWatch.LogResourcePolicy("example", new()
{
PolicyName = "example",
PolicyDocument = example.Apply(getPolicyDocumentResult => getPolicyDocumentResult.Json),
});
var exampleDomain = new Aws.OpenSearch.Domain("example", new()
{
LogPublishingOptions = new[]
{
new Aws.OpenSearch.Inputs.DomainLogPublishingOptionArgs
{
CloudwatchLogGroupArn = exampleLogGroup.Arn,
LogType = "INDEX_SLOW_LOGS",
},
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.cloudwatch.LogGroup;
import com.pulumi.aws.cloudwatch.LogGroupArgs;
import com.pulumi.aws.iam.IamFunctions;
import com.pulumi.aws.iam.inputs.GetPolicyDocumentArgs;
import com.pulumi.aws.cloudwatch.LogResourcePolicy;
import com.pulumi.aws.cloudwatch.LogResourcePolicyArgs;
import com.pulumi.aws.opensearch.Domain;
import com.pulumi.aws.opensearch.DomainArgs;
import com.pulumi.aws.opensearch.inputs.DomainLogPublishingOptionArgs;
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 exampleLogGroup = new LogGroup("exampleLogGroup", LogGroupArgs.builder()
.name("example")
.build());
final var example = IamFunctions.getPolicyDocument(GetPolicyDocumentArgs.builder()
.statements(GetPolicyDocumentStatementArgs.builder()
.effect("Allow")
.principals(GetPolicyDocumentStatementPrincipalArgs.builder()
.type("Service")
.identifiers("es.amazonaws.com")
.build())
.actions(
"logs:PutLogEvents",
"logs:PutLogEventsBatch",
"logs:CreateLogStream")
.resources("arn:aws:logs:*")
.build())
.build());
var exampleLogResourcePolicy = new LogResourcePolicy("exampleLogResourcePolicy", LogResourcePolicyArgs.builder()
.policyName("example")
.policyDocument(example.json())
.build());
var exampleDomain = new Domain("exampleDomain", DomainArgs.builder()
.logPublishingOptions(DomainLogPublishingOptionArgs.builder()
.cloudwatchLogGroupArn(exampleLogGroup.arn())
.logType("INDEX_SLOW_LOGS")
.build())
.build());
}
}
resources:
exampleLogGroup:
type: aws:cloudwatch:LogGroup
name: example
properties:
name: example
exampleLogResourcePolicy:
type: aws:cloudwatch:LogResourcePolicy
name: example
properties:
policyName: example
policyDocument: ${example.json}
exampleDomain:
type: aws:opensearch:Domain
name: example
properties:
logPublishingOptions:
- cloudwatchLogGroupArn: ${exampleLogGroup.arn}
logType: INDEX_SLOW_LOGS
variables:
example:
fn::invoke:
function: aws:iam:getPolicyDocument
arguments:
statements:
- effect: Allow
principals:
- type: Service
identifiers:
- es.amazonaws.com
actions:
- logs:PutLogEvents
- logs:PutLogEventsBatch
- logs:CreateLogStream
resources:
- arn:aws:logs:*
The logPublishingOptions array enables log streaming to CloudWatch. Each entry specifies a logType (INDEX_SLOW_LOGS, SEARCH_SLOW_LOGS, or ES_APPLICATION_LOGS) and a cloudwatchLogGroupArn. OpenSearch requires a CloudWatch Logs resource policy that grants the service permission to write logs.
Deploy into a VPC for private access
Production deployments often place domains inside VPCs to restrict access to internal networks.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const config = new pulumi.Config();
const vpc = config.requireObject<any>("vpc");
const domain = config.get("domain") || "tf-test";
const example = aws.ec2.getVpc({
tags: {
Name: vpc,
},
});
const exampleGetSubnets = example.then(example => aws.ec2.getSubnets({
filters: [{
name: "vpc-id",
values: [example.id],
}],
tags: {
Tier: "private",
},
}));
const current = aws.getRegion({});
const currentGetCallerIdentity = aws.getCallerIdentity({});
const exampleSecurityGroup = new aws.ec2.SecurityGroup("example", {
name: `${vpc}-opensearch-${domain}`,
description: "Managed by Pulumi",
vpcId: example.then(example => example.id),
ingress: [{
fromPort: 443,
toPort: 443,
protocol: "tcp",
cidrBlocks: [example.then(example => example.cidrBlock)],
}],
});
const exampleServiceLinkedRole = new aws.iam.ServiceLinkedRole("example", {awsServiceName: "opensearchservice.amazonaws.com"});
const exampleGetPolicyDocument = Promise.all([current, currentGetCallerIdentity]).then(([current, currentGetCallerIdentity]) => aws.iam.getPolicyDocument({
statements: [{
effect: "Allow",
principals: [{
type: "*",
identifiers: ["*"],
}],
actions: ["es:*"],
resources: [`arn:aws:es:${current.region}:${currentGetCallerIdentity.accountId}:domain/${domain}/*`],
}],
}));
const exampleDomain = new aws.opensearch.Domain("example", {
domainName: domain,
engineVersion: "OpenSearch_1.0",
clusterConfig: {
instanceType: "m4.large.search",
zoneAwarenessEnabled: true,
},
vpcOptions: {
subnetIds: [
exampleGetSubnets.then(exampleGetSubnets => exampleGetSubnets.ids?.[0]),
exampleGetSubnets.then(exampleGetSubnets => exampleGetSubnets.ids?.[1]),
],
securityGroupIds: [exampleSecurityGroup.id],
},
advancedOptions: {
"rest.action.multi.allow_explicit_index": "true",
},
accessPolicies: exampleGetPolicyDocument.then(exampleGetPolicyDocument => exampleGetPolicyDocument.json),
tags: {
Domain: "TestDomain",
},
}, {
dependsOn: [exampleServiceLinkedRole],
});
import pulumi
import pulumi_aws as aws
config = pulumi.Config()
vpc = config.require_object("vpc")
domain = config.get("domain")
if domain is None:
domain = "tf-test"
example = aws.ec2.get_vpc(tags={
"Name": vpc,
})
example_get_subnets = aws.ec2.get_subnets(filters=[{
"name": "vpc-id",
"values": [example.id],
}],
tags={
"Tier": "private",
})
current = aws.get_region()
current_get_caller_identity = aws.get_caller_identity()
example_security_group = aws.ec2.SecurityGroup("example",
name=f"{vpc}-opensearch-{domain}",
description="Managed by Pulumi",
vpc_id=example.id,
ingress=[{
"from_port": 443,
"to_port": 443,
"protocol": "tcp",
"cidr_blocks": [example.cidr_block],
}])
example_service_linked_role = aws.iam.ServiceLinkedRole("example", aws_service_name="opensearchservice.amazonaws.com")
example_get_policy_document = aws.iam.get_policy_document(statements=[{
"effect": "Allow",
"principals": [{
"type": "*",
"identifiers": ["*"],
}],
"actions": ["es:*"],
"resources": [f"arn:aws:es:{current.region}:{current_get_caller_identity.account_id}:domain/{domain}/*"],
}])
example_domain = aws.opensearch.Domain("example",
domain_name=domain,
engine_version="OpenSearch_1.0",
cluster_config={
"instance_type": "m4.large.search",
"zone_awareness_enabled": True,
},
vpc_options={
"subnet_ids": [
example_get_subnets.ids[0],
example_get_subnets.ids[1],
],
"security_group_ids": [example_security_group.id],
},
advanced_options={
"rest.action.multi.allow_explicit_index": "true",
},
access_policies=example_get_policy_document.json,
tags={
"Domain": "TestDomain",
},
opts = pulumi.ResourceOptions(depends_on=[example_service_linked_role]))
package main
import (
"fmt"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/iam"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/opensearch"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi/config"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
cfg := config.New(ctx, "")
vpc := cfg.RequireObject("vpc")
domain := "tf-test";
if param := cfg.Get("domain"); param != ""{
domain = param
}
example, err := ec2.LookupVpc(ctx, &ec2.LookupVpcArgs{
Tags: pulumi.StringMap{
"Name": vpc,
},
}, nil);
if err != nil {
return err
}
exampleGetSubnets, err := ec2.GetSubnets(ctx, &ec2.GetSubnetsArgs{
Filters: []ec2.GetSubnetsFilter{
{
Name: "vpc-id",
Values: interface{}{
example.Id,
},
},
},
Tags: map[string]interface{}{
"Tier": "private",
},
}, nil);
if err != nil {
return err
}
current, err := aws.GetRegion(ctx, &aws.GetRegionArgs{
}, nil);
if err != nil {
return err
}
currentGetCallerIdentity, err := aws.GetCallerIdentity(ctx, &aws.GetCallerIdentityArgs{
}, nil);
if err != nil {
return err
}
exampleSecurityGroup, err := ec2.NewSecurityGroup(ctx, "example", &ec2.SecurityGroupArgs{
Name: pulumi.Sprintf("%v-opensearch-%v", vpc, domain),
Description: pulumi.String("Managed by Pulumi"),
VpcId: pulumi.String(example.Id),
Ingress: ec2.SecurityGroupIngressArray{
&ec2.SecurityGroupIngressArgs{
FromPort: pulumi.Int(443),
ToPort: pulumi.Int(443),
Protocol: pulumi.String("tcp"),
CidrBlocks: pulumi.StringArray{
pulumi.String(example.CidrBlock),
},
},
},
})
if err != nil {
return err
}
exampleServiceLinkedRole, err := iam.NewServiceLinkedRole(ctx, "example", &iam.ServiceLinkedRoleArgs{
AwsServiceName: pulumi.String("opensearchservice.amazonaws.com"),
})
if err != nil {
return err
}
exampleGetPolicyDocument, err := iam.GetPolicyDocument(ctx, &iam.GetPolicyDocumentArgs{
Statements: []iam.GetPolicyDocumentStatement{
{
Effect: pulumi.StringRef("Allow"),
Principals: []iam.GetPolicyDocumentStatementPrincipal{
{
Type: "*",
Identifiers: []string{
"*",
},
},
},
Actions: []string{
"es:*",
},
Resources: []string{
fmt.Sprintf("arn:aws:es:%v:%v:domain/%v/*", current.Region, currentGetCallerIdentity.AccountId, domain),
},
},
},
}, nil);
if err != nil {
return err
}
_, err = opensearch.NewDomain(ctx, "example", &opensearch.DomainArgs{
DomainName: pulumi.String(domain),
EngineVersion: pulumi.String("OpenSearch_1.0"),
ClusterConfig: &opensearch.DomainClusterConfigArgs{
InstanceType: pulumi.String("m4.large.search"),
ZoneAwarenessEnabled: pulumi.Bool(true),
},
VpcOptions: &opensearch.DomainVpcOptionsArgs{
SubnetIds: pulumi.StringArray{
pulumi.String(exampleGetSubnets.Ids[0]),
pulumi.String(exampleGetSubnets.Ids[1]),
},
SecurityGroupIds: pulumi.StringArray{
exampleSecurityGroup.ID(),
},
},
AdvancedOptions: pulumi.StringMap{
"rest.action.multi.allow_explicit_index": pulumi.String("true"),
},
AccessPolicies: pulumi.String(exampleGetPolicyDocument.Json),
Tags: pulumi.StringMap{
"Domain": pulumi.String("TestDomain"),
},
}, pulumi.DependsOn([]pulumi.Resource{
exampleServiceLinkedRole,
}))
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 config = new Config();
var vpc = config.RequireObject<dynamic>("vpc");
var domain = config.Get("domain") ?? "tf-test";
var example = Aws.Ec2.GetVpc.Invoke(new()
{
Tags =
{
{ "Name", vpc },
},
});
var exampleGetSubnets = Aws.Ec2.GetSubnets.Invoke(new()
{
Filters = new[]
{
new Aws.Ec2.Inputs.GetSubnetsFilterInputArgs
{
Name = "vpc-id",
Values = new[]
{
example.Apply(getVpcResult => getVpcResult.Id),
},
},
},
Tags =
{
{ "Tier", "private" },
},
});
var current = Aws.GetRegion.Invoke();
var currentGetCallerIdentity = Aws.GetCallerIdentity.Invoke();
var exampleSecurityGroup = new Aws.Ec2.SecurityGroup("example", new()
{
Name = $"{vpc}-opensearch-{domain}",
Description = "Managed by Pulumi",
VpcId = example.Apply(getVpcResult => getVpcResult.Id),
Ingress = new[]
{
new Aws.Ec2.Inputs.SecurityGroupIngressArgs
{
FromPort = 443,
ToPort = 443,
Protocol = "tcp",
CidrBlocks = new[]
{
example.Apply(getVpcResult => getVpcResult.CidrBlock),
},
},
},
});
var exampleServiceLinkedRole = new Aws.Iam.ServiceLinkedRole("example", new()
{
AwsServiceName = "opensearchservice.amazonaws.com",
});
var exampleGetPolicyDocument = Aws.Iam.GetPolicyDocument.Invoke(new()
{
Statements = new[]
{
new Aws.Iam.Inputs.GetPolicyDocumentStatementInputArgs
{
Effect = "Allow",
Principals = new[]
{
new Aws.Iam.Inputs.GetPolicyDocumentStatementPrincipalInputArgs
{
Type = "*",
Identifiers = new[]
{
"*",
},
},
},
Actions = new[]
{
"es:*",
},
Resources = new[]
{
$"arn:aws:es:{current.Apply(getRegionResult => getRegionResult.Region)}:{currentGetCallerIdentity.Apply(getCallerIdentityResult => getCallerIdentityResult.AccountId)}:domain/{domain}/*",
},
},
},
});
var exampleDomain = new Aws.OpenSearch.Domain("example", new()
{
DomainName = domain,
EngineVersion = "OpenSearch_1.0",
ClusterConfig = new Aws.OpenSearch.Inputs.DomainClusterConfigArgs
{
InstanceType = "m4.large.search",
ZoneAwarenessEnabled = true,
},
VpcOptions = new Aws.OpenSearch.Inputs.DomainVpcOptionsArgs
{
SubnetIds = new[]
{
exampleGetSubnets.Apply(getSubnetsResult => getSubnetsResult.Ids[0]),
exampleGetSubnets.Apply(getSubnetsResult => getSubnetsResult.Ids[1]),
},
SecurityGroupIds = new[]
{
exampleSecurityGroup.Id,
},
},
AdvancedOptions =
{
{ "rest.action.multi.allow_explicit_index", "true" },
},
AccessPolicies = exampleGetPolicyDocument.Apply(getPolicyDocumentResult => getPolicyDocumentResult.Json),
Tags =
{
{ "Domain", "TestDomain" },
},
}, new CustomResourceOptions
{
DependsOn =
{
exampleServiceLinkedRole,
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.Ec2Functions;
import com.pulumi.aws.ec2.inputs.GetVpcArgs;
import com.pulumi.aws.ec2.inputs.GetSubnetsArgs;
import com.pulumi.aws.AwsFunctions;
import com.pulumi.aws.inputs.GetRegionArgs;
import com.pulumi.aws.inputs.GetCallerIdentityArgs;
import com.pulumi.aws.ec2.SecurityGroup;
import com.pulumi.aws.ec2.SecurityGroupArgs;
import com.pulumi.aws.ec2.inputs.SecurityGroupIngressArgs;
import com.pulumi.aws.iam.ServiceLinkedRole;
import com.pulumi.aws.iam.ServiceLinkedRoleArgs;
import com.pulumi.aws.iam.IamFunctions;
import com.pulumi.aws.iam.inputs.GetPolicyDocumentArgs;
import com.pulumi.aws.opensearch.Domain;
import com.pulumi.aws.opensearch.DomainArgs;
import com.pulumi.aws.opensearch.inputs.DomainClusterConfigArgs;
import com.pulumi.aws.opensearch.inputs.DomainVpcOptionsArgs;
import com.pulumi.resources.CustomResourceOptions;
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 config = ctx.config();
final var vpc = config.require("vpc");
final var domain = config.get("domain").orElse("tf-test");
final var example = Ec2Functions.getVpc(GetVpcArgs.builder()
.tags(Map.of("Name", vpc))
.build());
final var exampleGetSubnets = Ec2Functions.getSubnets(GetSubnetsArgs.builder()
.filters(GetSubnetsFilterArgs.builder()
.name("vpc-id")
.values(example.id())
.build())
.tags(Map.of("Tier", "private"))
.build());
final var current = AwsFunctions.getRegion(GetRegionArgs.builder()
.build());
final var currentGetCallerIdentity = AwsFunctions.getCallerIdentity(GetCallerIdentityArgs.builder()
.build());
var exampleSecurityGroup = new SecurityGroup("exampleSecurityGroup", SecurityGroupArgs.builder()
.name(String.format("%s-opensearch-%s", vpc,domain))
.description("Managed by Pulumi")
.vpcId(example.id())
.ingress(SecurityGroupIngressArgs.builder()
.fromPort(443)
.toPort(443)
.protocol("tcp")
.cidrBlocks(example.cidrBlock())
.build())
.build());
var exampleServiceLinkedRole = new ServiceLinkedRole("exampleServiceLinkedRole", ServiceLinkedRoleArgs.builder()
.awsServiceName("opensearchservice.amazonaws.com")
.build());
final var exampleGetPolicyDocument = IamFunctions.getPolicyDocument(GetPolicyDocumentArgs.builder()
.statements(GetPolicyDocumentStatementArgs.builder()
.effect("Allow")
.principals(GetPolicyDocumentStatementPrincipalArgs.builder()
.type("*")
.identifiers("*")
.build())
.actions("es:*")
.resources(String.format("arn:aws:es:%s:%s:domain/%s/*", current.region(),currentGetCallerIdentity.accountId(),domain))
.build())
.build());
var exampleDomain = new Domain("exampleDomain", DomainArgs.builder()
.domainName(domain)
.engineVersion("OpenSearch_1.0")
.clusterConfig(DomainClusterConfigArgs.builder()
.instanceType("m4.large.search")
.zoneAwarenessEnabled(true)
.build())
.vpcOptions(DomainVpcOptionsArgs.builder()
.subnetIds(
exampleGetSubnets.ids()[0],
exampleGetSubnets.ids()[1])
.securityGroupIds(exampleSecurityGroup.id())
.build())
.advancedOptions(Map.of("rest.action.multi.allow_explicit_index", "true"))
.accessPolicies(exampleGetPolicyDocument.json())
.tags(Map.of("Domain", "TestDomain"))
.build(), CustomResourceOptions.builder()
.dependsOn(exampleServiceLinkedRole)
.build());
}
}
configuration:
vpc:
type: dynamic
domain:
type: string
default: tf-test
resources:
exampleSecurityGroup:
type: aws:ec2:SecurityGroup
name: example
properties:
name: ${vpc}-opensearch-${domain}
description: Managed by Pulumi
vpcId: ${example.id}
ingress:
- fromPort: 443
toPort: 443
protocol: tcp
cidrBlocks:
- ${example.cidrBlock}
exampleServiceLinkedRole:
type: aws:iam:ServiceLinkedRole
name: example
properties:
awsServiceName: opensearchservice.amazonaws.com
exampleDomain:
type: aws:opensearch:Domain
name: example
properties:
domainName: ${domain}
engineVersion: OpenSearch_1.0
clusterConfig:
instanceType: m4.large.search
zoneAwarenessEnabled: true
vpcOptions:
subnetIds:
- ${exampleGetSubnets.ids[0]}
- ${exampleGetSubnets.ids[1]}
securityGroupIds:
- ${exampleSecurityGroup.id}
advancedOptions:
rest.action.multi.allow_explicit_index: 'true'
accessPolicies: ${exampleGetPolicyDocument.json}
tags:
Domain: TestDomain
options:
dependsOn:
- ${exampleServiceLinkedRole}
variables:
example:
fn::invoke:
function: aws:ec2:getVpc
arguments:
tags:
Name: ${vpc}
exampleGetSubnets:
fn::invoke:
function: aws:ec2:getSubnets
arguments:
filters:
- name: vpc-id
values:
- ${example.id}
tags:
Tier: private
current:
fn::invoke:
function: aws:getRegion
arguments: {}
currentGetCallerIdentity:
fn::invoke:
function: aws:getCallerIdentity
arguments: {}
exampleGetPolicyDocument:
fn::invoke:
function: aws:iam:getPolicyDocument
arguments:
statements:
- effect: Allow
principals:
- type: '*'
identifiers:
- '*'
actions:
- es:*
resources:
- arn:aws:es:${current.region}:${currentGetCallerIdentity.accountId}:domain/${domain}/*
The vpcOptions block places the domain in specified subnets with attached security groups. VPC deployment is immutable; adding or removing vpcOptions forces resource replacement. The example creates a service-linked role (AWSServiceRoleForAmazonOpenSearchService) that OpenSearch uses to manage network interfaces. VPC-based domains use security group rules instead of IP-based access policies.
Enable fine-grained access control with encryption
Fine-grained access control provides user-level permissions within OpenSearch, but requires encryption at rest, node-to-node encryption, and HTTPS enforcement.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const example = new aws.opensearch.Domain("example", {
domainName: "ggkitty",
engineVersion: "Elasticsearch_7.1",
clusterConfig: {
instanceType: "r5.large.search",
},
advancedSecurityOptions: {
enabled: false,
anonymousAuthEnabled: true,
internalUserDatabaseEnabled: true,
masterUserOptions: {
masterUserName: "example",
masterUserPassword: "Barbarbarbar1!",
},
},
encryptAtRest: {
enabled: true,
},
domainEndpointOptions: {
enforceHttps: true,
tlsSecurityPolicy: "Policy-Min-TLS-1-2-2019-07",
},
nodeToNodeEncryption: {
enabled: true,
},
ebsOptions: {
ebsEnabled: true,
volumeSize: 10,
},
});
import pulumi
import pulumi_aws as aws
example = aws.opensearch.Domain("example",
domain_name="ggkitty",
engine_version="Elasticsearch_7.1",
cluster_config={
"instance_type": "r5.large.search",
},
advanced_security_options={
"enabled": False,
"anonymous_auth_enabled": True,
"internal_user_database_enabled": True,
"master_user_options": {
"master_user_name": "example",
"master_user_password": "Barbarbarbar1!",
},
},
encrypt_at_rest={
"enabled": True,
},
domain_endpoint_options={
"enforce_https": True,
"tls_security_policy": "Policy-Min-TLS-1-2-2019-07",
},
node_to_node_encryption={
"enabled": True,
},
ebs_options={
"ebs_enabled": True,
"volume_size": 10,
})
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/opensearch"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := opensearch.NewDomain(ctx, "example", &opensearch.DomainArgs{
DomainName: pulumi.String("ggkitty"),
EngineVersion: pulumi.String("Elasticsearch_7.1"),
ClusterConfig: &opensearch.DomainClusterConfigArgs{
InstanceType: pulumi.String("r5.large.search"),
},
AdvancedSecurityOptions: &opensearch.DomainAdvancedSecurityOptionsArgs{
Enabled: pulumi.Bool(false),
AnonymousAuthEnabled: pulumi.Bool(true),
InternalUserDatabaseEnabled: pulumi.Bool(true),
MasterUserOptions: &opensearch.DomainAdvancedSecurityOptionsMasterUserOptionsArgs{
MasterUserName: pulumi.String("example"),
MasterUserPassword: pulumi.String("Barbarbarbar1!"),
},
},
EncryptAtRest: &opensearch.DomainEncryptAtRestArgs{
Enabled: pulumi.Bool(true),
},
DomainEndpointOptions: &opensearch.DomainDomainEndpointOptionsArgs{
EnforceHttps: pulumi.Bool(true),
TlsSecurityPolicy: pulumi.String("Policy-Min-TLS-1-2-2019-07"),
},
NodeToNodeEncryption: &opensearch.DomainNodeToNodeEncryptionArgs{
Enabled: pulumi.Bool(true),
},
EbsOptions: &opensearch.DomainEbsOptionsArgs{
EbsEnabled: pulumi.Bool(true),
VolumeSize: pulumi.Int(10),
},
})
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.OpenSearch.Domain("example", new()
{
DomainName = "ggkitty",
EngineVersion = "Elasticsearch_7.1",
ClusterConfig = new Aws.OpenSearch.Inputs.DomainClusterConfigArgs
{
InstanceType = "r5.large.search",
},
AdvancedSecurityOptions = new Aws.OpenSearch.Inputs.DomainAdvancedSecurityOptionsArgs
{
Enabled = false,
AnonymousAuthEnabled = true,
InternalUserDatabaseEnabled = true,
MasterUserOptions = new Aws.OpenSearch.Inputs.DomainAdvancedSecurityOptionsMasterUserOptionsArgs
{
MasterUserName = "example",
MasterUserPassword = "Barbarbarbar1!",
},
},
EncryptAtRest = new Aws.OpenSearch.Inputs.DomainEncryptAtRestArgs
{
Enabled = true,
},
DomainEndpointOptions = new Aws.OpenSearch.Inputs.DomainDomainEndpointOptionsArgs
{
EnforceHttps = true,
TlsSecurityPolicy = "Policy-Min-TLS-1-2-2019-07",
},
NodeToNodeEncryption = new Aws.OpenSearch.Inputs.DomainNodeToNodeEncryptionArgs
{
Enabled = true,
},
EbsOptions = new Aws.OpenSearch.Inputs.DomainEbsOptionsArgs
{
EbsEnabled = true,
VolumeSize = 10,
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.opensearch.Domain;
import com.pulumi.aws.opensearch.DomainArgs;
import com.pulumi.aws.opensearch.inputs.DomainClusterConfigArgs;
import com.pulumi.aws.opensearch.inputs.DomainAdvancedSecurityOptionsArgs;
import com.pulumi.aws.opensearch.inputs.DomainAdvancedSecurityOptionsMasterUserOptionsArgs;
import com.pulumi.aws.opensearch.inputs.DomainEncryptAtRestArgs;
import com.pulumi.aws.opensearch.inputs.DomainDomainEndpointOptionsArgs;
import com.pulumi.aws.opensearch.inputs.DomainNodeToNodeEncryptionArgs;
import com.pulumi.aws.opensearch.inputs.DomainEbsOptionsArgs;
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 Domain("example", DomainArgs.builder()
.domainName("ggkitty")
.engineVersion("Elasticsearch_7.1")
.clusterConfig(DomainClusterConfigArgs.builder()
.instanceType("r5.large.search")
.build())
.advancedSecurityOptions(DomainAdvancedSecurityOptionsArgs.builder()
.enabled(false)
.anonymousAuthEnabled(true)
.internalUserDatabaseEnabled(true)
.masterUserOptions(DomainAdvancedSecurityOptionsMasterUserOptionsArgs.builder()
.masterUserName("example")
.masterUserPassword("Barbarbarbar1!")
.build())
.build())
.encryptAtRest(DomainEncryptAtRestArgs.builder()
.enabled(true)
.build())
.domainEndpointOptions(DomainDomainEndpointOptionsArgs.builder()
.enforceHttps(true)
.tlsSecurityPolicy("Policy-Min-TLS-1-2-2019-07")
.build())
.nodeToNodeEncryption(DomainNodeToNodeEncryptionArgs.builder()
.enabled(true)
.build())
.ebsOptions(DomainEbsOptionsArgs.builder()
.ebsEnabled(true)
.volumeSize(10)
.build())
.build());
}
}
resources:
example:
type: aws:opensearch:Domain
properties:
domainName: ggkitty
engineVersion: Elasticsearch_7.1
clusterConfig:
instanceType: r5.large.search
advancedSecurityOptions:
enabled: false
anonymousAuthEnabled: true
internalUserDatabaseEnabled: true
masterUserOptions:
masterUserName: example
masterUserPassword: Barbarbarbar1!
encryptAtRest:
enabled: true
domainEndpointOptions:
enforceHttps: true
tlsSecurityPolicy: Policy-Min-TLS-1-2-2019-07
nodeToNodeEncryption:
enabled: true
ebsOptions:
ebsEnabled: true
volumeSize: 10
Fine-grained access control (advancedSecurityOptions) adds user authentication and role-based access to domain data. It requires three encryption settings: encryptAtRest, nodeToNodeEncryption, and enforceHttps via domainEndpointOptions. The masterUserOptions block creates an internal user database with a master username and password. This example shows the initial configuration with advancedSecurityOptions.enabled set to false; enabling it on an existing domain requires a second apply with enabled set to true.
Beyond these examples
These snippets focus on specific domain-level features: engine version and cluster sizing, access control (IP policies and fine-grained access), and VPC networking and CloudWatch logging. They’re intentionally minimal rather than full search deployments.
The examples may reference pre-existing infrastructure such as VPC subnets and security groups (for VPC deployments), CloudWatch log groups and IAM resource policies, and service-linked roles for OpenSearch. They focus on configuring the domain rather than provisioning everything around it.
To keep things focused, common domain patterns are omitted, including:
- Auto-Tune and off-peak maintenance windows
- Cognito or IAM Identity Center authentication
- EBS volume configuration and snapshot options
- Custom endpoint configuration (domainEndpointOptions)
- Machine learning features (aimlOptions)
These omissions are intentional: the goal is to illustrate how each domain feature is wired, not provide drop-in search clusters. See the OpenSearch Domain resource reference for all available configuration options.
Let's create AWS OpenSearch Domains
Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.
Try Pulumi Cloud for FREEFrequently Asked Questions
Configuration & Perpetual Diff
advancedOptions values aren’t strings (wrapped in quotes), they may cause perpetual diff and trigger unwanted recreation. Always quote the values.domainName and vpcOptions are immutable. Changing either forces creation of a new domain.Elasticsearch vs OpenSearch Differences
arn:aws:es:, IAM actions use es:* prefix, and the principal service remains es.amazonaws.com..search (e.g., t2.micro.search), while Elasticsearch types end in .elasticsearch (e.g., t2.micro.elasticsearch).Elasticsearch_7.10 format (with prefix) for both OpenSearch and legacy Elasticsearch. Legacy Elasticsearch used 7.10 (version only), but OpenSearch requires the prefix.AWSServiceRoleForAmazonOpenSearchService for OpenSearch, not AWSServiceRoleForAmazonElasticsearchService (which was for Elasticsearch).VPC & Networking
vpcOptions with subnetIds and securityGroupIds. Create the AWSServiceRoleForAmazonOpenSearchService service-linked role first and use dependsOn to ensure proper ordering.vpcOptions is immutable. Adding or removing VPC configuration forces creation of a new domain.Security & Fine-Grained Access Control
advancedSecurityOptions.enabled set to false but with all prerequisites configured. Then update to set enabled to true.encryptAtRest, nodeToNodeEncryption, set enforceHttps to true, and use tlsSecurityPolicy of Policy-Min-TLS-1-2-2019-07 or higher.Snapshots & Backups
snapshotOptions is deprecated. OpenSearch 5.3 and later automatically takes hourly snapshots. Earlier versions take daily snapshots.Using a different cloud?
Explore analytics guides for other cloud providers: