DocumentDB always separates two things that RDS couples: storage and compute. A cluster owns the shared, auto-replicated storage volume. Instances are the compute nodes you attach to it. Provisioning DocumentDB is therefore always at least two calls - create the cluster, then add one or more instances - and understanding that split makes everything else in this section straightforward.
This page provisions a cluster with a writer and a reader, with encryption, private networking, and Secrets Manager credentials on from the start, then shows where the app takes over with a MongoDB driver.
Provision an encrypted, private cluster; attach a writer and a reader; wait for the instances; then read the endpoints your driver will use.
# --- Python (boto3) ---import boto3docdb = boto3.client("docdb", region_name="us-east-1")# 1. Cluster (shared storage): encrypted, private, managed credential.docdb.create_db_cluster( DBClusterIdentifier="app-docdb", Engine="docdb", EngineVersion="5.0.0", MasterUsername="appuser", ManageMasterUserPassword=True, StorageEncrypted=True, DBSubnetGroupName="app-docdb-subnets", VpcSecurityGroupIds=["sg-0123456789abcdef0"], BackupRetentionPeriod=7, DeletionProtection=True,)# 2. Writer instance (compute): the first instance attached to the cluster.docdb.create_db_instance( DBInstanceIdentifier="app-docdb-writer", DBClusterIdentifier="app-docdb", Engine="docdb", DBInstanceClass="db.r6g.large",)# 3. Reader instance: a second instance in the same cluster.docdb.create_db_instance( DBInstanceIdentifier="app-docdb-reader-1", DBClusterIdentifier="app-docdb", Engine="docdb", DBInstanceClass="db.r6g.large",)# 4. Wait for each instance, THEN read the endpoints.waiter = docdb.get_waiter("db_instance_available")waiter.wait(DBInstanceIdentifier="app-docdb-writer")waiter.wait(DBInstanceIdentifier="app-docdb-reader-1")cl = docdb.describe_db_clusters(DBClusterIdentifier="app-docdb")["DBClusters"][0]print("writer:", cl["Endpoint"], "port", cl["Port"]) # writes hereprint("reader:", cl["ReaderEndpoint"]) # read-only queries here
// --- TypeScript (AWS SDK v3) ---import { DocDBClient, CreateDBClusterCommand, CreateDBInstanceCommand, DescribeDBClustersCommand, waitUntilDBInstanceAvailable,} from "@aws-sdk/client-docdb";const docdb = new DocDBClient({ region: "us-east-1" });// 1. Cluster (shared storage): encrypted, private, managed credential.await docdb.send(new CreateDBClusterCommand({ DBClusterIdentifier: "app-docdb", Engine: "docdb", EngineVersion: "5.0.0", MasterUsername: "appuser", ManageMasterUserPassword: true, StorageEncrypted: true, DBSubnetGroupName: "app-docdb-subnets", VpcSecurityGroupIds: ["sg-0123456789abcdef0"], BackupRetentionPeriod: 7, DeletionProtection: true,}));// 2. Writer instance (compute): the first instance attached to the cluster.await docdb.send(new CreateDBInstanceCommand({ DBInstanceIdentifier: "app-docdb-writer", DBClusterIdentifier: "app-docdb", Engine: "docdb", DBInstanceClass: "db.r6g.large",}));// 3. Reader instance: a second instance in the same cluster.await docdb.send(new CreateDBInstanceCommand({ DBInstanceIdentifier: "app-docdb-reader-1", DBClusterIdentifier: "app-docdb", Engine: "docdb", DBInstanceClass: "db.r6g.large",}));// 4. Wait for each instance, THEN read the endpoints.await waitUntilDBInstanceAvailable({ client: docdb, maxWaitTime: 1200 }, { DBInstanceIdentifier: "app-docdb-writer" });await waitUntilDBInstanceAvailable({ client: docdb, maxWaitTime: 1200 }, { DBInstanceIdentifier: "app-docdb-reader-1" });const dc = await docdb.send(new DescribeDBClustersCommand({ DBClusterIdentifier: "app-docdb" }));const cl = dc.DBClusters![0];console.log("writer:", cl.Endpoint, "port", cl.Port); // writes hereconsole.log("reader:", cl.ReaderEndpoint); // read-only queries here
What this demonstrates:
The cluster is created first (storage); instances attach afterward (compute).
The first instance becomes the writer; the second becomes a reader on shared storage.
Encryption, private subnets, security groups, and a managed credential are set at create time.
Each instance is gated by the db_instance_available waiter before the endpoint is used.
Once the endpoints are available, your app connects with a MongoDB driver over TLS on port 27017. That connection is the data plane and never touches the AWS SDK - see the Basics page.
The cluster owns a distributed, self-healing storage volume replicated six ways across three Availability Zones. It has no CPU of its own. DBClusterIdentifier names it, StorageEncrypted encrypts its volume, BackupRetentionPeriod enables automated backups, and MasterUsername sets the admin user. But you cannot connect to a cluster with no instances - the endpoint resolves to nothing.
An instance is a compute node. Each CreateDBInstance with a DBClusterIdentifier attaches CPU and memory sized by DBInstanceClass. The first instance is the writer; every additional instance is a reader. Because the storage is shared, a new reader does not copy data - it attaches to the existing volume and starts serving reads within seconds. That is why DocumentDB scales reads by adding instances rather than replicating a full copy.
DocumentDB provisioning is asynchronous. A create call returns in milliseconds with the resource in creating, and the endpoint is unusable until an instance is available. Wait on the db_instance_available waiter for each instance you create. The meaningful readiness signal is the instance, not the cluster - a cluster can report a status while its only instance is still coming up. Give the waiter a generous maxWaitTime because instance provisioning takes several minutes.
StorageEncrypted=True enables encryption at rest with the default KMS key (pass KmsKeyId for a customer-managed key). You cannot toggle encryption on an existing unencrypted cluster in place - you would snapshot, copy the snapshot with encryption, and restore. DBSubnetGroupName places the cluster in private subnets and VpcSecurityGroupIds restricts who can reach port 27017; a DocumentDB cluster should never be publicly reachable. ManageMasterUserPassword=True puts the credential in Secrets Manager from the first moment. Decide all three at create time.
EngineVersion selects the MongoDB wire-protocol compatibility (for example 5.0.0). Pick it for the operators and features your app needs, and keep it consistent across the cluster. For DBInstanceClass, the memory-optimized db.r6g and db.r5 families suit most production document workloads; db.t3.medium is fine for development. Match reader size to writer size so a failover does not land traffic on an undersized node.
Trying to connect to a cluster with no instance. A bare cluster owns storage but has no compute. Fix: call CreateDBInstance and wait for it before connecting.
Passing an RDS engine name. DocumentDB uses Engine="docdb", not postgres or mysql. Fix: set docdb on both the cluster and each instance.
Reading the endpoint before it exists. The endpoint is unusable while the instance is creating. Fix: block on db_instance_available, then describe the cluster.
Trying to encrypt later. Encryption at rest is create-time only. Fix: enable StorageEncrypted now, or snapshot-copy-restore into an encrypted copy.
Leaving the cluster publicly reachable. A missing or loose security group can expose port 27017. Fix: use a private DBSubnetGroupName and tight VpcSecurityGroupIds.
Under-sizing the waiter timeout. Instances take minutes to provision. Fix: raise maxWaitTime so the waiter outlasts provisioning.
DocumentDB separates storage from compute. CreateDBCluster provisions the shared storage volume, and CreateDBInstance attaches a compute node to it. A cluster with no instances exists but has nothing to connect to, so you always create at least one instance.
Which instance is the writer?
The first instance you attach to the cluster becomes the writer. Additional instances become readers. Route writes and read-after-write to the cluster Endpoint, and read-only queries to the ReaderEndpoint.
How do I add read capacity later?
Call CreateDBInstance with the same DBClusterIdentifier. Because storage is shared, the new reader attaches in seconds without copying data and immediately joins the reader endpoint's load balancing.
Can I enable encryption after the cluster exists?
Not in place. Encryption at rest is a create-time choice. To encrypt an existing unencrypted cluster, take a cluster snapshot, copy it with encryption enabled, and restore from the encrypted copy into a new cluster.
Why is my endpoint empty right after creating?
Provisioning is asynchronous and the instance is still creating. The endpoint is usable only once an instance is available. Wait on the db_instance_available waiter before reading Endpoint or ReaderEndpoint.
How should I network the cluster?
Place it in private subnets via a DBSubnetGroupName, and restrict port 27017 with VpcSecurityGroupIds so only the application tier can reach it. A DocumentDB cluster should never be reachable from the public internet.
Should I provision this from the SDK or IaC?
The SDK is ideal for tooling, automation, and learning the control plane. For durable, reviewed, repeatable infrastructure, define the same cluster and instances in CloudFormation, CDK, or Terraform. The underlying API calls and parameters are identical.
Stack versions: This page was written for boto3 1.43.x (Python 3.10+) and the AWS SDK for JavaScript v3 (Node.js 18+). Y29kZWd1aWRlcy5pb3xjZ2lvMTE4MHwyMDI2MDc=
Reviewed by Chris St. John·Last updated Jul 24, 2026