ECS
Deploy DeepIntShield on AWS ECS using either Makefile automation or direct AWS CLI commands. This guide covers both Fargate and EC2 launch types, with options for managing configuration secrets.
Deployment Methods
Section titled “Deployment Methods”Choose your preferred deployment method:
Quick Start with Makefile
Section titled “Quick Start with Makefile”The easiest way to deploy DeepIntShield to ECS is using the provided Makefile.
# First, create your config.json file with your DeepIntShield configurationcat > /tmp/deepintshield-config.json <<EOF{ "config_store": { "enabled": true, "type": "postgres", "config": { "host": "your-db-host", "port": "5432", "user": "your-db-user", "password": "your-db-password", "db_name": "deepintshield", "ssl_mode": "disable" } }, "logs_store": { "enabled": true, "type": "postgres", "config": { "host": "your-db-host", "port": "5432", "user": "your-db-user", "password": "your-db-password", "db_name": "deepintshield", "ssl_mode": "disable" } }}EOF
# Deploy with VPC ID (recommended - auto-fetches all subnets)make deploy-ecs \ VPC_ID='vpc-xxx' \ SECURITY_GROUP_IDS='sg-xxx' \ CONFIG_JSON_FILE='/tmp/deepintshield-config.json'
# Deploy with specific subnet IDsmake deploy-ecs \ SUBNET_IDS='subnet-xxx,subnet-yyy' \ SECURITY_GROUP_IDS='sg-xxx' \ CONFIG_JSON_FILE='/tmp/deepintshield-config.json'
# Deploy with EC2 launch type and SSM Parameter Storemake deploy-ecs \ LAUNCH_TYPE=EC2 \ SECRET_BACKEND=ssm \ VPC_ID='vpc-xxx' \ SECURITY_GROUP_IDS='sg-xxx' \ CONFIG_JSON_FILE='/tmp/deepintshield-config.json'
# Deploy with Application Load Balancermake deploy-ecs \ VPC_ID='vpc-xxx' \ SECURITY_GROUP_IDS='sg-xxx' \ TARGET_GROUP_ARN='arn:aws:elasticloadbalancing:...' \ CONFIG_JSON_FILE='/tmp/deepintshield-config.json'
# Deploy without configuration secretmake deploy-ecs \ VPC_ID='vpc-xxx' \ SECURITY_GROUP_IDS='sg-xxx'Available Makefile Parameters
Section titled “Available Makefile Parameters”| Parameter | Default | Description |
|---|---|---|
ECS_CLUSTER_NAME | deepintshield-cluster | Name of the ECS cluster |
ECS_SERVICE_NAME | deepintshield-service | Name of the ECS service |
ECS_TASK_FAMILY | deepintshield-task | Task definition family name |
IMAGE_TAG | latest | DeepIntShield Docker image tag |
LAUNCH_TYPE | FARGATE | Launch type: FARGATE or EC2 |
SECRET_BACKEND | secretsmanager | Secret storage: secretsmanager or ssm |
AWS_REGION | us-east-1 | AWS region |
VPC_ID | (optional*) | VPC ID (auto-fetches all subnets in VPC) |
SUBNET_IDS | (optional*) | Comma-separated subnet IDs (if VPC_ID not provided) |
SECURITY_GROUP_IDS | (required) | Comma-separated security group IDs |
TARGET_GROUP_ARN | (optional) | ALB target group ARN |
CONTAINER_PORT | 8080 | Container port |
SECRET_NAME | deepintshield/config | Secret/parameter name |
CONFIG_JSON_FILE | (optional) | Path to config.json file |
SECRET_ARN | (optional) | Existing secret ARN (skip auto-lookup) |
EXECUTION_ROLE_ARN | (optional) | ECS task execution role ARN |
TASK_ROLE_ARN | (optional) | ECS task role ARN |
Makefile Targets
Section titled “Makefile Targets”list-ecs-network-resources: List available VPCs, subnets and security groups in your AWS region (helpful for first deployment)deploy-ecs: Complete deployment (creates secret if CONFIG_JSON_FILE provided, registers task definition, creates service, waits for stabilization, and shows deployment status)create-ecs-secret: Create/update configuration secret (requires CONFIG_JSON_FILE parameter)register-ecs-task-definition: Register new task definition (with or without secret)create-ecs-service: Create or update ECS serviceupdate-ecs-service: Force new deploymenttail-ecs-logs: Continuously tail CloudWatch logs in real-time (Ctrl+C to exit)ecs-status: Show current service status, running tasks, and recent logsget-ecs-url: Get the public URL/IP to access the service (works with or without load balancer)cleanup-ecs: Remove service and deregister task definitions
Deployment with AWS CLI
Section titled “Deployment with AWS CLI”Deploy DeepIntShield to ECS using direct AWS CLI commands. This section provides step-by-step instructions for both Fargate and EC2 launch types.
1. Configuration Secret
Section titled “1. Configuration Secret”Choose between AWS Secrets Manager or SSM Parameter Store to store your DeepIntShield configuration.
Create a secret containing the DeepIntShield configuration with Postgres backend:
# Create the configuration JSONcat > /tmp/deepintshield-config.json <<EOF{ "config_store": { "enabled": true, "type": "postgres", "config": { "host": "your-postgres-host", "port": "5432", "user": "your-postgres-user", "password": "your-postgres-password", "db_name": "deepintshield", "ssl_mode": "disable" } }, "logs_store": { "enabled": true, "type": "postgres", "config": { "host": "your-postgres-host", "port": "5432", "user": "your-postgres-user", "password": "your-postgres-password", "db_name": "deepintshield", "ssl_mode": "disable" } }}EOF
# Create the secretaws secretsmanager create-secret \ --name deepintshield/config \ --secret-string file:///tmp/deepintshield-config.json \ --region us-east-1
# Get the secret ARN (save this for later)aws secretsmanager describe-secret \ --secret-id deepintshield/config \ --region us-east-1 \ --query 'ARN' \ --output textCreate a parameter containing the DeepIntShield configuration:
# Create the configuration JSONcat > /tmp/deepintshield-config.json <<EOF{ "config_store": { "enabled": true, "type": "postgres", "config": { "host": "your-postgres-host", "port": "5432", "user": "your-postgres-user", "password": "your-postgres-password", "db_name": "deepintshield", "ssl_mode": "disable" } }, "logs_store": { "enabled": true, "type": "postgres", "config": { "host": "your-postgres-host", "port": "5432", "user": "your-postgres-user", "password": "your-postgres-password", "db_name": "deepintshield", "ssl_mode": "disable" } }}EOF
# Create the parameteraws ssm put-parameter \ --name /deepintshield/config \ --value file:///tmp/deepintshield-config.json \ --type SecureString \ --region us-east-1
# Get the parameter ARN (save this for later)aws ssm get-parameter \ --name /deepintshield/config \ --region us-east-1 \ --query 'Parameter.ARN' \ --output text2. Task Definition
Section titled “2. Task Definition”Create a task definition for Fargate with the configuration secret injected:
# Create task definition JSONcat > /tmp/deepintshield-task-definition.json <<EOF{ "family": "deepintshield-task", "networkMode": "awsvpc", "requiresCompatibilities": ["FARGATE"], "cpu": "512", "memory": "1024", "executionRoleArn": "arn:aws:iam::YOUR_ACCOUNT_ID:role/ecsTaskExecutionRole", "containerDefinitions": [ { "name": "deepintshield", "image": "maximhq/deepintshield:latest", "essential": true, "entryPoint": ["/bin/sh", "-c"], "command": ["if [ -n \"$DEEPINTSHIELD_CONFIG\" ]; then echo \"$DEEPINTSHIELD_CONFIG\" > /app/data/config.json; else echo \"ERROR: DEEPINTSHIELD_CONFIG not set\" >&2 && exit 1; fi && exec /app/docker-entrypoint.sh /app/main"], "portMappings": [ { "containerPort": 8080, "protocol": "tcp" } ], "secrets": [ { "name": "DEEPINTSHIELD_CONFIG", "valueFrom": "arn:aws:secretsmanager:us-east-1:YOUR_ACCOUNT_ID:secret:deepintshield/config" } ], "healthCheck": { "command": ["CMD-SHELL", "wget --no-verbose --tries=1 -O /dev/null http://127.0.0.1:8080/health || exit 1"], "interval": 30, "timeout": 5, "retries": 3, "startPeriod": 60 }, "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/ecs/deepintshield-task", "awslogs-region": "us-east-1", "awslogs-stream-prefix": "deepintshield", "awslogs-create-group": "true" } } } ]}EOF
# Register the task definitionaws ecs register-task-definition \ --cli-input-json file:///tmp/deepintshield-task-definition.json \ --region us-east-1# Create task definition JSONcat > /tmp/deepintshield-task-definition.json <<EOF{ "family": "deepintshield-task", "networkMode": "awsvpc", "requiresCompatibilities": ["FARGATE"], "cpu": "512", "memory": "1024", "executionRoleArn": "arn:aws:iam::YOUR_ACCOUNT_ID:role/ecsTaskExecutionRole", "containerDefinitions": [ { "name": "deepintshield", "image": "maximhq/deepintshield:latest", "essential": true, "entryPoint": ["/bin/sh", "-c"], "command": ["if [ -n \"$DEEPINTSHIELD_CONFIG\" ]; then echo \"$DEEPINTSHIELD_CONFIG\" > /app/data/config.json; else echo \"ERROR: DEEPINTSHIELD_CONFIG not set\" >&2 && exit 1; fi && exec /app/docker-entrypoint.sh /app/main"], "portMappings": [ { "containerPort": 8080, "protocol": "tcp" } ], "secrets": [ { "name": "DEEPINTSHIELD_CONFIG", "valueFrom": "arn:aws:ssm:us-east-1:YOUR_ACCOUNT_ID:parameter/deepintshield/config" } ], "healthCheck": { "command": ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1"], "interval": 30, "timeout": 5, "retries": 3, "startPeriod": 60 }, "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/ecs/deepintshield-task", "awslogs-region": "us-east-1", "awslogs-stream-prefix": "deepintshield", "awslogs-create-group": "true" } } } ]}EOF
# Register the task definitionaws ecs register-task-definition \ --cli-input-json file:///tmp/deepintshield-task-definition.json \ --region us-east-13. Create ECS Service
Section titled “3. Create ECS Service”aws ecs create-service \ --cluster deepintshield-cluster \ --service-name deepintshield-service \ --task-definition deepintshield-task \ --desired-count 1 \ --launch-type FARGATE \ --network-configuration "awsvpcConfiguration={subnets=[subnet-xxx,subnet-yyy],securityGroups=[sg-xxx],assignPublicIp=ENABLED}" \ --region us-east-1aws ecs create-service \ --cluster deepintshield-cluster \ --service-name deepintshield-service \ --task-definition deepintshield-task \ --desired-count 1 \ --launch-type FARGATE \ --network-configuration "awsvpcConfiguration={subnets=[subnet-xxx,subnet-yyy],securityGroups=[sg-xxx],assignPublicIp=ENABLED}" \ --load-balancers "targetGroupArn=arn:aws:elasticloadbalancing:us-east-1:YOUR_ACCOUNT_ID:targetgroup/deepintshield-tg/xxx,containerName=deepintshield,containerPort=8080" \ --health-check-grace-period-seconds 60 \ --region us-east-14. Update Service
Section titled “4. Update Service”To deploy a new version or force a redeployment:
aws ecs update-service \ --cluster deepintshield-cluster \ --service deepintshield-service \ --force-new-deployment \ --region us-east-11. Configuration Secret
Section titled “1. Configuration Secret”Choose between AWS Secrets Manager or SSM Parameter Store to store your DeepIntShield configuration.
Create a secret containing the DeepIntShield configuration with Postgres backend:
# Create the configuration JSONcat > /tmp/deepintshield-config.json <<EOF{ "config_store": { "enabled": true, "type": "postgres", "config": { "host": "your-postgres-host", "port": "5432", "user": "your-postgres-user", "password": "your-postgres-password", "db_name": "deepintshield", "ssl_mode": "disable" } }, "logs_store": { "enabled": true, "type": "postgres", "config": { "host": "your-postgres-host", "port": "5432", "user": "your-postgres-user", "password": "your-postgres-password", "db_name": "deepintshield", "ssl_mode": "disable" } }}EOF
# Create the secretaws secretsmanager create-secret \ --name deepintshield/config \ --secret-string file:///tmp/deepintshield-config.json \ --region us-east-1
# Get the secret ARN (save this for later)aws secretsmanager describe-secret \ --secret-id deepintshield/config \ --region us-east-1 \ --query 'ARN' \ --output textCreate a parameter containing the DeepIntShield configuration:
# Create the configuration JSONcat > /tmp/deepintshield-config.json <<EOF{ "config_store": { "enabled": true, "type": "postgres", "config": { "host": "your-postgres-host", "port": "5432", "user": "your-postgres-user", "password": "your-postgres-password", "db_name": "deepintshield", "ssl_mode": "disable" } }, "logs_store": { "enabled": true, "type": "postgres", "config": { "host": "your-postgres-host", "port": "5432", "user": "your-postgres-user", "password": "your-postgres-password", "db_name": "deepintshield", "ssl_mode": "disable" } }}EOF
# Create the parameteraws ssm put-parameter \ --name /deepintshield/config \ --value file:///tmp/deepintshield-config.json \ --type SecureString \ --region us-east-1
# Get the parameter ARN (save this for later)aws ssm get-parameter \ --name /deepintshield/config \ --region us-east-1 \ --query 'Parameter.ARN' \ --output text2. Task Definition
Section titled “2. Task Definition”Create a task definition for EC2 launch type with the configuration secret injected:
# Create task definition JSONcat > /tmp/deepintshield-task-definition.json <<EOF{ "family": "deepintshield-task", "networkMode": "awsvpc", "requiresCompatibilities": ["EC2"], "executionRoleArn": "arn:aws:iam::YOUR_ACCOUNT_ID:role/ecsTaskExecutionRole", "containerDefinitions": [ { "name": "deepintshield", "image": "maximhq/deepintshield:latest", "cpu": 256, "memory": 512, "essential": true, "entryPoint": ["/bin/sh", "-c"], "command": ["if [ -n \"$DEEPINTSHIELD_CONFIG\" ]; then echo \"$DEEPINTSHIELD_CONFIG\" > /app/data/config.json; else echo \"ERROR: DEEPINTSHIELD_CONFIG not set\" >&2 && exit 1; fi && exec /app/docker-entrypoint.sh /app/main"], "portMappings": [ { "containerPort": 8080, "protocol": "tcp" } ], "secrets": [ { "name": "DEEPINTSHIELD_CONFIG", "valueFrom": "arn:aws:secretsmanager:us-east-1:YOUR_ACCOUNT_ID:secret:deepintshield/config" } ], "healthCheck": { "command": ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1"], "interval": 30, "timeout": 5, "retries": 3, "startPeriod": 60 }, "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/ecs/deepintshield-task", "awslogs-region": "us-east-1", "awslogs-stream-prefix": "deepintshield", "awslogs-create-group": "true" } } } ]}EOF
# Register the task definitionaws ecs register-task-definition \ --cli-input-json file:///tmp/deepintshield-task-definition.json \ --region us-east-1# Create task definition JSONcat > /tmp/deepintshield-task-definition.json <<EOF{ "family": "deepintshield-task", "networkMode": "awsvpc", "requiresCompatibilities": ["EC2"], "executionRoleArn": "arn:aws:iam::YOUR_ACCOUNT_ID:role/ecsTaskExecutionRole", "containerDefinitions": [ { "name": "deepintshield", "image": "maximhq/deepintshield:latest", "cpu": 256, "memory": 512, "essential": true, "entryPoint": ["/bin/sh", "-c"], "command": ["if [ -n \"$DEEPINTSHIELD_CONFIG\" ]; then echo \"$DEEPINTSHIELD_CONFIG\" > /app/data/config.json; else echo \"ERROR: DEEPINTSHIELD_CONFIG not set\" >&2 && exit 1; fi && exec /app/docker-entrypoint.sh /app/main"], "portMappings": [ { "containerPort": 8080, "protocol": "tcp" } ], "secrets": [ { "name": "DEEPINTSHIELD_CONFIG", "valueFrom": "arn:aws:ssm:us-east-1:YOUR_ACCOUNT_ID:parameter/deepintshield/config" } ], "healthCheck": { "command": ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1"], "interval": 30, "timeout": 5, "retries": 3, "startPeriod": 60 }, "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/ecs/deepintshield-task", "awslogs-region": "us-east-1", "awslogs-stream-prefix": "deepintshield", "awslogs-create-group": "true" } } } ]}EOF
# Register the task definitionaws ecs register-task-definition \ --cli-input-json file:///tmp/deepintshield-task-definition.json \ --region us-east-13. Create ECS Service
Section titled “3. Create ECS Service”aws ecs create-service \ --cluster deepintshield-cluster \ --service-name deepintshield-service \ --task-definition deepintshield-task \ --desired-count 1 \ --launch-type EC2 \ --network-configuration "awsvpcConfiguration={subnets=[subnet-xxx,subnet-yyy],securityGroups=[sg-xxx]}" \ --region us-east-1aws ecs create-service \ --cluster deepintshield-cluster \ --service-name deepintshield-service \ --task-definition deepintshield-task \ --desired-count 1 \ --launch-type EC2 \ --network-configuration "awsvpcConfiguration={subnets=[subnet-xxx,subnet-yyy],securityGroups=[sg-xxx]}" \ --load-balancers "targetGroupArn=arn:aws:elasticloadbalancing:us-east-1:YOUR_ACCOUNT_ID:targetgroup/deepintshield-tg/xxx,containerName=deepintshield,containerPort=8080" \ --health-check-grace-period-seconds 60 \ --region us-east-14. Update Service
Section titled “4. Update Service”To deploy a new version or force a redeployment:
aws ecs update-service \ --cluster deepintshield-cluster \ --service deepintshield-service \ --force-new-deployment \ --region us-east-1CloudFormation Deployment
Section titled “CloudFormation Deployment”Deploy DeepIntShield to ECS using AWS CloudFormation for infrastructure as code management.
CloudFormation Template
Section titled “CloudFormation Template”The template (cloudformation/ecs-deployment.yaml):
AWSTemplateFormatVersion: '2010-09-09'Description: 'Deploy DeepIntShield service on ECS'
Parameters: ClusterName: Type: String Default: deepintshield-cluster Description: Name of the ECS cluster
ServiceName: Type: String Default: deepintshield-service Description: Name of the ECS service
TaskFamily: Type: String Default: deepintshield-task Description: Task definition family name
ImageTag: Type: String Default: latest Description: DeepIntShield Docker image tag
LaunchType: Type: String Default: FARGATE AllowedValues: - FARGATE - EC2 Description: ECS launch type
ContainerPort: Type: Number Default: 8080 Description: Container port
DesiredCount: Type: Number Default: 1 Description: Desired number of tasks
VpcId: Type: AWS::EC2::VPC::Id Description: VPC ID where the service will run
SubnetIds: Type: List<AWS::EC2::Subnet::Id> Description: Subnet IDs for the service (use public subnets for direct access)
SecurityGroupIds: Type: List<AWS::EC2::SecurityGroup::Id> Description: Security group IDs (must allow inbound on ContainerPort)
ConfigSecretArn: Type: String Default: '' Description: (Optional) ARN of Secrets Manager secret or SSM parameter containing config.json
ExecutionRoleArn: Type: String Default: '' Description: (Optional) ECS task execution role ARN (will create default if not provided)
TaskRoleArn: Type: String Default: '' Description: (Optional) ECS task role ARN
TargetGroupArn: Type: String Default: '' Description: (Optional) ALB target group ARN for load balancing
AssignPublicIp: Type: String Default: ENABLED AllowedValues: - ENABLED - DISABLED Description: Assign public IP to tasks (ENABLED for direct access without load balancer)
Conditions: IsFargate: !Equals [!Ref LaunchType, FARGATE] HasSecret: !Not [!Equals [!Ref ConfigSecretArn, '']] HasExecutionRole: !Not [!Equals [!Ref ExecutionRoleArn, '']] HasTaskRole: !Not [!Equals [!Ref TaskRoleArn, '']] HasTargetGroup: !Not [!Equals [!Ref TargetGroupArn, '']] CreateExecutionRole: !And - !Not [!Condition HasExecutionRole] - !Condition IsFargate
Resources: # CloudWatch Log Group LogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub '/ecs/${TaskFamily}' RetentionInDays: 7
# ECS Task Execution Role (created only if not provided and using Fargate) TaskExecutionRole: Type: AWS::IAM::Role Condition: CreateExecutionRole Properties: RoleName: !Sub '${ServiceName}-execution-role' AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: ecs-tasks.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy Policies: - PolicyName: SecretAccess PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - secretsmanager:GetSecretValue - ssm:GetParameter - ssm:GetParameters Resource: - !Sub 'arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:deepintshield/*' - !Sub 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/deepintshield/*' - Effect: Allow Action: - kms:Decrypt Resource: '*'
# ECS Task Definition TaskDefinition: Type: AWS::ECS::TaskDefinition Properties: Family: !Ref TaskFamily NetworkMode: awsvpc RequiresCompatibilities: - !Ref LaunchType Cpu: !If [IsFargate, '512', '256'] Memory: !If [IsFargate, '1024', '512'] ExecutionRoleArn: !If - HasExecutionRole - !Ref ExecutionRoleArn - !If - CreateExecutionRole - !GetAtt TaskExecutionRole.Arn - !Ref AWS::NoValue TaskRoleArn: !If [HasTaskRole, !Ref TaskRoleArn, !Ref AWS::NoValue] ContainerDefinitions: - Name: deepintshield Image: !Sub 'maximhq/deepintshield:${ImageTag}' Essential: true EntryPoint: !If - HasSecret - - /bin/sh - -c - !Ref AWS::NoValue Command: !If - HasSecret - - 'if [ -n "$DEEPINTSHIELD_CONFIG" ]; then echo "$DEEPINTSHIELD_CONFIG" > /app/data/config.json; else echo "ERROR: DEEPINTSHIELD_CONFIG not set" >&2 && exit 1; fi && exec /app/docker-entrypoint.sh /app/main' - !Ref AWS::NoValue PortMappings: - ContainerPort: !Ref ContainerPort Protocol: tcp Environment: [] Secrets: !If - HasSecret - - Name: DEEPINTSHIELD_CONFIG ValueFrom: !Ref ConfigSecretArn - !Ref AWS::NoValue HealthCheck: Command: - CMD-SHELL - !Sub 'wget --no-verbose --tries=1 --spider http://localhost:${ContainerPort}/health || exit 1' Interval: 30 Timeout: 5 Retries: 3 StartPeriod: 60 LogConfiguration: LogDriver: awslogs Options: awslogs-group: !Ref LogGroup awslogs-region: !Ref AWS::Region awslogs-stream-prefix: deepintshield
# ECS Service Service: Type: AWS::ECS::Service Properties: ServiceName: !Ref ServiceName Cluster: !Ref ClusterName TaskDefinition: !Ref TaskDefinition DesiredCount: !Ref DesiredCount LaunchType: !Ref LaunchType NetworkConfiguration: AwsvpcConfiguration: Subnets: !Ref SubnetIds SecurityGroups: !Ref SecurityGroupIds AssignPublicIp: !Ref AssignPublicIp LoadBalancers: !If - HasTargetGroup - - ContainerName: deepintshield ContainerPort: !Ref ContainerPort TargetGroupArn: !Ref TargetGroupArn - !Ref AWS::NoValue HealthCheckGracePeriodSeconds: !If [HasTargetGroup, 60, !Ref AWS::NoValue]
Outputs: ServiceName: Description: ECS Service Name Value: !Ref Service Export: Name: !Sub '${AWS::StackName}-ServiceName'
TaskDefinitionArn: Description: Task Definition ARN Value: !Ref TaskDefinition Export: Name: !Sub '${AWS::StackName}-TaskDefinitionArn'
LogGroupName: Description: CloudWatch Log Group Value: !Ref LogGroup Export: Name: !Sub '${AWS::StackName}-LogGroupName'
ExecutionRoleArn: Condition: CreateExecutionRole Description: Created Task Execution Role ARN Value: !GetAtt TaskExecutionRole.Arn Export: Name: !Sub '${AWS::StackName}-ExecutionRoleArn'Deploy with CloudFormation
Section titled “Deploy with CloudFormation”Deploy without configuration secret:
aws cloudformation create-stack \ --stack-name deepintshield-ecs-stack \ --template-body file://cloudformation/ecs-deployment.yaml \ --parameters \ ParameterKey=VpcId,ParameterValue=vpc-xxx \ ParameterKey=SubnetIds,ParameterValue="subnet-xxx\,subnet-yyy" \ ParameterKey=SecurityGroupIds,ParameterValue="sg-xxx" \ --capabilities CAPABILITY_NAMED_IAM \ --region us-east-1
# Wait for stack creationaws cloudformation wait stack-create-complete \ --stack-name deepintshield-ecs-stack \ --region us-east-1
# Get service detailsaws cloudformation describe-stacks \ --stack-name deepintshield-ecs-stack \ --region us-east-1 \ --query 'Stacks[0].Outputs'Deploy with Secrets Manager:
First, create the secret:
aws secretsmanager create-secret \ --name deepintshield/config \ --secret-string file://config.json \ --region us-east-1
# Get the secret ARNSECRET_ARN=$(aws secretsmanager describe-secret \ --secret-id deepintshield/config \ --region us-east-1 \ --query 'ARN' \ --output text)Then deploy with the secret:
aws cloudformation create-stack \ --stack-name deepintshield-ecs-stack \ --template-body file://cloudformation/ecs-deployment.yaml \ --parameters \ ParameterKey=VpcId,ParameterValue=vpc-xxx \ ParameterKey=SubnetIds,ParameterValue="subnet-xxx\,subnet-yyy" \ ParameterKey=SecurityGroupIds,ParameterValue="sg-xxx" \ ParameterKey=ConfigSecretArn,ParameterValue=$SECRET_ARN \ --capabilities CAPABILITY_NAMED_IAM \ --region us-east-1aws cloudformation create-stack \ --stack-name deepintshield-ecs-stack \ --template-body file://cloudformation/ecs-deployment.yaml \ --parameters \ ParameterKey=VpcId,ParameterValue=vpc-xxx \ ParameterKey=SubnetIds,ParameterValue="subnet-xxx\,subnet-yyy" \ ParameterKey=SecurityGroupIds,ParameterValue="sg-xxx" \ ParameterKey=TargetGroupArn,ParameterValue=arn:aws:elasticloadbalancing:... \ ParameterKey=AssignPublicIp,ParameterValue=DISABLED \ --capabilities CAPABILITY_NAMED_IAM \ --region us-east-1aws cloudformation create-stack \ --stack-name deepintshield-ecs-stack \ --template-body file://cloudformation/ecs-deployment.yaml \ --parameters \ ParameterKey=VpcId,ParameterValue=vpc-xxx \ ParameterKey=SubnetIds,ParameterValue="subnet-xxx\,subnet-yyy" \ ParameterKey=SecurityGroupIds,ParameterValue="sg-xxx" \ ParameterKey=LaunchType,ParameterValue=EC2 \ ParameterKey=ExecutionRoleArn,ParameterValue=arn:aws:iam::ACCOUNT:role/ecsTaskExecutionRole \ --capabilities CAPABILITY_NAMED_IAM \ --region us-east-1Update Stack
Section titled “Update Stack”To update your deployment (e.g., change image tag or configuration):
# Update the stackaws cloudformation update-stack \ --stack-name deepintshield-ecs-stack \ --template-body file://cloudformation/ecs-deployment.yaml \ --parameters \ ParameterKey=VpcId,UsePreviousValue=true \ ParameterKey=SubnetIds,UsePreviousValue=true \ ParameterKey=SecurityGroupIds,UsePreviousValue=true \ ParameterKey=ImageTag,ParameterValue=v1.2.0 \ --capabilities CAPABILITY_NAMED_IAM \ --region us-east-1
# Wait for update to completeaws cloudformation wait stack-update-complete \ --stack-name deepintshield-ecs-stack \ --region us-east-1Get Service URL
Section titled “Get Service URL”After deployment, get your service URL:
# Get the task public IP (without load balancer)TASK_ARN=$(aws ecs list-tasks \ --cluster deepintshield-cluster \ --service-name deepintshield-service \ --region us-east-1 \ --query 'taskArns[0]' \ --output text)
ENI_ID=$(aws ecs describe-tasks \ --cluster deepintshield-cluster \ --tasks $TASK_ARN \ --region us-east-1 \ --query 'tasks[0].attachments[0].details[?name==`networkInterfaceId`].value' \ --output text)
PUBLIC_IP=$(aws ec2 describe-network-interfaces \ --network-interface-ids $ENI_ID \ --region us-east-1 \ --query 'NetworkInterfaces[0].Association.PublicIp' \ --output text)
echo "Service URL: http://$PUBLIC_IP:8080"echo "Health check: http://$PUBLIC_IP:8080/health"
# Test the servicecurl http://$PUBLIC_IP:8080/healthMonitor Logs
Section titled “Monitor Logs”# Tail logsaws logs tail /ecs/deepintshield-task --follow --region us-east-1
# View recent logsLOG_STREAM=$(aws logs describe-log-streams \ --log-group-name /ecs/deepintshield-task \ --order-by LastEventTime \ --descending \ --max-items 1 \ --region us-east-1 \ --query 'logStreams[0].logStreamName' \ --output text)
aws logs get-log-events \ --log-group-name /ecs/deepintshield-task \ --log-stream-name $LOG_STREAM \ --region us-east-1Delete Stack
Section titled “Delete Stack”To remove all resources:
aws cloudformation delete-stack \ --stack-name deepintshield-ecs-stack \ --region us-east-1
# Wait for deletionaws cloudformation wait stack-delete-complete \ --stack-name deepintshield-ecs-stack \ --region us-east-1CloudFormation Parameters Reference
Section titled “CloudFormation Parameters Reference”| Parameter | Default | Required | Description |
|---|---|---|---|
ClusterName | deepintshield-cluster | No | ECS cluster name (must exist) |
ServiceName | deepintshield-service | No | ECS service name |
TaskFamily | deepintshield-task | No | Task definition family |
ImageTag | latest | No | Docker image tag |
LaunchType | FARGATE | No | FARGATE or EC2 |
ContainerPort | 8080 | No | Container port |
DesiredCount | 1 | No | Number of tasks |
VpcId | - | Yes | VPC ID |
SubnetIds | - | Yes | Comma-separated subnet IDs |
SecurityGroupIds | - | Yes | Comma-separated security group IDs |
ConfigSecretArn | (empty) | No | Secret/parameter ARN |
ExecutionRoleArn | (empty) | No | Task execution role ARN |
TaskRoleArn | (empty) | No | Task role ARN |
TargetGroupArn | (empty) | No | ALB target group ARN |
AssignPublicIp | ENABLED | No | Assign public IP to tasks |
IAM Permissions
Section titled “IAM Permissions”Task Execution Role
Section titled “Task Execution Role”The task execution role (ecsTaskExecutionRole) needs the following permissions:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ecr:GetAuthorizationToken", "ecr:BatchCheckLayerAvailability", "ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "arn:aws:logs:*:*:log-group:/ecs/deepintshield-task:*" }, { "Effect": "Allow", "Action": [ "secretsmanager:GetSecretValue" ], "Resource": "arn:aws:secretsmanager:us-east-1:YOUR_ACCOUNT_ID:secret:deepintshield/config*" } ]}{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ecr:GetAuthorizationToken", "ecr:BatchCheckLayerAvailability", "ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "arn:aws:logs:*:*:log-group:/ecs/deepintshield-task:*" }, { "Effect": "Allow", "Action": [ "ssm:GetParameters", "ssm:GetParameter" ], "Resource": "arn:aws:ssm:us-east-1:YOUR_ACCOUNT_ID:parameter/deepintshield/config" }, { "Effect": "Allow", "Action": [ "kms:Decrypt" ], "Resource": "arn:aws:kms:us-east-1:YOUR_ACCOUNT_ID:key/YOUR_KMS_KEY_ID" } ]}Accessing Your Service
Section titled “Accessing Your Service”Without Load Balancer
Section titled “Without Load Balancer”When deployed without a load balancer, the ECS task gets a public IP address. You can find it using AWS CLI:
# Get the public IP address of your running taskaws ec2 describe-network-interfaces \ --network-interface-ids $(aws ecs describe-tasks \ --cluster deepintshield-cluster \ --tasks $(aws ecs list-tasks \ --cluster deepintshield-cluster \ --service-name deepintshield-service \ --region us-east-1 \ --query 'taskArns[0]' \ --output text) \ --region us-east-1 \ --query 'tasks[0].attachments[0].details[?name==`networkInterfaceId`].value' \ --output text) \ --region us-east-1 \ --query 'NetworkInterfaces[0].Association.PublicIp' \ --output textTesting your deployment:
# Test health endpoint (replace YOUR_PUBLIC_IP with the IP from above)curl http://YOUR_PUBLIC_IP:8080/health
# Expected response{"status":"ok"}With Load Balancer
Section titled “With Load Balancer”If you deployed with TARGET_GROUP_ARN, your service is accessible via the load balancer’s DNS name:
# Get the load balancer DNS name (replace YOUR_TARGET_GROUP_ARN with your actual ARN)aws elbv2 describe-load-balancers \ --load-balancer-arns $(aws elbv2 describe-target-groups \ --target-group-arns YOUR_TARGET_GROUP_ARN \ --region us-east-1 \ --query 'TargetGroups[0].LoadBalancerArns[0]' \ --output text) \ --region us-east-1 \ --query 'LoadBalancers[0].DNSName' \ --output text
# Test via load balancer (replace YOUR_ALB_DNS with the DNS from above)curl http://YOUR_ALB_DNS/healthThe load balancer provides:
- ✅ Stable DNS endpoint
- ✅ SSL/TLS termination (if configured)
- ✅ Health checks with automatic failover
- ✅ Multiple task load balancing
Monitoring and Logs
Section titled “Monitoring and Logs”Tail Logs (Makefile)
Section titled “Tail Logs (Makefile)”The easiest way to monitor your deployment logs:
# Tail logs in real-time (press Ctrl+C to exit)make tail-ecs-logs
# Check service status and recent logsmake ecs-statusView Logs (AWS CLI)
Section titled “View Logs (AWS CLI)”# Tail logs using AWS CLI v2 (recommended)aws logs tail /ecs/deepintshield-task --follow --region us-east-1
# Get log stream namesaws logs describe-log-streams \ --log-group-name /ecs/deepintshield-task \ --order-by LastEventTime \ --descending \ --max-items 5 \ --region us-east-1
# View logs from a specific streamaws logs get-log-events \ --log-group-name /ecs/deepintshield-task \ --log-stream-name deepintshield/deepintshield/TASK_ID \ --region us-east-1Check Service Status
Section titled “Check Service Status”# Describe serviceaws ecs describe-services \ --cluster deepintshield-cluster \ --services deepintshield-service \ --region us-east-1
# List tasksaws ecs list-tasks \ --cluster deepintshield-cluster \ --service-name deepintshield-service \ --region us-east-1
# Describe taskaws ecs describe-tasks \ --cluster deepintshield-cluster \ --tasks TASK_ARN \ --region us-east-1Cleanup
Section titled “Cleanup”To remove all ECS resources:
# Using Makefilemake cleanup-ecs
# Or manually# Delete serviceaws ecs update-service \ --cluster deepintshield-cluster \ --service deepintshield-service \ --desired-count 0 \ --region us-east-1
aws ecs delete-service \ --cluster deepintshield-cluster \ --service deepintshield-service \ --region us-east-1
# Deregister task definitionsaws ecs list-task-definitions \ --family-prefix deepintshield-task \ --region us-east-1 \ --query 'taskDefinitionArns[]' \ --output text | \ xargs -n 1 aws ecs deregister-task-definition --task-definition --region us-east-1
# Delete secret (optional)aws secretsmanager delete-secret \ --secret-id deepintshield/config \ --force-delete-without-recovery \ --region us-east-1
# Or delete SSM parameter (optional)aws ssm delete-parameter \ --name /deepintshield/config \ --region us-east-1