Skip to content

CI/CD Pipelines and Deployment Strategies

CI/CD Fundamentals

Continuous Integration (CI)

Core Principles - Frequent code commits to shared repository - Automated build and test on every commit - Fast feedback on code quality - Early detection of integration issues - Maintain always-deployable main branch

CI Best Practices - Commit code frequently (multiple times per day) - Automated testing at multiple levels - Fast build times (under 10 minutes) - Fix broken builds immediately - Keep builds and tests deterministic - Version control everything (code, configs, infrastructure)

Continuous Delivery vs Continuous Deployment

Continuous Delivery - Every change is deployable - Manual approval for production deployment - Automated deployment to staging/test environments - Business decides when to release

Continuous Deployment - Fully automated deployment to production - No manual intervention - Every passing change goes to production - Requires mature testing and monitoring

AWS CodePipeline

Pipeline Architecture

Core Components - Source: CodeCommit, GitHub, S3, ECR - Build: CodeBuild, Jenkins - Test: CodeBuild, third-party tools - Deploy: CodeDeploy, CloudFormation, ECS, Lambda - Approval: Manual approval actions

Basic Pipeline Structure

# CloudFormation template for CodePipeline
Resources:
  Pipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      RoleArn: !GetAtt PipelineRole.Arn
      ArtifactStore:
        Type: S3
        Location: !Ref ArtifactBucket
      Stages:
        - Name: Source
          Actions:
            - Name: SourceAction
              ActionTypeId:
                Category: Source
                Owner: AWS
                Provider: CodeCommit
                Version: '1'
              Configuration:
                RepositoryName: !Ref Repository
                BranchName: main
              OutputArtifacts:
                - Name: SourceOutput

        - Name: Build
          Actions:
            - Name: BuildAction
              ActionTypeId:
                Category: Build
                Owner: AWS
                Provider: CodeBuild
                Version: '1'
              Configuration:
                ProjectName: !Ref BuildProject
              InputArtifacts:
                - Name: SourceOutput
              OutputArtifacts:
                - Name: BuildOutput

        - Name: Test
          Actions:
            - Name: UnitTest
              ActionTypeId:
                Category: Test
                Owner: AWS
                Provider: CodeBuild
                Version: '1'
              Configuration:
                ProjectName: !Ref TestProject
              InputArtifacts:
                - Name: BuildOutput

        - Name: Approval
          Actions:
            - Name: ManualApproval
              ActionTypeId:
                Category: Approval
                Owner: AWS
                Provider: Manual
                Version: '1'
              Configuration:
                CustomData: 'Please approve deployment to production'
                NotificationArn: !Ref ApprovalTopic

        - Name: Deploy
          Actions:
            - Name: DeployAction
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Provider: CodeDeploy
                Version: '1'
              Configuration:
                ApplicationName: !Ref Application
                DeploymentGroupName: !Ref DeploymentGroup
              InputArtifacts:
                - Name: BuildOutput

Advanced Pipeline Patterns

Parallel Actions

- Name: ParallelTesting
  Actions:
    - Name: UnitTests
      RunOrder: 1
      ActionTypeId:
        Category: Test
        Owner: AWS
        Provider: CodeBuild
        Version: '1'
      Configuration:
        ProjectName: !Ref UnitTestProject

    - Name: IntegrationTests
      RunOrder: 1  # Same RunOrder = parallel execution
      ActionTypeId:
        Category: Test
        Owner: AWS
        Provider: CodeBuild
        Version: '1'
      Configuration:
        ProjectName: !Ref IntegrationTestProject

    - Name: SecurityScan
      RunOrder: 1
      ActionTypeId:
        Category: Test
        Owner: AWS
        Provider: CodeBuild
        Version: '1'
      Configuration:
        ProjectName: !Ref SecurityScanProject

Multi-Region Deployment

- Name: DeployProduction
  Actions:
    - Name: DeployUSEast1
      Region: us-east-1
      ActionTypeId:
        Category: Deploy
        Owner: AWS
        Provider: CloudFormation
        Version: '1'
      Configuration:
        ActionMode: CREATE_UPDATE
        StackName: app-stack
        TemplatePath: BuildOutput::template.yaml

    - Name: DeployUSWest2
      Region: us-west-2
      ActionTypeId:
        Category: Deploy
        Owner: AWS
        Provider: CloudFormation
        Version: '1'
      Configuration:
        ActionMode: CREATE_UPDATE
        StackName: app-stack
        TemplatePath: BuildOutput::template.yaml

Cross-Account Deployment

# In target account, create role for deployment
Resources:
  CrossAccountRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              AWS: !Sub 'arn:aws:iam::${ToolsAccountId}:root'
            Action: 'sts:AssumeRole'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/PowerUserAccess'

# In pipeline account
- Name: DeployToProduction
  Actions:
    - Name: DeployAction
      RoleArn: !Sub 'arn:aws:iam::${ProductionAccountId}:role/CrossAccountRole'
      ActionTypeId:
        Category: Deploy
        Owner: AWS
        Provider: CloudFormation
        Version: '1'

AWS CodeBuild

Build Specification

buildspec.yml Structure

version: 0.2

# Environment variables
env:
  variables:
    NODE_ENV: production
  parameter-store:
    DB_PASSWORD: /prod/db/password
  secrets-manager:
    API_KEY: prod/api:key

# Build phases
phases:
  install:
    runtime-versions:
      nodejs: 18
      python: 3.11
    commands:
      - npm install -g npm@latest
      - pip install -r requirements.txt

  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
      - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
      - IMAGE_TAG=${COMMIT_HASH:=latest}

  build:
    commands:
      - echo Build started on `date`
      - npm run test
      - npm run build
      - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
      - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG

  post_build:
    commands:
      - echo Build completed on `date`
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
      - printf '[{"name":"app","imageUri":"%s"}]' $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG > imagedefinitions.json

# Artifacts to pass to next stage
artifacts:
  files:
    - imagedefinitions.json
    - '**/*'
  name: BuildArtifact

# Reports for test results and code coverage
reports:
  jest_reports:
    files:
      - 'test-results.xml'
    file-format: 'JUNITXML'
  coverage_report:
    files:
      - 'coverage/clover.xml'
    file-format: 'CLOVERXML'

# Cache dependencies for faster builds
cache:
  paths:
    - 'node_modules/**/*'
    - '.npm/**/*'

Build Project Configuration

Advanced Build Settings

Resources:
  BuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: my-build-project
      ServiceRole: !GetAtt BuildRole.Arn
      Artifacts:
        Type: CODEPIPELINE
      Environment:
        Type: LINUX_CONTAINER
        ComputeType: BUILD_GENERAL1_MEDIUM
        Image: aws/codebuild/standard:7.0
        PrivilegedMode: true  # Required for Docker builds
        EnvironmentVariables:
          - Name: AWS_ACCOUNT_ID
            Value: !Ref AWS::AccountId
          - Name: IMAGE_REPO_NAME
            Value: !Ref ECRRepository
      Source:
        Type: CODEPIPELINE
        BuildSpec: buildspec.yml
      Cache:
        Type: S3
        Location: !Sub '${CacheBucket}/cache'
      LogsConfig:
        CloudWatchLogs:
          Status: ENABLED
          GroupName: /aws/codebuild/my-project
        S3Logs:
          Status: ENABLED
          Location: !Sub '${LogsBucket}/build-logs'
      VpcConfig:  # Build in VPC for private resource access
        VpcId: !Ref VPC
        Subnets:
          - !Ref PrivateSubnet1
          - !Ref PrivateSubnet2
        SecurityGroupIds:
          - !Ref BuildSecurityGroup

Build with Multiple Buildspecs

# Primary build
- Name: Build
  Actions:
    - Name: BuildApp
      ActionTypeId:
        Category: Build
        Owner: AWS
        Provider: CodeBuild
        Version: '1'
      Configuration:
        ProjectName: !Ref BuildProject
        PrimarySource: SourceOutput
        EnvironmentVariables: |
          [
            {"name":"BUILD_TYPE","value":"application","type":"PLAINTEXT"}
          ]

# Secondary build for different artifact
- Name: BuildDocumentation
  Actions:
    - Name: BuildDocs
      ActionTypeId:
        Category: Build
        Owner: AWS
        Provider: CodeBuild
        Version: '1'
      Configuration:
        ProjectName: !Ref DocsBuildProject
        EnvironmentVariables: |
          [
            {"name":"BUILD_TYPE","value":"documentation","type":"PLAINTEXT"}
          ]

AWS CodeDeploy

Deployment Configurations

EC2/On-Premises Deployments

Application Specification (appspec.yml)

version: 0.0
os: linux

files:
  - source: /
    destination: /var/www/html

permissions:
  - object: /var/www/html
    owner: www-data
    group: www-data
    mode: 755
    type:
      - directory
  - object: /var/www/html
    owner: www-data
    group: www-data
    mode: 644
    type:
      - file

hooks:
  ApplicationStop:
    - location: scripts/stop_application.sh
      timeout: 300
      runas: root

  BeforeInstall:
    - location: scripts/install_dependencies.sh
      timeout: 300
      runas: root

  AfterInstall:
    - location: scripts/configure_application.sh
      timeout: 300
      runas: root

  ApplicationStart:
    - location: scripts/start_application.sh
      timeout: 300
      runas: root

  ValidateService:
    - location: scripts/validate_service.sh
      timeout: 300
      runas: root

Deployment Group Configuration

Resources:
  DeploymentGroup:
    Type: AWS::CodeDeploy::DeploymentGroup
    Properties:
      ApplicationName: !Ref Application
      DeploymentGroupName: production
      ServiceRoleArn: !GetAtt CodeDeployRole.Arn
      DeploymentConfigName: CodeDeployDefault.HalfAtATime
      Ec2TagFilters:
        - Type: KEY_AND_VALUE
          Key: Environment
          Value: production
      AutoRollbackConfiguration:
        Enabled: true
        Events:
          - DEPLOYMENT_FAILURE
          - DEPLOYMENT_STOP_ON_ALARM
      AlarmConfiguration:
        Enabled: true
        Alarms:
          - Name: !Ref HighErrorRateAlarm
      LoadBalancerInfo:
        TargetGroupInfoList:
          - Name: !GetAtt TargetGroup.TargetGroupName

ECS Deployments

ECS appspec.yaml

version: 0.0
Resources:
  - TargetService:
      Type: AWS::ECS::Service
      Properties:
        TaskDefinition: <TASK_DEFINITION>
        LoadBalancerInfo:
          ContainerName: "app"
          ContainerPort: 80
        PlatformVersion: "LATEST"
        NetworkConfiguration:
          AwsvpcConfiguration:
            Subnets:
              - "subnet-12345"
              - "subnet-67890"
            SecurityGroups:
              - "sg-12345"
            AssignPublicIp: "DISABLED"

Hooks:
  - BeforeInstall: "LambdaFunctionToValidateBeforeInstall"
  - AfterInstall: "LambdaFunctionToValidateAfterInstall"
  - AfterAllowTestTraffic: "LambdaFunctionToValidateAfterTestTraffic"
  - BeforeAllowTraffic: "LambdaFunctionToValidateBeforeTraffic"
  - AfterAllowTraffic: "LambdaFunctionToValidateAfterTraffic"

Lambda Deployments

Lambda appspec.yaml

version: 0.0
Resources:
  - MyFunction:
      Type: AWS::Lambda::Function
      Properties:
        Name: my-lambda-function
        Alias: live
        CurrentVersion: "1"
        TargetVersion: "2"

Hooks:
  - BeforeAllowTraffic: "PreTrafficHook"
  - AfterAllowTraffic: "PostTrafficHook"

Deployment Strategies

In-Place Deployment - Stop application on instances - Deploy new version - Start application - Downtime during deployment - Used for: EC2/On-Premises

Blue/Green Deployment - Create new environment (Green) - Deploy to Green environment - Test Green environment - Switch traffic to Green - Keep Blue for rollback - Zero downtime - Used for: EC2, ECS, Lambda

Deployment Configurations | Configuration | Description | Use Case | |--------------|-------------|----------| | CodeDeployDefault.OneAtATime | Deploy to one instance at a time | Maximum availability | | CodeDeployDefault.HalfAtATime | Deploy to half of instances | Balance speed and availability | | CodeDeployDefault.AllAtOnce | Deploy to all instances simultaneously | Fastest, but downtime | | CodeDeployDefault.LambdaCanary10Percent5Minutes | 10% traffic for 5 min, then 100% | Lambda gradual rollout | | CodeDeployDefault.LambdaLinear10PercentEvery1Minute | Increase 10% every minute | Lambda linear rollout | | CodeDeployDefault.ECSCanary10Percent5Minutes | 10% traffic for 5 min, then 100% | ECS canary | | CodeDeployDefault.ECSLinear10PercentEvery1Minute | Increase 10% every minute | ECS linear |

Custom Deployment Configuration

Resources:
  CustomDeploymentConfig:
    Type: AWS::CodeDeploy::DeploymentConfig
    Properties:
      DeploymentConfigName: Custom25Percent
      MinimumHealthyHosts:
        Type: FLEET_PERCENT
        Value: 75  # 75% must remain healthy = deploy to 25% at a time

Deployment Strategies Deep Dive

Blue/Green Deployment

Benefits - Zero downtime - Easy rollback - Full testing in production environment - Reduced risk

Challenges - Increased cost (duplicate environment) - Database schema compatibility - Complex for stateful applications

ECS Blue/Green

Resources:
  ECSService:
    Type: AWS::ECS::Service
    Properties:
      Cluster: !Ref ECSCluster
      DeploymentController:
        Type: CODE_DEPLOY  # Enable blue/green
      LoadBalancers:
        - TargetGroupArn: !Ref BlueTargetGroup
          ContainerName: app
          ContainerPort: 80

  BlueTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: app-blue
      Port: 80
      Protocol: HTTP
      VpcId: !Ref VPC
      HealthCheckPath: /health

  GreenTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: app-green
      Port: 80
      Protocol: HTTP
      VpcId: !Ref VPC
      HealthCheckPath: /health

  Listener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      LoadBalancerArn: !Ref LoadBalancer
      Port: 80
      Protocol: HTTP
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref BlueTargetGroup

  TestListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      LoadBalancerArn: !Ref LoadBalancer
      Port: 8080
      Protocol: HTTP
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref GreenTargetGroup

Canary Deployment

Strategy - Route small percentage of traffic to new version - Monitor metrics - Gradually increase traffic - Rollback if issues detected

Lambda Canary with Alias

Resources:
  LambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: my-function
      Code:
        S3Bucket: !Ref DeploymentBucket
        S3Key: function.zip

  LambdaVersion:
    Type: AWS::Lambda::Version
    Properties:
      FunctionName: !Ref LambdaFunction

  LambdaAlias:
    Type: AWS::Lambda::Alias
    Properties:
      FunctionName: !Ref LambdaFunction
      FunctionVersion: !GetAtt LambdaVersion.Version
      Name: live
      RoutingConfig:
        AdditionalVersionWeights:
          - FunctionVersion: !GetAtt NewLambdaVersion.Version
            FunctionWeight: 0.1  # 10% canary traffic

API Gateway Canary

Resources:
  Deployment:
    Type: AWS::ApiGateway::Deployment
    Properties:
      RestApiId: !Ref RestApi
      Description: Production deployment
      DeploymentCanarySettings:
        PercentTraffic: 10.0
        StageVariableOverrides:
          lambdaAlias: canary
        UseStageCache: false

  Stage:
    Type: AWS::ApiGateway::Stage
    Properties:
      RestApiId: !Ref RestApi
      DeploymentId: !Ref Deployment
      StageName: prod
      CanarySettings:
        PercentTraffic: 10.0
        DeploymentId: !Ref Deployment

Rolling Deployment

Strategy - Update instances in batches - Maintain minimum healthy instances - Gradual rollout reduces risk - Some instances may run old version during deployment

Auto Scaling Rolling Update

Resources:
  LaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateName: app-template
      LaunchTemplateData:
        ImageId: !Ref LatestAMI
        InstanceType: t3.medium
        UserData:
          Fn::Base64: !Sub |
            #!/bin/bash
            # Install application

  AutoScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    UpdatePolicy:
      AutoScalingRollingUpdate:
        MinInstancesInService: 2
        MaxBatchSize: 2
        PauseTime: PT5M
        WaitOnResourceSignals: true
        SuspendProcesses:
          - HealthCheck
          - ReplaceUnhealthy
          - AZRebalance
          - AlarmNotification
          - ScheduledActions
    Properties:
      LaunchTemplate:
        LaunchTemplateId: !Ref LaunchTemplate
        Version: !GetAtt LaunchTemplate.LatestVersionNumber
      MinSize: 4
      MaxSize: 8
      DesiredCapacity: 4
      TargetGroupARNs:
        - !Ref TargetGroup
      VPCZoneIdentifier:
        - !Ref Subnet1
        - !Ref Subnet2

Feature Toggles

Implementation

import boto3
import json

ssm = boto3.client('ssm')
appconfig = boto3.client('appconfig')

# Using SSM Parameter Store
def is_feature_enabled(feature_name):
    try:
        parameter = ssm.get_parameter(
            Name=f'/app/features/{feature_name}',
            WithDecryption=True
        )
        return json.loads(parameter['Parameter']['Value'])['enabled']
    except:
        return False

# Using AWS AppConfig
def get_feature_config():
    response = appconfig.get_configuration(
        Application='my-app',
        Environment='production',
        Configuration='feature-flags',
        ClientId='unique-client-id'
    )
    return json.loads(response['Content'].read())

# Usage in application
if is_feature_enabled('new_checkout_flow'):
    # New feature code
    process_checkout_v2()
else:
    # Old feature code
    process_checkout_v1()

AppConfig Configuration

Resources:
  AppConfigApplication:
    Type: AWS::AppConfig::Application
    Properties:
      Name: my-application

  AppConfigEnvironment:
    Type: AWS::AppConfig::Environment
    Properties:
      ApplicationId: !Ref AppConfigApplication
      Name: production

  AppConfigProfile:
    Type: AWS::AppConfig::ConfigurationProfile
    Properties:
      ApplicationId: !Ref AppConfigApplication
      Name: feature-flags
      LocationUri: hosted
      Type: AWS.AppConfig.FeatureFlags

  AppConfigDeploymentStrategy:
    Type: AWS::AppConfig::DeploymentStrategy
    Properties:
      Name: Canary10Percent20Minutes
      DeploymentDurationInMinutes: 20
      GrowthFactor: 10
      ReplicateTo: NONE

GitFlow and Branching Strategies

GitFlow Model

Branch Types - main: Production-ready code - develop: Integration branch - feature/*: New features - release/*: Release preparation - hotfix/*: Production bug fixes

Workflow

# Start new feature
git checkout -b feature/new-feature develop
# Develop feature
git commit -am "Add new feature"
# Merge back to develop
git checkout develop
git merge --no-ff feature/new-feature
git branch -d feature/new-feature

# Create release
git checkout -b release/1.2.0 develop
# Fix release bugs
git commit -am "Fix release bugs"
# Merge to main and tag
git checkout main
git merge --no-ff release/1.2.0
git tag -a 1.2.0
# Merge back to develop
git checkout develop
git merge --no-ff release/1.2.0

# Hotfix
git checkout -b hotfix/1.2.1 main
git commit -am "Fix critical bug"
git checkout main
git merge --no-ff hotfix/1.2.1
git tag -a 1.2.1
git checkout develop
git merge --no-ff hotfix/1.2.1

Trunk-Based Development

Strategy - Single main branch - Short-lived feature branches - Frequent integration - Feature toggles for incomplete features

Pipeline for Trunk-Based

# Trigger on main branch
- Name: Source
  Actions:
    - Name: Source
      ActionTypeId:
        Category: Source
        Owner: AWS
        Provider: CodeCommit
        Version: '1'
      Configuration:
        RepositoryName: my-repo
        BranchName: main  # Only main branch

# Continuous deployment
- Name: Deploy
  Actions:
    - Name: DeployProduction
      ActionTypeId:
        Category: Deploy
        Owner: AWS
        Provider: CodeDeploy
        Version: '1'
      Configuration:
        ApplicationName: !Ref Application
        DeploymentGroupName: production

Testing in CI/CD

Test Pyramid

Levels 1. Unit Tests (70%) - Fast, isolated 2. Integration Tests (20%) - Component interaction 3. End-to-End Tests (10%) - Full system

Unit Tests in CodeBuild

phases:
  build:
    commands:
      - npm test -- --coverage --watchAll=false
      - python -m pytest tests/ --cov=src --cov-report=xml

reports:
  unit_tests:
    files:
      - 'test-results.xml'
    file-format: 'JUNITXML'
  coverage:
    files:
      - 'coverage.xml'
    file-format: 'COBERTURAXML'

Integration Tests

- Name: IntegrationTest
  Actions:
    - Name: DeployTestEnv
      ActionTypeId:
        Category: Deploy
        Owner: AWS
        Provider: CloudFormation
        Version: '1'
      Configuration:
        ActionMode: CREATE_UPDATE
        StackName: test-environment
        TemplatePath: BuildOutput::template.yaml
        ParameterOverrides: |
          {
            "Environment": "test"
          }

    - Name: RunIntegrationTests
      RunOrder: 2
      ActionTypeId:
        Category: Test
        Owner: AWS
        Provider: CodeBuild
        Version: '1'
      Configuration:
        ProjectName: !Ref IntegrationTestProject

    - Name: TearDownTestEnv
      RunOrder: 3
      ActionTypeId:
        Category: Deploy
        Owner: AWS
        Provider: CloudFormation
        Version: '1'
      Configuration:
        ActionMode: DELETE_ONLY
        StackName: test-environment

Exam Tips

Key Concepts

  • CodePipeline: Orchestrates CI/CD workflow
  • CodeBuild: Compiles, tests, produces artifacts
  • CodeDeploy: Automates deployments
  • Blue/Green: Zero downtime, easy rollback
  • Canary: Gradual traffic shift
  • Rolling: Batch updates maintaining capacity

Common Scenarios

  • Fast feedback needed: Parallel test actions
  • Zero downtime required: Blue/green deployment
  • Gradual rollout: Canary with CloudWatch alarms
  • Multi-region: Cross-region actions in pipeline
  • Multi-account: Cross-account IAM roles
  • Docker builds: CodeBuild with privileged mode
  • Automated rollback: CloudWatch alarms with CodeDeploy

Best Practices

  • Automate everything possible
  • Test early and often
  • Fast feedback loops
  • Immutable artifacts
  • Environment parity
  • Security scanning in pipeline
  • Automated rollback mechanisms
  • Version everything