AWS Application Deployment Guide

AWSEC2ECSLambdaCI/CDCDKCloudFormationDevOps

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

  1. Scalability: Automatic resource adjustment based on demand
  2. High Availability: Fault tolerance through multi-AZ configuration
  3. Automation: Rapid deployment via CI/CD pipelines
  4. Security: Leverage AWS security best practices
  5. Cost Efficiency: Pay-as-you-go, optimization options
  6. Managed Services: Reduced operational burden
  7. Global Deployment: Easy deployment to regions worldwide

Cons

  1. Learning Curve: Initial learning cost due to AWS service complexity
  2. Vendor Lock-in: Dependency on AWS-specific services
  3. Cost Management: Unexpected cost increases from improper configuration
  4. Network Latency: Inter-region communication delays
  5. Complexity: Microservices architecture management overhead

References

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),
});