AWS Application Deployment Guide
Overview
This guide covers best practices for deploying applications on AWS using EC2, ECS, Lambda, and CI/CD pipelines. We'll explore scalable and reliable deployment strategies, leveraging Infrastructure as Code (IaC) with AWS CDK and CloudFormation, while addressing load balancing, auto-scaling, security, and cost optimization.
Details
EC2 Deployment Patterns
EC2 instances are ideal for applications requiring full OS control or specific hardware configurations. AWS CDK enables infrastructure management as code, ensuring reproducible deployments.
VPC Configuration Best Practices:
- Restrict default security group inbound/outbound traffic
- Properly separate private and public subnets
- Manage outbound connections via NAT gateways or NAT instances
Instance Configuration:
- Automate initial setup using CloudFormation Init (cfn-init)
- Install software and start services via user data scripts
- Ensure availability with Auto Scaling Groups
ECS/Fargate Container Deployment
ECS is a fully managed container orchestration service. Fargate enables serverless container execution without managing infrastructure.
Launch Type Selection:
- EC2 Launch Type: Full control over instances and task placement
- Fargate Launch Type: AWS manages infrastructure, serverless execution
- ECS Anywhere: Run tasks on customer-managed infrastructure
Task Definition Optimization:
- Use lightweight base images
- Minimize container image layers
- Allocate appropriate CPU and memory (Fargate supports 1/4 vCPU to 16 vCPU, 512MB to 120GB memory)
Auto Scaling Configuration:
- Target tracking with Application Auto Scaling
- Schedule-based scaling
- Custom metrics-based scaling
Lambda Serverless Deployment
Lambda is optimal for event-driven architectures and short-running tasks. 2025 best practices emphasize cold start optimization.
Cold Start Optimization:
- Minimize package size (import only required SDK components)
- Choose optimal runtime (Python, Node.js, Go achieve <500ms cold starts)
- Use Provisioned Concurrency for pre-initialization
- Optimize memory configuration (leverage AWS Lambda Power Tuning tool)
Code Initialization Optimization:
- Design stateless functions
- Remove unnecessary external dependencies
- Minimize static variables
CI/CD Pipeline Construction
Build modern CI/CD pipelines combining AWS CodePipeline with GitHub Actions.
Native AWS CI/CD Tools:
- AWS CodePipeline: Pipeline orchestration
- AWS CodeBuild: Build and test execution
- AWS CodeDeploy: Automated deployment to EC2, ECS, Lambda, on-premises
Secure Authentication:
- Authentication using IAM roles and OIDC
- Secret management with AWS Secrets Manager or Parameter Store
- Establish trust relationship between GitHub Actions and AWS
Deployment Strategies:
- Blue/Green deployment
- Canary deployment (gradual rollout)
- Rolling deployment
- Immutable deployment
Infrastructure as Code (CDK/CloudFormation)
Define type-safe, reusable infrastructure using AWS CDK.
CDK Best Practices:
- Design for construct reusability
- Environment-specific configuration management (dev, staging, prod)
- Proper stack dependency management
- Gradual feature introduction via Feature Flags
CloudFormation Templates:
- Template splitting and reuse
- Leverage parameters and mappings
- Use cross-stack references
- Ensure infrastructure consistency with drift detection
Load Balancing and Auto Scaling
Configure for high availability and elasticity.
Load Balancer Selection:
- Application Load Balancer (ALB): HTTP/HTTPS traffic, path-based routing
- Network Load Balancer (NLB): TCP/UDP traffic, ultra-low latency
- Classic Load Balancer: Legacy applications (deprecated for new use)
Auto Scaling Configuration:
- Target tracking scaling policies
- Step scaling policies
- Predictive scaling
- Schedule-based scaling
Availability Zone Distribution:
- Distribute across minimum 3 AZs
- Enable cross-zone load balancing
- Configure appropriate health checks
Security Best Practices
Ensure application and infrastructure security.
Network Security:
- Principle of least privilege with security groups
- Additional network layer protection with NACLs
- Private connections via VPC endpoints
- Web application protection with AWS WAF
Access Management:
- Minimal permission IAM roles and policies
- Use temporary credentials
- Enforce MFA
- Audit logging with CloudTrail
Data Protection:
- Encryption at rest (EBS, S3, RDS)
- Encryption in transit (TLS/SSL)
- Key management with AWS KMS
- Backup and disaster recovery planning
Cost Optimization Strategies
Efficient resource usage and cost reduction methods.
Instance Optimization:
- Select appropriate instance types
- Leverage Reserved Instances and Savings Plans
- Strategic use of Spot Instances
- Automatic shutdown of unused resources
Resource Efficiency:
- Reduce waste with on-demand scaling
- Reduce compute load with caching
- Optimize data transfer costs
- Manage old data with lifecycle policies
Monitoring and Optimization:
- Cost analysis with AWS Cost Explorer
- Review recommendations from Trusted Advisor
- Cost allocation with tagging
- Prevent cost overruns with Budget Alerts
Pros & Cons
Pros
- Scalability: Automatic resource adjustment based on demand
- High Availability: Fault tolerance through multi-AZ configuration
- Automation: Rapid deployment via CI/CD pipelines
- Security: Leverage AWS security best practices
- Cost Efficiency: Pay-as-you-go, optimization options
- Managed Services: Reduced operational burden
- Global Deployment: Easy deployment to regions worldwide
Cons
- Learning Curve: Initial learning cost due to AWS service complexity
- Vendor Lock-in: Dependency on AWS-specific services
- Cost Management: Unexpected cost increases from improper configuration
- Network Latency: Inter-region communication delays
- Complexity: Microservices architecture management overhead
References
- AWS Official Documentation
- AWS Well-Architected Framework
- AWS CDK Developer Guide
- AWS DevOps Blog
- AWS Samples GitHub
- AWS Skill Builder
Examples
EC2 Application Deployment (CDK)
import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as autoscaling from 'aws-cdk-lib/aws-autoscaling';
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2';
export class EC2AppStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Create VPC
const vpc = new ec2.Vpc(this, 'AppVPC', {
maxAzs: 3,
natGateways: 2,
subnetConfiguration: [
{
cidrMask: 24,
name: 'Public',
subnetType: ec2.SubnetType.PUBLIC,
},
{
cidrMask: 24,
name: 'Private',
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
},
],
});
// Security Group
const webSg = new ec2.SecurityGroup(this, 'WebSecurityGroup', {
vpc,
description: 'Security group for web servers',
allowAllOutbound: true,
});
webSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP');
webSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS');
// IAM Role
const role = new iam.Role(this, 'InstanceRole', {
assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'),
],
});
// Launch Template
const launchTemplate = new ec2.LaunchTemplate(this, 'LaunchTemplate', {
instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
machineImage: ec2.MachineImage.latestAmazonLinux2(),
securityGroup: webSg,
role: role,
userData: ec2.UserData.forLinux(),
});
// User Data Script
launchTemplate.userData?.addCommands(
'yum update -y',
'yum install -y httpd',
'systemctl start httpd',
'systemctl enable httpd',
'echo "<h1>Hello from AWS EC2</h1>" > /var/www/html/index.html'
);
// Auto Scaling Group
const asg = new autoscaling.AutoScalingGroup(this, 'ASG', {
vpc,
launchTemplate,
minCapacity: 2,
maxCapacity: 10,
desiredCapacity: 3,
vpcSubnets: {
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
},
healthCheck: autoscaling.HealthCheck.elb({
grace: cdk.Duration.seconds(300),
}),
});
// Application Load Balancer
const alb = new elbv2.ApplicationLoadBalancer(this, 'ALB', {
vpc,
internetFacing: true,
securityGroup: webSg,
});
const listener = alb.addListener('Listener', {
port: 80,
open: true,
});
listener.addTargets('Target', {
port: 80,
targets: [asg],
healthCheck: {
path: '/',
interval: cdk.Duration.seconds(30),
},
});
// Auto Scaling Policies
asg.scaleOnCpuUtilization('CpuScaling', {
targetUtilizationPercent: 70,
});
asg.scaleOnRequestCount('RequestScaling', {
targetRequestsPerMinute: 1000,
});
// Outputs
new cdk.CfnOutput(this, 'LoadBalancerDNS', {
value: alb.loadBalancerDnsName,
description: 'Load balancer DNS name',
});
}
}
ECS Fargate Service Deployment (CDK)
import * as cdk from 'aws-cdk-lib';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ecsPatterns from 'aws-cdk-lib/aws-ecs-patterns';
export class EcsFargateStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Create ECS Cluster
const cluster = new ecs.Cluster(this, 'Cluster', {
clusterName: 'app-cluster',
containerInsights: true,
});
// Create Fargate Service with ALB
const fargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'Service', {
cluster,
cpu: 512,
memoryLimitMiB: 1024,
desiredCount: 3,
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry('nginx:latest'),
containerPort: 80,
environment: {
ENVIRONMENT: 'production',
},
},
publicLoadBalancer: true,
domainName: 'app.example.com',
domainZone: route53.HostedZone.fromLookup(this, 'Zone', {
domainName: 'example.com',
}),
certificate: certificatemanager.Certificate.fromCertificateArn(
this,
'Certificate',
'arn:aws:acm:region:account:certificate/certificate-id'
),
});
// Auto Scaling Configuration
const scaling = fargateService.service.autoScaleTaskCount({
minCapacity: 2,
maxCapacity: 20,
});
scaling.scaleOnCpuUtilization('CpuScaling', {
targetUtilizationPercent: 50,
});
scaling.scaleOnMemoryUtilization('MemoryScaling', {
targetUtilizationPercent: 70,
});
// Schedule-based Scaling
scaling.scaleOnSchedule('BusinessHours', {
schedule: autoscaling.Schedule.cron({
hour: '8',
minute: '0',
weekDay: '1-5',
}),
minCapacity: 10,
maxCapacity: 20,
});
scaling.scaleOnSchedule('OffHours', {
schedule: autoscaling.Schedule.cron({
hour: '20',
minute: '0',
weekDay: '1-5',
}),
minCapacity: 2,
maxCapacity: 5,
});
// CloudWatch Alarms
const cpuAlarm = fargateService.service.metricCpuUtilization().createAlarm(this, 'CpuAlarm', {
threshold: 80,
evaluationPeriods: 3,
datapointsToAlarm: 2,
});
// Outputs
new cdk.CfnOutput(this, 'ServiceURL', {
value: `https://${fargateService.loadBalancer.loadBalancerDnsName}`,
});
}
}
Lambda Function Deployment (CDK)
import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';
export class LambdaApiStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Create Lambda Function (Cold Start Optimized)
const apiFunction = new lambda.Function(this, 'ApiFunction', {
runtime: lambda.Runtime.PYTHON_3_11, // Fast startup time
code: lambda.Code.fromAsset('lambda'),
handler: 'app.handler',
memorySize: 1024, // Optimized memory size
timeout: cdk.Duration.seconds(30),
environment: {
ENVIRONMENT: 'production',
},
architecture: lambda.Architecture.ARM_64, // Cost-efficient architecture
tracing: lambda.Tracing.ACTIVE,
});
// Provisioned Concurrency (Production)
const alias = new lambda.Alias(this, 'ProdAlias', {
aliasName: 'prod',
version: apiFunction.currentVersion,
provisionedConcurrentExecutions: 5, // 5 pre-initialized environments
});
// Create API Gateway
const api = new apigateway.RestApi(this, 'Api', {
restApiName: 'service-api',
deployOptions: {
stageName: 'prod',
throttlingBurstLimit: 5000,
throttlingRateLimit: 1000,
tracingEnabled: true,
loggingLevel: apigateway.MethodLoggingLevel.INFO,
},
});
// Configure API Endpoints
const integration = new apigateway.LambdaIntegration(alias, {
requestTemplates: { 'application/json': '{ "statusCode": "200" }' },
});
api.root.addMethod('GET', integration);
const items = api.root.addResource('items');
items.addMethod('GET', integration);
items.addMethod('POST', integration);
const item = items.addResource('{id}');
item.addMethod('GET', integration);
item.addMethod('PUT', integration);
item.addMethod('DELETE', integration);
// CloudWatch Dashboard
const dashboard = new cloudwatch.Dashboard(this, 'ApiDashboard', {
dashboardName: 'lambda-api-dashboard',
});
dashboard.addWidgets(
new cloudwatch.GraphWidget({
title: 'Lambda Performance',
left: [apiFunction.metricInvocations()],
right: [apiFunction.metricDuration()],
}),
new cloudwatch.GraphWidget({
title: 'Lambda Errors',
left: [apiFunction.metricErrors()],
right: [apiFunction.metricThrottles()],
}),
);
// Alarm Configuration
new cloudwatch.Alarm(this, 'ErrorAlarm', {
metric: apiFunction.metricErrors(),
threshold: 10,
evaluationPeriods: 2,
treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING,
});
// Outputs
new cdk.CfnOutput(this, 'ApiUrl', {
value: api.url,
description: 'API Gateway URL',
});
}
}
CI/CD Pipeline (GitHub Actions + CDK)
import * as cdk from 'aws-cdk-lib';
import * as pipelines from 'aws-cdk-lib/pipelines';
import * as codebuild from 'aws-cdk-lib/aws-codebuild';
export class PipelineStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Create CodePipeline
const pipeline = new pipelines.CodePipeline(this, 'Pipeline', {
pipelineName: 'AppDeploymentPipeline',
synth: new pipelines.ShellStep('Synth', {
input: pipelines.CodePipelineSource.connection('org/repo', 'main', {
connectionArn: 'arn:aws:codestar-connections:region:account:connection/id',
}),
commands: [
'npm ci',
'npm run build',
'npm run test',
'npx cdk synth',
],
primaryOutputDirectory: 'cdk.out',
}),
// CodeBuild Default Settings
codeBuildDefaults: {
buildEnvironment: {
computeType: codebuild.ComputeType.MEDIUM,
privileged: true,
},
partialBuildSpec: codebuild.BuildSpec.fromObject({
version: '0.2',
phases: {
install: {
commands: [
'n stable',
'npm install -g aws-cdk',
],
},
},
cache: {
paths: ['node_modules/**/*'],
},
}),
},
});
// Development Stage
pipeline.addStage(new AppStage(this, 'Dev', {
env: { account: '111111111111', region: 'us-east-1' },
}), {
pre: [
new pipelines.ShellStep('Lint', {
commands: ['npm run lint'],
}),
],
post: [
new pipelines.ShellStep('IntegrationTest', {
commands: [
'npm run integration-test',
],
envFromCfnOutputs: {
ENDPOINT_URL: devStage.urlOutput,
},
}),
],
});
// Production Stage (with Approval)
pipeline.addStage(new AppStage(this, 'Prod', {
env: { account: '222222222222', region: 'us-east-1' },
}), {
pre: [
new pipelines.ManualApprovalStep('PromoteToProd'),
],
});
}
}
// Application Stage
class AppStage extends cdk.Stage {
public readonly urlOutput: cdk.CfnOutput;
constructor(scope: Construct, id: string, props?: cdk.StageProps) {
super(scope, id, props);
const service = new EcsFargateStack(this, 'Service');
this.urlOutput = service.urlOutput;
}
}
Infrastructure as Code Best Practices
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
// Reusable Construct
export class NetworkingConstruct extends Construct {
public readonly vpc: ec2.Vpc;
constructor(scope: Construct, id: string, props: NetworkingProps) {
super(scope, id);
this.vpc = new ec2.Vpc(this, 'VPC', {
maxAzs: props.maxAzs || 3,
natGateways: props.natGateways || 2,
enableDnsHostnames: true,
enableDnsSupport: true,
subnetConfiguration: [
{
name: 'Public',
subnetType: ec2.SubnetType.PUBLIC,
cidrMask: 24,
},
{
name: 'Private',
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
cidrMask: 24,
},
{
name: 'Isolated',
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
cidrMask: 24,
},
],
});
// VPC Endpoints
this.vpc.addGatewayEndpoint('S3Endpoint', {
service: ec2.GatewayVpcEndpointAwsService.S3,
});
this.vpc.addInterfaceEndpoint('ECREndpoint', {
service: ec2.InterfaceVpcEndpointAwsService.ECR,
});
// Tagging
cdk.Tags.of(this.vpc).add('Environment', props.environment);
cdk.Tags.of(this.vpc).add('Project', props.projectName);
}
}
// Environment Configuration
export class EnvironmentConfig {
static getConfig(environment: string): AppConfig {
const configs: { [key: string]: AppConfig } = {
dev: {
instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
minCapacity: 1,
maxCapacity: 3,
alarmThreshold: 90,
enableLogging: true,
},
staging: {
instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.SMALL),
minCapacity: 2,
maxCapacity: 10,
alarmThreshold: 80,
enableLogging: true,
},
prod: {
instanceType: ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.LARGE),
minCapacity: 3,
maxCapacity: 50,
alarmThreshold: 70,
enableLogging: true,
},
};
return configs[environment] || configs.dev;
}
}
// Feature Flags Usage
export class FeatureFlags {
static isEnabled(feature: string): boolean {
const flags = {
NEW_DEPLOYMENT_STRATEGY: process.env.ENABLE_NEW_DEPLOYMENT === 'true',
ENHANCED_MONITORING: process.env.ENABLE_ENHANCED_MONITORING === 'true',
CANARY_DEPLOYMENTS: process.env.ENABLE_CANARY === 'true',
};
return flags[feature] || false;
}
}
Security and Cost Optimization
// Security Group Least Privilege
const dbSecurityGroup = new ec2.SecurityGroup(this, 'DatabaseSG', {
vpc,
description: 'Security group for RDS database',
allowAllOutbound: false, // Restrict outbound too
});
// Allow access only from app servers
dbSecurityGroup.addIngressRule(
appSecurityGroup,
ec2.Port.tcp(3306),
'Allow MySQL access from app servers only'
);
// Database Credentials with Secrets Manager
const dbSecret = new secretsmanager.Secret(this, 'DBSecret', {
description: 'RDS database credentials',
generateSecretString: {
secretStringTemplate: JSON.stringify({ username: 'admin' }),
generateStringKey: 'password',
excludeCharacters: ' %+~`#$&*()|[]{}:;<>?!\'/@"\\',
},
});
// Cost Optimization: Spot Instances
const spotFleet = new ec2.CfnSpotFleet(this, 'SpotFleet', {
spotFleetRequestConfigData: {
iamFleetRole: spotFleetRole.roleArn,
allocationStrategy: 'lowestPrice',
targetCapacity: 10,
launchSpecifications: [
{
instanceType: 't3.medium',
imageId: ec2.MachineImage.latestAmazonLinux2().getImage(this).imageId,
spotPrice: '0.05', // Maximum bid price
securityGroups: [{ groupId: webSg.securityGroupId }],
},
],
},
});
// Lifecycle Policies for Cost Reduction
const lifecycleRule = new s3.LifecycleRule({
id: 'delete-old-logs',
enabled: true,
transitions: [
{
storageClass: s3.StorageClass.INFREQUENT_ACCESS,
transitionAfter: cdk.Duration.days(30),
},
{
storageClass: s3.StorageClass.GLACIER,
transitionAfter: cdk.Duration.days(90),
},
],
expiration: cdk.Duration.days(365),
});