Provision a Cluster via CloudFormation
CloudFormation
Step 1: Copy the curl Command from the UI
On the Create a new replicaset (Master) screen, click { REST API }. The following example is copied:
curl -v \
-H "Authorization: Bearer <YOUR_JWT_TOKEN>" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"host": {
"name": "<HOST_NAME>",
"ssh_port": <SSH_PORT>,
"username": "<SSH_USERNAME>",
"password": "<SSH_PASSWORD>",
"private_key": "<OPTIONAL_PRIVATE_KEY_CONTENT>"
},
"cluster": {
"name": "<CLUSTER_NAME>"
},
"ex_params": {
"replicaset_name": "<REPLICASET_NAME>",
"repository": "<BACKUP_REPOSITORY>",
"ssl": <USE_SSL>
"service_name": "proxmox",
"proxmox": true,
"distributor": "<OS_DISTRIBUTOR>",
"release": "<OS_VERSION>"
},
"name": "<NODE_NAME>",
"auto_delete": false,
"version": "<DB_VERSION>",
"auto_delete_days": 1,
"port": <DB_PORT>,
"sizing": "<SIZING>",
"dir_123Cluster": "<DATA_DIRECTORY>",
"drop_inventories": false,
"database_user": "<DB_ADMIN_USER>",
"database_pwd": "<DB_ADMIN_PASSWORD>",
"rest_api": true
}' \
<API_BASE_URL>
Step 2: Parse the curl Command
- Authorization header: Extract your JWT token from Bearer <YOUR_JWT_TOKEN>.
- Content-Type & Accept: Preserve both application/json headers.
- Payload fields
- host.name / host.ssh_port / host.username / host.password / host.private_key
- cluster.name
- ex_params.replicaset_name / ex_params.repository / ex_params.ssl / ex_params.distributor / ex_params.release
- name (node identifier)
- auto_delete / auto_delete_days
- version / port / sizing / dir_123Cluster / drop_inventories
- database_user / database_pwd
- rest_api
- Endpoint
- Base URI: <API_BASE_URL>
Step 3: Translate into CloudFormation
Directory Structure
├── params.json
├── run.sh
└── template.yaml
CloudFormation Parameters
Replace fields in corner brackets <> in parameters file with values extracted from the URL.
File params.json:
[
{
"ParameterKey": "ApiBaseUrl",
"ParameterValue": "<API_BASE_URL>"
},
{
"ParameterKey": "JwtToken",
"ParameterValue": "<YOUR_JWT_TOKEN>"
},
{
"ParameterKey": "HostName",
"ParameterValue": "<HOST_NAME>"
},
{
"ParameterKey": "SshUser",
"ParameterValue": "<SSH_USERNAME>"
},
{
"ParameterKey": "SshPassword",
"ParameterValue": "<SSH_PASSWORD>"
},
{
"ParameterKey": "OsDistributor",
"ParameterValue": "<OS_DISTRIBUTOR>"
},
{
"ParameterKey": "OSRelease",
"ParameterValue": "<OS_VERSION>"
},
{
"ParameterKey": "ClusterName",
"ParameterValue": "<CLUSTER_NAME>"
},
{
"ParameterKey": "ReplicasetName",
"ParameterValue": "<REPLICASET_NAME>"
},
{
"ParameterKey": "DbVersion",
"ParameterValue": "<DB_VERSION>"
},
{
"ParameterKey": "Port",
"ParameterValue": "<DB_PORT>"
},
{
"ParameterKey": "Sizing",
"ParameterValue": "<SIZING>"
},
{
"ParameterKey": "DataDirectory",
"ParameterValue": "<DATA_DIRECTORY>"
},
{
"ParameterKey": "DatabaseUser",
"ParameterValue": "<DB_ADMIN_USER>"
},
{
"ParameterKey": "DatabasePassword",
"ParameterValue": "<DB_ADMIN_PASSWORD>"
},
{
"ParameterKey": "EnableSsl",
"ParameterValue": "<USE_SSL>"
}
]
CloudFormation Template
Copy template contents as is.
Description: Enable SSL for DB connections
Default: 'false'
File template.yaml:
AWSTemplateFormatVersion: '2010-09-09'
Description: |
CloudFormation template for provisioning a DB cluster in 123cluster.
Uses a Lambda-backed custom resource to interact with the 123cluster REST API.
Parameters:
# API Configuration
ApiBaseUrl:
Type: String
Description: Base URL for 123cluster API
JwtToken:
Type: String
Description: JWT token for API authentication
NoEcho: true
# Host Configuration
HostName:
Type: String
Description: Host name address of the DB server
SshPort:
Type: Number
Description: SSH port for initial host setup
Default: '22'
SshUser:
Type: String
Description: SSH username
Default: 'root'
SshPassword:
Type: String
Description: SSH password
NoEcho: true
PrivateKey:
Type: String
Description: Optional SSH private key content
NoEcho: true
Default: ''
OsDistributor:
Type: String
Description: OS distributor name
OSRelease:
Type: String
Description: OS release number
# Cluster Configuration
ClusterName:
Type: String
Description: Name of the cluster
ReplicasetName:
Type: String
Description: Internal replicaset identifier
Repository:
Type: String
Description: Backup repository location
Default: 'network'
DbVersion:
Type: String
Description: Desired DB version
Port:
Type: Number
Description: Service port
Sizing:
Type: String
Description: Resource sizing for the node
Default: 'SMALL'
DataDirectory:
Type: String
Description: Data directory for DB files
# Database Credentials
DatabaseUser:
Type: String
Description: Database administrator username
DatabasePassword:
Type: String
Description: Database administrator password
NoEcho: true
EnableSsl:
Type: String
AllowedValues:
- 'true'
- 'false'
DropInventories:
Type: String
Description: Delete all backups and exports if exist
Default: 'false'
AllowedValues:
- 'true'
- 'false'
AutoDelete:
Type: String
Description: Delete cluster automatically
Default: 'false'
AllowedValues:
- 'true'
- 'false'
AutoDeleteDays:
Type: Number
Description: Delete cluster automatically after N days
Default: '1'
Resources:
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: CloudFormationResponsePolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: '*'
# Lambda function for custom resource
ProvisionClusterFunction:
Type: AWS::Lambda::Function
Properties:
Runtime: python3.11
Handler: index.handler
Role: !GetAtt LambdaExecutionRole.Arn
Timeout: 300
Code:
ZipFile: |
import json
import json
import urllib3
import os
from urllib.parse import urljoin
from urllib3.exceptions import InsecureRequestWarning
urllib3.disable_warnings(InsecureRequestWarning)
http = urllib3.PoolManager(
cert_reqs='CERT_NONE',
assert_hostname=False
)
def handler(event, context):
"""
CloudFormation custom resource handler for 123cluster provisioning.
Handles Create operations.
"""
request_type = event['RequestType']
properties = event['ResourceProperties']
api_base_url = properties['ApiBaseUrl']
jwt_token = properties['JwtToken']
headers = {
'Authorization': f'Bearer {jwt_token}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
payload = {
'host': {
'name': properties['HostName'],
'ssh_port': int(properties['SshPort']),
'username': properties['SshUser'],
'password': properties['SshPassword'],
'private_key': properties.get('PrivateKey', '')
},
'cluster': {
'name': properties['ClusterName']
},
'ex_params': {
'replicaset_name': properties['ReplicasetName'],
'repository': properties['Repository'],
'ssl': properties['EnableSsl'].lower() == 'true',
'service_name': 'proxmox',
'proxmox': True,
'distributor': properties['OsDistributor'],
'release': properties['OSRelease'],
},
'name': properties['ClusterName'],
'auto_delete': properties['AutoDelete'].lower() == 'true',
'version': properties['DbVersion'],
'auto_delete_days': int(properties['AutoDeleteDays']),
'port': int(properties['Port']),
'sizing': properties['Sizing'],
'dir_123Cluster': properties['DataDirectory'],
'drop_inventories': properties['DropInventories'].lower() == 'true',
'database_user': properties['DatabaseUser'],
'database_pwd': properties['DatabasePassword'],
'rest_api': True
}
try:
if request_type == 'Create':
response = http.request(
'POST',
api_base_url,
body=json.dumps(payload),
headers=headers
)
response_data = json.loads(response.data.decode('utf-8'))
if response.status >= 200 and response.status < 300:
send_response(event, context, 'SUCCESS', {
'ClusterId': properties['ClusterName'],
'Response': json.dumps(response_data)
})
else:
send_response(event, context, 'FAILED', {
'Reason': f'API returned status {response.status}: {response_data}'
})
elif request_type == 'Update':
send_response(event, context, 'SUCCESS', {
'Message': 'Update operations not implemented'
})
elif request_type == 'Delete':
send_response(event, context, 'SUCCESS', {
'Message': 'Delete operations not implemented'
})
except Exception as e:
send_response(event, context, 'FAILED', {
'Reason': str(e)
})
def send_response(event, context, status, response_data):
"""Send response to CloudFormation"""
response_body = {
'Status': status,
'Reason': response_data.get('Reason', 'See CloudWatch logs'),
'PhysicalResourceId': event.get('PhysicalResourceId',
context.log_stream_name),
'StackId': event['StackId'],
'RequestId': event['RequestId'],
'LogicalResourceId': event['LogicalResourceId'],
'Data': response_data
}
http.request(
'PUT',
event['ResponseURL'],
body=json.dumps(response_body),
headers={'Content-Type': ''}
)
# Custom resource for DB cluster provisioning
DBCluster:
Type: Custom::DBCluster
Properties:
ServiceToken: !GetAtt ProvisionClusterFunction.Arn
ApiBaseUrl: !Ref ApiBaseUrl
JwtToken: !Ref JwtToken
HostName: !Ref HostName
SshPort: !Ref SshPort
SshUser: !Ref SshUser
SshPassword: !Ref SshPassword
PrivateKey: !Ref PrivateKey
OsDistributor: !Ref OsDistributor
OSRelease: !Ref OSRelease
ClusterName: !Ref ClusterName
ReplicasetName: !Ref ReplicasetName
Repository: !Ref Repository
DbVersion: !Ref DbVersion
Port: !Ref Port
Sizing: !Ref Sizing
DataDirectory: !Ref DataDirectory
DatabaseUser: !Ref DatabaseUser
DatabasePassword: !Ref DatabasePassword
EnableSsl: !Ref EnableSsl
DropInventories: !Ref DropInventories
AutoDelete: !Ref AutoDelete
AutoDeleteDays: !Ref AutoDeleteDays
Outputs:
ClusterId:
Description: ID of the created cluster
Value: !GetAtt DBCluster.ClusterId
ApiResponse:
Description: Full API response from cluster creation
Value: !GetAtt DBCluster.Response
LambdaFunctionArn:
Description: ARN of the Lambda function handling provisioning
Value: !GetAtt ProvisionClusterFunction.Arn
Run shell script (optional)
Copy shell script as is (for simple run).
File run.sh:
#!/bin/bash
aws cloudformation create-stack \
--capabilities CAPABILITY_IAM \
--stack-name my-stack-name \
--template-body file://template.yaml \
--parameters file://params.json
Step 4: Deploy the Stack
Use AWS CLI to deploy the stack
# Validate the template
aws cloudformation validate-template \
--template-body file://template.yaml
# Create the stack
aws cloudformation create-stack \
--capabilities CAPABILITY_IAM \
--stack-name my-stack-name \
--template-body file://template.yaml \
--parameters file://params.json
# Monitor stack creation
aws cloudformation wait stack-create-complete \
--stack-name mongodb-replicaset-master
# Retrieve outputs
aws cloudformation describe-stacks \
--stack-name mongodb-replicaset-master \
--query 'Stacks[0].Outputs'
Additional Guidance & Best Practices
Parameterization
All user-specific and environment-specific values are exposed as CloudFormation parameters, making the template portable and reusable across multiple clusters, regions, and AWS accounts.
Security
- Sensitive Parameters: Use NoEcho: true for passwords and tokens to prevent them from appearing in CloudFormation console or API responses.
- Secrets Management: Consider integrating with AWS Secrets Manager for production deployments:
- JwtToken:
- Type: String
- Default: '{{resolve:secretsmanager:123cluster/jwt:SecretString:token}}'
- NoEcho: true
- IAM Permissions: The Lambda execution role follows least-privilege principles with only necessary permissions.
API Versioning
Always verify endpoint and payload requirements with the latest 123cluster API documentation to ensure compatibility. Update the Lambda function code as the API evolves.
Cost Optimization
- Lambda functions are billed per invocation and execution time; the provisioning function typically completes within seconds.
- Consider setting appropriate timeouts and memory allocation for the Lambda function.
- Use CloudWatch Logs retention policies to manage log storage costs.


