First pass CDK stack

pull/501/head
Mikhael Rakauskas 3 months ago
parent a317c030a1
commit e39ab1fba3

@ -0,0 +1,5 @@
node_modules
# CDK asset staging directory
.cdk.staging
cdk.out

@ -0,0 +1,3 @@
# CDK asset staging directory
.cdk.staging
cdk.out

@ -0,0 +1,153 @@
# CP Docuseal CDK Infrastructure
This directory contains AWS CDK v2 infrastructure code for deploying the CP Docuseal app. At present it only deploys to development and staging; production will follow.
## Architecture Overview
This infrastructure is basically nicked wholesale from the Integration Station application, just dialed down a notch as our needs are a bit less than theirs.
- **Internal Application Load Balancer (ALB)** - Routes traffic to ECS services within the VPC
- **Amazon ECS Cluster** - Runs containerized applications on EC2 instances (ARM64 t4g.small)
- **ECS Service** - Manages container deployment and scaling
- **ECS Task Definition** - Defines container configuration and resource requirements
- **CloudWatch Logging** - Centralized logging for application monitoring
- **Security Groups** - Network security controls
- **ECR Integration** - Uses existing "integration-station" ECR repository
## Prerequisites
1. **AWS CLI** configured with appropriate permissions
2. **Node.js** (version 22 or later)
3. **AWS CDK v2** installed globally: `npm install -g aws-cdk`
4. **Existing AWS Infrastructure**:
- VPC with public and private subnets
- ECR repository named "cp-docuseal"
- Appropriate IAM permissions for CDK deployment
## Setup
1. **Install dependencies**:
```bash
cd cdk_deploy
npm install
```
2. **Bootstrap CDK** (application setup ONLY):
```bash
npm run bootstrap
```
3. **Update VPC and Subnet IDs**:
Edit `app.js` and replace the placeholder IDs with your actual VPC and subnet IDs:
```javascript
vpcId: 'vpc-your-actual-vpc-id',
privateSubnetIds: ['subnet-your-private-1', 'subnet-your-private-2'],
publicSubnetIds: ['subnet-your-public-1', 'subnet-your-public-2']
```
## Environment Configuration
The infrastructure supports three environments with different resource allocations:
### Development
- **Instances**: 1 ECS instance
- **CPU**: 512 units
- **Memory**: 1024 MB
### Staging
- **Instances**: 1 ECS instance
- **CPU**: 512 units
- **Memory**: 1024 MB
### Production
- N/A
## Deployment
### Deploy to Development
```bash
npm run deploy:dev
```
### Deploy to Staging
```bash
npm run deploy:staging
```
### Deploy to Production - NOT YET SUPPORTED
```bash
npm run deploy:prod
```
### View CloudFormation Template
```bash
npm run synth
```
### Compare Changes
```bash
npm run diff
```
## Cleanup
### Destroy Development Environment
```bash
npm run destroy:dev
```
### Destroy Staging Environment
```bash
npm run destroy:staging
```
### Destroy Production Environment - NOT YET SUPPORTED
```bash
npm run destroy:prod
```
## Important Notes
1. **Internal ALB**: The Application Load Balancer is configured as internal-only and deployed in private subnets for security.
2. **ARM64 Instances**: The ECS cluster uses t4g.small ARM64 instances for cost efficiency. Ensure your container images are built for ARM64 architecture.
3. **Health Checks**: The ALB target group is configured to perform health checks on `/health` endpoint. Make sure your application responds to this endpoint.
4. **Logging**: All ECS tasks automatically log to CloudWatch under `/ecs/cp-docuseal-{environment}` log groups.
5. **Security**: Security groups are configured to allow:
- ALB: HTTP (80) and HTTPS (443) traffic
- ECS: Traffic from ALB on port 3000
## Troubleshooting
1. **VPC Lookup Issues**: Ensure the VPC IDs and subnet IDs in `app.js` are correct and exist in your AWS account.
2. **ECR Repository**: Verify that the "cp-docuseal" ECR repository exists and contains the required Docker images.
3. **Permissions**: Ensure your AWS credentials have sufficient permissions for:
- EC2 (VPC, Security Groups, Launch Templates)
- ECS (Clusters, Services, Tasks)
- ELB (Application Load Balancers, Target Groups)
- CloudWatch (Log Groups)
- IAM (Roles and Policies)
4. **Container Health**: If services fail to start, check CloudWatch logs for container startup issues.
## Customization
You can modify the following aspects:
- **Instance Types**: Change `ec2.InstanceClass.T4G` and `ec2.InstanceSize.SMALL` in the stack
- **Container Port**: Update port mappings if your application uses a different port
- **Resource Limits**: Adjust CPU and memory allocations in the environment configurations
- **Auto Scaling**: Modify `minCapacity` and `maxCapacity` for different scaling behaviors
## Stack Outputs
After deployment, the stack provides:
- **ALB DNS Name**: Internal DNS name for the Application Load Balancer
- **ECS Cluster Name**: Name of the created ECS cluster
- **ECS Service Name**: Name of the ECS service

@ -0,0 +1,109 @@
#!/usr/bin/env node
const { App } = require('aws-cdk-lib');
const { CPDocusealStack } = require('./lib/cp-docuseal-stack');
const fs = require('fs');
const app = new App();
const userDataScript = fs.readFileSync('./userdata.txt', 'utf8');
// Get staging number from context if provided
const stagingNumber = app.node.tryGetContext('stagingNumber');
// Function to get certificate ARN based on staging number
function getStagingCertificateArn(stagingNumber) {
if (!stagingNumber) {
// Default certificate for staging when no staging number is provided
return 'arn:aws:acm:us-east-1:788066832395:certificate/5b1f59b9-ab27-4056-a5e2-0d89554e5f35';
}
const num = parseInt(stagingNumber);
if (num >= 1 && num <= 11) {
return 'arn:aws:acm:us-east-1:788066832395:certificate/d3ae2320-6da3-4a6f-a3d9-0f00f85033cb';
} else if (num >= 12 && num <= 22) {
return 'arn:aws:acm:us-east-1:788066832395:certificate/5b1f59b9-ab27-4056-a5e2-0d89554e5f35';
} else if (num >= 23 && num <= 24) {
return 'arn:aws:acm:us-east-1:788066832395:certificate/69a8fe61-f12f-4251-9e55-d68c21553388';
} else if (num >= 25 && num <= 27) {
return 'arn:aws:acm:us-east-1:788066832395:certificate/3ce231d1-b2ec-4013-80db-b231db5a1e02';
} else {
// Default certificate for staging numbers outside defined ranges
return 'arn:aws:acm:us-east-1:788066832395:certificate/5b1f59b9-ab27-4056-a5e2-0d89554e5f35';
}
}
// Environment configurations
const environments = {
dev: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION || 'us-east-1',
vpcId: 'vpc-95c19df2',
publicSubnetIds: ['subnet-ff02dbb6', 'subnet-69fe1132', 'subnet-cb8e63f7' ],
instanceCount: 1,
instanceSize: 'SMALL',
cpu: 512,
memory: 1024,
securityGroupIds: ["sg-0f0da2fa2d6088742", "sg-006e8df67aec60469"],
userDataScript: userDataScript,
certificateArn: 'arn:aws:acm:us-east-1:788066832395:certificate/5b1f59b9-ab27-4056-a5e2-0d89554e5f35',
},
staging: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION || 'us-east-1',
vpcId: 'vpc-95c19df2',
publicSubnetIds: ['subnet-ff02dbb6', 'subnet-69fe1132', 'subnet-cb8e63f7'],
instanceCount: 1,
instanceSize: 'SMALL',
apiCpu: 512,
apiMemory: 1024,
securityGroupIds: ["sg-0f0da2fa2d6088742", "sg-006e8df67aec60469"],
userDataScript: userDataScript,
certificateArn: getStagingCertificateArn(stagingNumber),
},
// production: {
// account: process.env.CDK_DEFAULT_ACCOUNT,
// region: process.env.CDK_DEFAULT_REGION || 'us-east-1',
// vpcId: 'vpc-95c19df2',
// publicSubnetIds: ['subnet-ff02dbb6', 'subnet-69fe1132', 'subnet-cb8e63f7' ],
// instanceCount: 2,
// instanceSize: 'LARGE',
// apiCpu: 900,
// apiMemory: 3000,
// sidekiqCpu: 900,
// sidekiqMemory: 3000,
// securityGroupIds: ["sg-09fa17711757036e6", "sg-006e8df67aec60469"],
// userDataScript: userDataScript,
// certificateArn: 'arn:aws:acm:us-east-1:788066832395:certificate/05fa2bb8-5589-4425-9f28-427f1082a64b',
// }
};
// Create stacks for each environment
Object.entries(environments).forEach(([envName, config]) => {
// Construct stack name with staging number if provided
let stackName = `CPDocusealStack-${envName}`;
let environmentName = envName;
// Append staging number for staging environments
if (envName === 'staging' && stagingNumber) {
stackName += `-${stagingNumber}`;
environmentName += `-${stagingNumber}`;
}
new CPDocusealStack(app, stackName, {
env: {
account: config.account,
region: config.region
},
environment: envName,
vpcConfig: {
vpcId: config.vpcId,
publicSubnetIds: config.publicSubnetIds
},
ecsConfig: config,
certificateArn: config.certificateArn,
securityGroupIds: config.securityGroupIds,
userDataScript: config.userDataScript
});
});

@ -0,0 +1,82 @@
{
"vpc-provider:account=788066832395:filter.vpc-id=vpc-95c19df2:region=us-east-1:returnAsymmetricSubnets=true": {
"vpcId": "vpc-95c19df2",
"vpcCidrBlock": "172.30.0.0/16",
"ownerAccountId": "788066832395",
"availabilityZones": [],
"subnetGroups": [
{
"name": "Public",
"type": "Public",
"subnets": [
{
"subnetId": "subnet-ff02dbb6",
"cidr": "172.30.0.0/24",
"availabilityZone": "us-east-1a",
"routeTableId": "rtb-27219141"
},
{
"subnetId": "subnet-69fe1132",
"cidr": "172.30.1.0/24",
"availabilityZone": "us-east-1b",
"routeTableId": "rtb-27219141"
},
{
"subnetId": "subnet-95927bb8",
"cidr": "172.30.2.0/24",
"availabilityZone": "us-east-1d",
"routeTableId": "rtb-27219141"
},
{
"subnetId": "subnet-0a1feb6c1333efbab",
"cidr": "172.30.8.0/24",
"availabilityZone": "us-east-1d",
"routeTableId": "rtb-27219141"
},
{
"subnetId": "subnet-cb8e63f7",
"cidr": "172.30.3.0/24",
"availabilityZone": "us-east-1e",
"routeTableId": "rtb-27219141"
},
{
"subnetId": "subnet-0f5743e7967df31ef",
"cidr": "172.30.4.0/24",
"availabilityZone": "us-east-1f",
"routeTableId": "rtb-27219141"
},
{
"subnetId": "subnet-0118637ce4cf80f0a",
"cidr": "172.30.10.0/24",
"availabilityZone": "us-east-1f",
"routeTableId": "rtb-27219141"
}
]
},
{
"name": "Private",
"type": "Private",
"subnets": [
{
"subnetId": "subnet-0a263997449735cfd",
"cidr": "172.30.5.0/24",
"availabilityZone": "us-east-1a",
"routeTableId": "rtb-07ee1e39f005b06f2"
},
{
"subnetId": "subnet-05e98535001316fe8",
"cidr": "172.30.6.0/24",
"availabilityZone": "us-east-1b",
"routeTableId": "rtb-07ee1e39f005b06f2"
},
{
"subnetId": "subnet-0bcf29b2a2a47fbb7",
"cidr": "172.30.9.0/24",
"availabilityZone": "us-east-1e",
"routeTableId": "rtb-07ee1e39f005b06f2"
}
]
}
]
}
}

@ -0,0 +1,64 @@
{
"app": "node app.js",
"watch": {
"include": [
"**"
],
"exclude": [
"README.md",
"cdk*.json",
"**/*.js",
"package*.json",
"yarn.lock",
"node_modules"
]
},
"context": {
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
"@aws-cdk/core:checkSecretUsage": true,
"@aws-cdk/core:target-partitions": [
"aws",
"aws-cn"
],
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
"@aws-cdk/aws-iam:minimizePolicies": true,
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
"@aws-cdk/core:enablePartitionLiterals": true,
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
"@aws-cdk/aws-iam:standardizedServicePrincipals": true,
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
"@aws-cdk/aws-route53-patters:useCertificate": true,
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
"@aws-cdk/aws-redshift:columnId": true,
"@aws-cdk/aws-stepfunctions-tasks:enableLoggingConfiguration": true,
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
"@aws-cdk/aws-kms:aliasNameRef": true,
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
"@aws-cdk/aws-efs:denyAnonymousAccess": true,
"@aws-cdk/aws-opensearchservice:enableLogging": true,
"@aws-cdk/aws-noralization:emptyOperationContext": true,
"@aws-cdk/aws-lambda:codecommitPolicyDefaultTargetsBranch": true,
"@aws-cdk/aws-lambda:recognizeVersionProps": true,
"@aws-cdk/aws-cloudformation:parseParamsAndSecretsConfiguration": true,
"@aws-cdk/aws-eks:nodegroupNameAttribute": true,
"@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true,
"@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true,
"@aws-cdk/aws-ec2:disableSubnetCreateDefaultRouteToIgw": true,
"@aws-cdk/aws-ec2:ipv4IpamPoolPublicIpSource": true
}
}

@ -0,0 +1,382 @@
const {
Stack,
Duration,
aws_ec2: ec2,
aws_ecs: ecs,
aws_elasticloadbalancingv2: elbv2,
aws_logs: logs,
aws_iam: iam,
aws_ecr: ecr,
aws_secretsmanager: secretsmanager,
aws_certificatemanager: acm,
aws_route53: route53,
aws_route53_targets: targets,
Aspects,
RemovalPolicy
} = require('aws-cdk-lib');
const { Construct } = require('constructs');
class CPDocusealStack extends Stack {
constructor(scope, id, props) {
super(scope, id, props);
const { environment, vpcConfig, ecsConfig, securityGroupIds, userDataScript } = props;
// Get the image tag from CDK context, default to 'latest' if not provided
const imageTag = this.node.tryGetContext('imageTag') || 'latest';
const stagingNumber = this.node.tryGetContext('stagingNumber') || null;
const envDescription = stagingNumber ? `${environment}-${stagingNumber}` : environment;
// Import existing VPC
const vpc = ec2.Vpc.fromLookup(this, 'VPC', {
vpcId: vpcConfig.vpcId
});
// Import existing subnets
const publicSubnets = vpcConfig.publicSubnetIds.map((subnetId, index) =>
ec2.Subnet.fromSubnetId(this, `PublicSubnet${index}`, subnetId)
);
// Security Group for ALB
const albSecurityGroup = new ec2.SecurityGroup(this, 'ALBSecurityGroup', {
vpc,
description: `ALB Security Group for ${envDescription}`,
allowAllOutbound: true
});
// we may not need this...
albSecurityGroup.addIngressRule(
ec2.Peer.anyIpv4(),
ec2.Port.tcp(80),
'Allow HTTP traffic'
);
albSecurityGroup.addIngressRule(
ec2.Peer.anyIpv4(),
ec2.Port.tcp(443),
'Allow HTTPS traffic'
);
// Security Group for ECS Tasks
const ecsSecurityGroup = new ec2.SecurityGroup(this, 'ECSSecurityGroup', {
vpc,
description: `ECS Security Group for ${envDescription}`,
allowAllOutbound: true
});
ecsSecurityGroup.addIngressRule(
albSecurityGroup,
ec2.Port.tcp(3001),
'Allow traffic from ALB'
);
// Internal Application Load Balancer
const alb = new elbv2.ApplicationLoadBalancer(this, 'InternalALB', {
vpc,
internetFacing: true,
vpcSubnets: {
subnets: publicSubnets
},
securityGroup: albSecurityGroup,
loadBalancerName: `cpd-alb-${envDescription}`
});
// Target Group for ECS Service
const targetGroup = new elbv2.ApplicationTargetGroup(this, 'ECSTargetGroup', {
vpc,
port: 3001,
protocol: elbv2.ApplicationProtocol.HTTP,
targetType: elbv2.TargetType.INSTANCE,
healthCheck: {
enabled: true,
healthyHttpCodes: '200',
path: '/up',
protocol: elbv2.Protocol.HTTP,
interval: Duration.seconds(30),
timeout: Duration.seconds(5),
healthyThresholdCount: 2,
unhealthyThresholdCount: 5
},
deregistrationDelay: Duration.seconds(20),
targetGroupName: `cpd-tg-${envDescription}`
});
// ALB Listener
const listener = alb.addListener('ALBListener', {
port: 80,
protocol: elbv2.ApplicationProtocol.HTTP,
defaultTargetGroups: [targetGroup]
});
// Accept certificateArn as input (default to provided value if not in props)
const certificateArn = props.certificateArn || 'arn:aws:acm:us-east-1:788066832395:certificate/05fa2bb8-5589-4425-9f28-427f1082a64b';
// Add HTTPS Listener with ACM certificate
const certificate = acm.Certificate.fromCertificateArn(this, 'ALBCertificate', certificateArn);
alb.addListener('ALBListenerHTTPS', {
port: 443,
protocol: elbv2.ApplicationProtocol.HTTPS,
certificates: [certificate],
defaultTargetGroups: [targetGroup]
});
// Create Route53 A record for staging and production environments
let hostedZoneDomain = null;
if (environment === 'staging' && stagingNumber) {
hostedZoneDomain = `cpstaging${stagingNumber}.name`;
// } else if (environment === 'production') {
// hostedZoneDomain = 'careerplug.com';
}
if (hostedZoneDomain) {
const recordName = 'cpd';
// Import the existing hosted zone
const hostedZone = route53.HostedZone.fromLookup(this, 'HostedZone', {
domainName: hostedZoneDomain
});
// Create A record pointing to ALB
new route53.ARecord(this, 'ALBARecord', {
zone: hostedZone,
recordName: recordName,
target: route53.RecordTarget.fromAlias(new targets.LoadBalancerTarget(alb)),
ttl: Duration.minutes(1),
comment: `A record for CP Docuseal ${envDescription}`
});
}
// ECS Cluster
const cluster = new ecs.Cluster(this, 'ECSCluster', {
vpc,
clusterName: `cp-docuseal-${envDescription}`,
containerInsightsV2: ecs.ContainerInsights.ENABLED
});
// Determine instance size based on configuration
const instanceSize = ecsConfig.instanceSize || 'SMALL';
// Import additional existing security groups by ID from props
const importedSecurityGroups = (securityGroupIds || []).map((sgId, idx) =>
ec2.SecurityGroup.fromSecurityGroupId(this, `ImportedSG${idx + 1}`, sgId)
);
// Auto Scaling Group for ECS with ARM64 instances
const autoScalingGroup = cluster.addCapacity('ECSAutoScalingGroup', {
instanceType: ec2.InstanceType.of(ec2.InstanceClass.T4G, ec2.InstanceSize[instanceSize]), // ARM64
machineImage: ecs.EcsOptimizedImage.amazonLinux2(ecs.AmiHardwareType.ARM),
minCapacity: ecsConfig.instanceCount,
maxCapacity: ecsConfig.instanceCount,
vpcSubnets: {
subnets: publicSubnets
}
});
// Attach imported security groups to the EC2 instances
importedSecurityGroups.forEach((sg, idx) => {
autoScalingGroup.addSecurityGroup(sg);
});
// Add security group rule to EC2 instances for HOST network mode
// Allow ALB to reach containers on port 3001
autoScalingGroup.addSecurityGroup(ecsSecurityGroup);
if (userDataScript) {
autoScalingGroup.addUserData(userDataScript);
}
// CloudWatch Log Groups
const apiLogGroup = new logs.LogGroup(this, 'APILogGroup', {
logGroupName: `/ecs/cp-docuseal-api-${envDescription}`,
retention: logs.RetentionDays.ONE_WEEK
});
apiLogGroup.applyRemovalPolicy(RemovalPolicy.DESTROY);
// const sidekiqLogGroup = new logs.LogGroup(this, 'SidekiqLogGroup', {
// logGroupName: `/ecs/cp-docuseal-sidekiq-${envDescription}`,
// retention: logs.RetentionDays.ONE_WEEK
// });
// sidekiqLogGroup.applyRemovalPolicy(RemovalPolicy.DESTROY);
// Import ECR Repository
const ecrRepository = ecr.Repository.fromRepositoryName(
this,
'ECRRepository',
'cp-docuseal'
);
// Task Definition
const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDefinition', {
family: `cp-docuseal-task-${envDescription}`,
networkMode: ecs.NetworkMode.HOST
});
// Base environment variables
const containerEnvironment = {
NODE_ENV: environment,
PORT: '3001',
RAILS_ENV: environment
};
// Add environment-specific variables
if (environment === 'staging') { // || environment === 'production') {
// For staging and production, we'll use Secrets Manager
containerEnvironment.AWS_REGION = this.region;
// Map production environment to prod for secret naming
const secretEnvironment = environment; // === 'production' ? 'prod' : environment;
containerEnvironment.DB_SECRETS_NAME = `${secretEnvironment}/db_creds`;
// set DB name
containerEnvironment.DB_NAME = `cpdocuseal${stagingNumber ? `${stagingNumber}` : ''}`;
}
// Check if we need multi-container setup (staging and production)
const isMultiContainer = environment === 'staging'; // || environment === 'production';
if (isMultiContainer) {
// API Container Definition
const apiContainerConfig = {
image: ecs.ContainerImage.fromEcrRepository(ecrRepository, imageTag),
cpu: ecsConfig.apiCpu,
memoryLimitMiB: ecsConfig.apiMemory,
essential: true,
logging: ecs.LogDrivers.awsLogs({
streamPrefix: 'api',
logGroup: apiLogGroup
}),
environment: containerEnvironment,
healthCheck: {
command: ['CMD-SHELL', 'curl -f http://localhost:3001/up || exit 1'],
interval: Duration.seconds(30),
timeout: Duration.seconds(5),
retries: 3,
startPeriod: Duration.seconds(60)
}
};
// Set the appropriate startup script for staging and production
if (environment === 'staging') {
apiContainerConfig.command = ['./bin/start_staging', 'api'];
}
// } else if (environment === 'production') {
// apiContainerConfig.command = ['./bin/start_production', 'api'];
// }
const apiContainer = taskDefinition.addContainer('APIContainer', apiContainerConfig);
// Add port mapping for HOST network mode
apiContainer.addPortMappings({
containerPort: 3001,
protocol: ecs.Protocol.TCP
});
// Sidekiq Container Definition
// const sidekiqContainerConfig = {
// image: ecs.ContainerImage.fromEcrRepository(ecrRepository, imageTag),
// cpu: ecsConfig.sidekiqCpu,
// memoryLimitMiB: ecsConfig.sidekiqMemory,
// essential: true,
// logging: ecs.LogDrivers.awsLogs({
// streamPrefix: 'sidekiq',
// logGroup: sidekiqLogGroup
// }),
// environment: containerEnvironment,
// };
// Set the appropriate startup script for staging and production
// if (environment === 'staging') {
// sidekiqContainerConfig.command = ['./bin/start_staging', 'sidekiq'];
// } else if (environment === 'production') {
// sidekiqContainerConfig.command = ['./bin/start_production', 'sidekiq'];
// }
// const sidekiqContainer = taskDefinition.addContainer('SidekiqContainer', sidekiqContainerConfig);
// // Make Sidekiq container depend on API container
// sidekiqContainer.addContainerDependencies({
// container: apiContainer,
// condition: ecs.ContainerDependencyCondition.HEALTHY
// });
} else {
// Single container setup for dev environment
const containerConfig = {
image: ecs.ContainerImage.fromEcrRepository(ecrRepository, imageTag),
cpu: ecsConfig.cpu,
memoryLimitMiB: ecsConfig.memory,
essential: true,
logging: ecs.LogDrivers.awsLogs({
streamPrefix: 'ecs',
logGroup: apiLogGroup
}),
environment: containerEnvironment
};
const container = taskDefinition.addContainer('CPDocusealContainer', containerConfig);
// Add port mapping for HOST network mode
container.addPortMappings({
containerPort: 3001,
protocol: ecs.Protocol.TCP
});
}
// ECS Service
const service = new ecs.Ec2Service(this, 'ECSService', {
cluster,
taskDefinition,
serviceName: `cp-docuseal-service-${envDescription}`,
desiredCount: ecsConfig.instanceCount,
deploymentConfiguration: {
maximumPercent: 200,
minimumHealthyPercent: 50
},
enableExecuteCommand: true // For debugging
// vpcSubnets and securityGroups removed - HOST network mode uses EC2 instance network configuration
});
// Attach the service to the target group
service.attachToApplicationTargetGroup(targetGroup);
// Task Role for CloudWatch logging and ECR access
taskDefinition.taskRole.addManagedPolicy(
iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonECSTaskExecutionRolePolicy')
);
// Add Secrets Manager permissions for staging and production environments
if (environment === 'staging') { // || environment === 'production') {
// Map production environment to prod for secret naming
const secretEnvironment = environment; // === 'production' ? 'prod' : environment;
const secretsManagerPolicy = new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
'secretsmanager:GetSecretValue',
'secretsmanager:DescribeSecret'
],
resources: [
`arn:aws:secretsmanager:${this.region}:${this.account}:secret:${secretEnvironment}/db_creds*`,
// `arn:aws:secretsmanager:${this.region}:${this.account}:secret:integration_station/encryption_key*`
]
});
taskDefinition.taskRole.addToPolicy(secretsManagerPolicy);
}
// Output important values
this.albDnsName = alb.loadBalancerDnsName;
this.clusterName = cluster.clusterName;
this.serviceName = service.serviceName;
// Output Route53 record name for environments that create DNS records
if (hostedZoneDomain) {
if (environment === 'staging' && stagingNumber) {
this.route53RecordName = `cpd.cpstaging${stagingNumber}.name`;
// } else if (environment === 'production') {
// this.route53RecordName = 'cpd.careerplug.com';
}
}
}
}
module.exports = { CPDocusealStack };

@ -0,0 +1,468 @@
{
"name": "cdk_deploy",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "cdk_deploy",
"version": "0.1.0",
"dependencies": {
"aws-cdk-lib": "^2.205.0",
"cdk-nag": "^2.36.22",
"constructs": "^10.3.0"
},
"bin": {
"cdk_deploy": "bin/cdk_deploy.js"
},
"devDependencies": {
"aws-cdk": "2.1021.0"
}
},
"node_modules/@aws-cdk/asset-awscli-v1": {
"version": "2.2.242",
"resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.242.tgz",
"integrity": "sha512-4c1bAy2ISzcdKXYS1k4HYZsNrgiwbiDzj36ybwFVxEWZXVAP0dimQTCaB9fxu7sWzEjw3d+eaw6Fon+QTfTIpQ==",
"license": "Apache-2.0"
},
"node_modules/@aws-cdk/asset-node-proxy-agent-v6": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.0.tgz",
"integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==",
"license": "Apache-2.0"
},
"node_modules/@aws-cdk/cloud-assembly-schema": {
"version": "45.2.0",
"resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-45.2.0.tgz",
"integrity": "sha512-5TTUkGHQ+nfuUGwKA8/Yraxb+JdNUh4np24qk/VHXmrCMq+M6HfmGWfhcg/QlHA2S5P3YIamfYHdQAB4uSNLAg==",
"bundleDependencies": [
"jsonschema",
"semver"
],
"license": "Apache-2.0",
"dependencies": {
"jsonschema": "~1.4.1",
"semver": "^7.7.2"
},
"engines": {
"node": ">= 18.0.0"
}
},
"node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": {
"version": "1.4.1",
"inBundle": true,
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": {
"version": "7.7.2",
"inBundle": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/aws-cdk": {
"version": "2.1021.0",
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1021.0.tgz",
"integrity": "sha512-kE557b4N9UFWax+7km3R6D56o4tGhpzOks/lRDugaoC8su3mocLCXJhb954b/IRl0ipnbZnY/Sftq+RQ/sxivg==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"cdk": "bin/cdk"
},
"engines": {
"node": ">= 18.0.0"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/aws-cdk-lib": {
"version": "2.205.0",
"resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.205.0.tgz",
"integrity": "sha512-GZHy/F8jql+1aFlgIhGQuLl9zHvceHL0VRuBgaYngwWrflHc+ZN3eCEtfzCblA2bXmi4NbLljpSUBIGBVx2EEQ==",
"bundleDependencies": [
"@balena/dockerignore",
"case",
"fs-extra",
"ignore",
"jsonschema",
"minimatch",
"punycode",
"semver",
"table",
"yaml",
"mime-types"
],
"license": "Apache-2.0",
"dependencies": {
"@aws-cdk/asset-awscli-v1": "2.2.242",
"@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0",
"@aws-cdk/cloud-assembly-schema": "^45.0.0",
"@balena/dockerignore": "^1.0.2",
"case": "1.6.3",
"fs-extra": "^11.3.0",
"ignore": "^5.3.2",
"jsonschema": "^1.5.0",
"mime-types": "^2.1.35",
"minimatch": "^3.1.2",
"punycode": "^2.3.1",
"semver": "^7.7.2",
"table": "^6.9.0",
"yaml": "1.10.2"
},
"engines": {
"node": ">= 14.15.0"
},
"peerDependencies": {
"constructs": "^10.0.0"
}
},
"node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": {
"version": "1.0.2",
"inBundle": true,
"license": "Apache-2.0"
},
"node_modules/aws-cdk-lib/node_modules/ajv": {
"version": "8.17.1",
"inBundle": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/aws-cdk-lib/node_modules/ansi-regex": {
"version": "5.0.1",
"inBundle": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/aws-cdk-lib/node_modules/ansi-styles": {
"version": "4.3.0",
"inBundle": true,
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/aws-cdk-lib/node_modules/astral-regex": {
"version": "2.0.0",
"inBundle": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/aws-cdk-lib/node_modules/balanced-match": {
"version": "1.0.2",
"inBundle": true,
"license": "MIT"
},
"node_modules/aws-cdk-lib/node_modules/brace-expansion": {
"version": "1.1.12",
"inBundle": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/aws-cdk-lib/node_modules/case": {
"version": "1.6.3",
"inBundle": true,
"license": "(MIT OR GPL-3.0-or-later)",
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/aws-cdk-lib/node_modules/color-convert": {
"version": "2.0.1",
"inBundle": true,
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/aws-cdk-lib/node_modules/color-name": {
"version": "1.1.4",
"inBundle": true,
"license": "MIT"
},
"node_modules/aws-cdk-lib/node_modules/concat-map": {
"version": "0.0.1",
"inBundle": true,
"license": "MIT"
},
"node_modules/aws-cdk-lib/node_modules/emoji-regex": {
"version": "8.0.0",
"inBundle": true,
"license": "MIT"
},
"node_modules/aws-cdk-lib/node_modules/fast-deep-equal": {
"version": "3.1.3",
"inBundle": true,
"license": "MIT"
},
"node_modules/aws-cdk-lib/node_modules/fast-uri": {
"version": "3.0.6",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"inBundle": true,
"license": "BSD-3-Clause"
},
"node_modules/aws-cdk-lib/node_modules/fs-extra": {
"version": "11.3.0",
"inBundle": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=14.14"
}
},
"node_modules/aws-cdk-lib/node_modules/graceful-fs": {
"version": "4.2.11",
"inBundle": true,
"license": "ISC"
},
"node_modules/aws-cdk-lib/node_modules/ignore": {
"version": "5.3.2",
"inBundle": true,
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"inBundle": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/aws-cdk-lib/node_modules/json-schema-traverse": {
"version": "1.0.0",
"inBundle": true,
"license": "MIT"
},
"node_modules/aws-cdk-lib/node_modules/jsonfile": {
"version": "6.1.0",
"inBundle": true,
"license": "MIT",
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/aws-cdk-lib/node_modules/jsonschema": {
"version": "1.5.0",
"inBundle": true,
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/aws-cdk-lib/node_modules/lodash.truncate": {
"version": "4.4.2",
"inBundle": true,
"license": "MIT"
},
"node_modules/aws-cdk-lib/node_modules/mime-db": {
"version": "1.52.0",
"inBundle": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/aws-cdk-lib/node_modules/mime-types": {
"version": "2.1.35",
"inBundle": true,
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/aws-cdk-lib/node_modules/minimatch": {
"version": "3.1.2",
"inBundle": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/aws-cdk-lib/node_modules/punycode": {
"version": "2.3.1",
"inBundle": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/aws-cdk-lib/node_modules/require-from-string": {
"version": "2.0.2",
"inBundle": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/aws-cdk-lib/node_modules/semver": {
"version": "7.7.2",
"inBundle": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/aws-cdk-lib/node_modules/slice-ansi": {
"version": "4.0.0",
"inBundle": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"astral-regex": "^2.0.0",
"is-fullwidth-code-point": "^3.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
}
},
"node_modules/aws-cdk-lib/node_modules/string-width": {
"version": "4.2.3",
"inBundle": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/aws-cdk-lib/node_modules/strip-ansi": {
"version": "6.0.1",
"inBundle": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/aws-cdk-lib/node_modules/table": {
"version": "6.9.0",
"inBundle": true,
"license": "BSD-3-Clause",
"dependencies": {
"ajv": "^8.0.1",
"lodash.truncate": "^4.4.2",
"slice-ansi": "^4.0.0",
"string-width": "^4.2.3",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/aws-cdk-lib/node_modules/universalify": {
"version": "2.0.1",
"inBundle": true,
"license": "MIT",
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/aws-cdk-lib/node_modules/yaml": {
"version": "1.10.2",
"inBundle": true,
"license": "ISC",
"engines": {
"node": ">= 6"
}
},
"node_modules/cdk-nag": {
"version": "2.36.40",
"resolved": "https://registry.npmjs.org/cdk-nag/-/cdk-nag-2.36.40.tgz",
"integrity": "sha512-8ZmVuGMAfwJcz88uGBj0plGi7qNN4jTmlj/td7jzNcaBoPx4ZGq/hresTTY8uvg26apjxgSZgZswzLoWGyMfOg==",
"license": "Apache-2.0",
"peerDependencies": {
"aws-cdk-lib": "^2.156.0",
"constructs": "^10.0.5"
}
},
"node_modules/constructs": {
"version": "10.4.2",
"resolved": "https://registry.npmjs.org/constructs/-/constructs-10.4.2.tgz",
"integrity": "sha512-wsNxBlAott2qg8Zv87q3eYZYgheb9lchtBfjHzzLHtXbttwSrHPs1NNQbBrmbb1YZvYg2+Vh0Dor76w4mFxJkA==",
"license": "Apache-2.0"
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
}
}
}

@ -0,0 +1,34 @@
{
"name": "cdk_deploy",
"version": "0.1.0",
"bin": {
"cdk_deploy": "bin/cdk_deploy.js"
},
"scripts": {
"cdk": "cdk",
"deploy:dev": "cdk deploy CPDocusealStack-dev --profile default",
"deploy:staging": "cdk deploy CPDocusealStack-staging --profile default",
"deploy:prod": "cdk deploy CPDocusealStack-production --profile default",
"destroy:dev": "cdk destroy CPDocusealStack-dev --profile default",
"destroy:staging": "cdk destroy CPDocusealStack-staging --profile default",
"destroy:prod": "cdk destroy CPDocusealStack-production --profile default",
"synth": "cdk synth",
"diff": "cdk diff",
"bootstrap": "cdk bootstrap"
},
"devDependencies": {
"aws-cdk": "2.1021.0"
},
"dependencies": {
"aws-cdk-lib": "^2.205.0",
"cdk-nag": "^2.36.22",
"constructs": "^10.3.0"
},
"keywords": [
"aws",
"cdk",
"infrastructure",
"ecs",
"alb"
]
}

@ -0,0 +1,39 @@
sudo yum install -y ec2-instance-connect
cat >/home/ec2-user/.bashrc <<'EOF'
# .bashrc
# Source global definitions
if [ -f /etc/bashrc ]; then
. /etc/bashrc
fi
# User specific aliases and functions
function setContainer {
CONTAINER_ID=`sudo docker ps | grep "rails/bin" | awk '{ print $1; }' | head -1`
}
function execDocker {
setContainer
sudo docker exec -u rails -i -t $CONTAINER_ID $@
}
function copy_from {
setContainer
sudo docker cp "$CONTAINER_ID:/rails/$1" "$2"
}
function copy_to {
setContainer
sudo docker cp "$1" "$CONTAINER_ID:/rails/$2"
sudo docker exec -u root -t $CONTAINER_ID /bin/sh -c 'chown rails:rails /rails/$2'
}
function dangerConsole {
echo
echo "********** Accessing the WRITE & READ console **********"
echo "********** THIS IS DANGEROUS AND CHANGES YOU MAKE WILL IMPACT THE DATABASE **********"
echo
sleep 3
execDocker bin/rails console
}
alias console='dangerConsole'
alias shell='execDocker /bin/bash'
EOF
Loading…
Cancel
Save