
In our previous post, we explored Continuous Integration (CI) and how it helps teams merge, build, and test code changes efficiently. But what happens after the code is integrated? This is where Continuous Delivery (CD) comes into play.
The benefits of Continuous Delivery extend beyond integration, ensuring that software is always in a deployable state. With CD, teams can automate release processes, minimize manual interventions, and accelerate software delivery. Letβs dive into what Continuous Delivery is and why it matters in DevOps.
π Missed our previous post? Read The Benefits of Continuous Integration to see how CI fits into the DevOps workflow.
What is Continuous Delivery (CD)?
Continuous Delivery (CD) is a DevOps practice that ensures software can be released to production at any time with minimal effort. It extends Continuous Integration by automating the testing, deployment, and release processes, ensuring that new features and bug fixes reach users faster and more reliably.
While Continuous Deployment (a related concept) automates releases directly into production, Continuous Delivery gives teams the control to release software when they choose, making it a balance between automation and governance.
Key Benefits of Continuous Delivery
The benefits of Continuous Delivery go beyond automationβit transforms how teams build, test, and deploy applications, improving overall efficiency.
π Faster and More Reliable Releases
- Automated pipelines remove manual bottlenecks, allowing teams to deploy frequently and with confidence.
- Faster feedback loops ensure that defects are caught before they reach production.
π Improved Software Quality
- Automated testing verifies that every change meets quality and security standards before deployment.
- Teams can deliver stable, well-tested applications with fewer post-release issues.
π‘ Reduced Deployment Risks
- Small, incremental updates are easier to test and rollback compared to large, complex deployments.
- CD ensures that every release is low-risk and highly controlled.
π§ Increased Developer Productivity
- Developers focus on writing code rather than manual deployment tasks.
- Automated pipelines allow teams to experiment and innovate without fear of breaking production.
π Better Collaboration Across Teams
- CD fosters a culture of shared responsibility among developers, testers, and operations teams.
- Transparency across the pipeline ensures everyone is aligned on release cycles and readiness.
How to Implement Continuous Delivery
Getting started with Continuous Delivery requires a well-structured pipeline and automation strategy. Hereβs how:
1. Extend Continuous Integration (CI) with Automated Deployment Pipelines π¦
- Use tools like Jenkins, GitHub Actions, GitLab CI/CD, or AWS CodePipeline to automate builds and deployments.
2. Automate Testing at Every Stage β
- Implement unit tests, integration tests, security scans, and performance testing to ensure release readiness.
3. Use Infrastructure as Code (IaC) ποΈ
- Tools like Terraform, Ansible, or AWS CloudFormation help automate and standardize environments.
4. Adopt Feature Flags for Safer Releases π
- Feature toggles allow teams to deploy code without enabling it for all users immediately.
5. Monitor and Gather Feedback Continuously π
- Use observability tools like Prometheus, Datadog, or AWS CloudWatch to track system performance.
π Example: A GitHub Actions workflow automating the deployment process of static front end code.
name: Build and Deploy Static Website to S3
on:
push:
branches:
- prod
paths:
- "frontend-react-js/**" # Trigger only when files in this folder change
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Set Up Node.js
uses: actions/setup-node@v4
with:
node-version: 18 # Adjust based on your project
- name: Install Dependencies
run: |
cd frontend-react-js
npm install
- name: Disable CI checks
run: echo "CI=false" >> $GITHUB_ENV
- name: Build the Website
env:
REACT_APP_BACKEND_URL: "<api-endpoint>"
REACT_APP_FRONTEND_URL: "<frontend-endpoint>"
REACT_APP_AWS_PROJECT_REGION: ${{ secrets.AWS_REGION }}
REACT_APP_AWS_COGNITO_REGION: ${{ secrets.AWS_REGION }}
REACT_APP_AWS_USER_POOLS_ID: ${{ secrets.AWS_USER_POOL_ID }}
REACT_APP_CLIENT_ID: ${{ secrets.AWS_USER_POOL_APP_CLIENT_ID }}
REACT_APP_API_GATEWAY_ENDPOINT_URL: ${{ secrets.API_GATEWAY_ENDPOINT_URL }}
run: |
cd frontend-react-js
npm run build
- name: Configure AWS CLI
run: |
aws configure set aws_access_key_id ${{ secrets.AWS_ACCESS_KEY_ID }}
aws configure set aws_secret_access_key ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws configure set region ${{ secrets.AWS_REGION }}
- name: Deploy to S3
run: |
aws s3 sync frontend-react-js/build/ s3://${{ secrets.S3_BUCKET_NAME }}/ --delete
- name: Invalidate CloudFront Cache
run: |
aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_CLOUDFRONT_DIST_ID }} --paths "/*"
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: ${{ secrets.AWS_REGION }}
- name: Delete Build Folder
run: rm -rf frontend-react-js/build
π Example: A CloudFormation template provisioning AWS CodePipeline and AWS CodeBuild that takes its sources from Github.
AWSTemplateFormatVersion: 2010-09-09
Description: |
- CodeStart connection v2 to Github
- CodePipeline
- CodeBuild used for baking images for containers:
- CodeBuild Project
- CodeBuildRole
Parameters:
GithubBranch:
Type: String
Default: prod
GithubRepo:
Type: String
Default: "denisgulev/aws-bootcamp-cruddur-2023"
ClusterStack:
Type: String
ServiceStack:
Type: String
S3Bucket:
Type: String
LogGroupPath:
Type: String
Default: "/cruddur/codebuild/bake-service"
Description: The path to the log group for the build project
LogStreamName:
Type: String
Default: "backend-flask"
Description: The CodeBuild image to use for the build project
CodeBuildImage:
Type: String
Default: aws/codebuild/amazonlinux-aarch64-standard:3.0
Description: The CodeBuild image to use for the build project
CodeBuildComputeType:
Type: String
# Default: BUILD_GENERAL1_SMALL
Default: BUILD_GENERAL1_MEDIUM
CodeBuildTimeoutMins:
Type: Number
Default: 20
Description: The build project timeout in minutes
BuildSpec:
Type: String
Default: "backend-flask/buildspec.yml"
Description: The buildspec file to use for the build project
Resources:
CodeBuildBakeImage:
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codebuild-project.html
Type: AWS::CodeBuild::Project
Properties:
Name: !Sub "${AWS::StackName}-BakeImage"
TimeoutInMinutes: !Ref CodeBuildTimeoutMins
ServiceRole: !GetAtt CodeBuildRole.Arn
Artifacts:
Type: CODEPIPELINE
Environment:
ComputeType: !Ref CodeBuildComputeType
Image: !Ref CodeBuildImage
Type: ARM_CONTAINER
PrivilegedMode: true
LogsConfig:
CloudWatchLogs:
GroupName: !Ref LogGroupPath
StreamName: !Ref LogStreamName
Status: ENABLED
Source:
Type: CODEPIPELINE
BuildSpec: !Ref BuildSpec
CodeBuildRole:
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Action: sts:AssumeRole
Effect: Allow
Principal:
Service: [ codebuild.amazonaws.com ]
Policies:
- PolicyName: !Sub "${AWS::StackName}-S3Policy"
PolicyDocument:
Version: '2012-10-17'
Statement:
- Action:
- s3:PutObject
- s3:GetObject
Effect: Allow
Resource: !Sub "arn:aws:s3:::${S3Bucket}/*"
- PolicyName: !Sub "${AWS::StackName}-ECRPolicy"
PolicyDocument:
Version: '2012-10-17'
Statement:
- Action:
- ecr:BatchCheckLayerAvailability
- ecr:CompleteLayerUpload
- ecr:GetAuthorizationToken
- ecr:InitiateLayerUpload
- ecr:PutImage
- ecr:UploadLayerPart
- ecr:GetDownloadUrlForLayer
- ecr:BatchGetImage
Effect: Allow
Resource: "*"
- PolicyName: !Sub "${AWS::StackName}-VPCPolicy"
PolicyDocument:
Version: '2012-10-17'
Statement:
- Action:
- ec2:CreateNetworkInterface
- ec2:DescribeDhcpOptions
- ec2:DescribeNetworkInterfaces
- ec2:DeleteNetworkInterface
- ec2:DescribeSubnets
- ec2:DescribeSecurityGroups
- ec2:DescribeVpcs
Effect: Allow
Resource: "*"
- Action:
- ec2:CreateNetworkInterfacePermission
Effect: Allow
Resource: "*"
- PolicyName: !Sub "${AWS::StackName}-LogsPolicy"
PolicyDocument:
Version: '2012-10-17'
Statement:
- Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Effect: Allow
Resource:
- !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${LogGroupPath}*"
- !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${LogGroupPath}:*"
CodeStartConnection:
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codestarconnections-connection.html
Type: AWS::CodeStarConnections::Connection
Properties:
ConnectionName: !Sub "${AWS::StackName}-Connection"
ProviderType: GitHub
Pipeline:
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codepipeline-pipeline.html
Type: AWS::CodePipeline::Pipeline
Properties:
ArtifactStore:
Type: S3
Location: !Sub "${S3Bucket}"
RoleArn: !GetAtt CodePipelineRole.Arn
Stages:
- Name: Source
Actions:
- Name: ApplicationSource
RunOrder: 1
ActionTypeId:
Category: Source
Owner: AWS
Version: 1
Provider: CodeStarSourceConnection
OutputArtifacts:
- Name: SourceOutput
Configuration:
ConnectionArn: !Ref CodeStartConnection
FullRepositoryId: !Ref GithubRepo
BranchName: !Ref GithubBranch
OutputArtifactFormat: CODE_ZIP
- Name: Build
Actions:
- Name: BuildContainerImage
RunOrder: 1
ActionTypeId:
Category: Build
Owner: AWS
Version: 1
Provider: CodeBuild
InputArtifacts:
- Name: SourceOutput
OutputArtifacts:
- Name: ImageDefinition
Configuration:
ProjectName: !Ref CodeBuildBakeImage
BatchEnabled: false
- Name: Deploy
Actions:
- Name: Deploy
RunOrder: 1
ActionTypeId:
Category: Deploy
Owner: AWS
Version: 1
Provider: ECS
InputArtifacts:
- Name: ImageDefinition
Configuration:
# In minutes
DeploymentTimeout: "10"
ClusterName:
Fn::ImportValue:
!Sub "${ClusterStack}-FargateClusterName"
ServiceName:
Fn::ImportValue:
!Sub "${ServiceStack}-ServiceName"
CodePipelineRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- codepipeline.amazonaws.com
Action:
- sts:AssumeRole
Policies:
- PolicyName: !Sub "${AWS::StackName}-ECSDeployPolicy"
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- ecs:DescribeServices
- ecs:DescribeTaskDefinition
- ecs:DescribeTasks
- ecs:RegisterTaskDefinition
- ecs:UpdateService
- ecs:ListTasks
- ecs:TagResource
Resource: '*'
- PolicyName: !Sub "${AWS::StackName}-CodeStarPolicy"
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- codestar-connections:UseConnection
Resource: !Ref CodeStartConnection
- PolicyName: !Sub "${AWS::StackName}-CodePipelinePolicy"
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- s3:*
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
- cloudformation:*
- iam:PassRole
Resource: '*'
- PolicyName: !Sub "${AWS::StackName}-CodePipelineBuildPolicy"
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- codebuild:StartBuild
# even though Batch is not active, AWS still requires this permission
- codebuild:BatchGetBuilds
Resource: !Join
- ''
- - 'arn:aws:codebuild:'
- !Ref 'AWS::Region'
- ':'
- !Ref 'AWS::AccountId'
- ':project/*'
- !Ref CodeBuildBakeImage
Conclusion
The benefits of Continuous Delivery make it a crucial practice for DevOps teams aiming for faster, safer, and more efficient software releases. By automating deployments, improving collaboration, and reducing risks, CD empowers organizations to innovate with confidence.
π’ Up next: Stay tuned for how to automate infrastructure with Infrastructure as Code (IaC) !