- ECS CodePipeline (#ECS CodePipeline)
- Lambda CodePipeline (#Lambda)
ECS CodePipeline
https://docs.aws.amazon.com/codepipeline/latest/userguide/tutorials.html
## buildspec.yaml
version: 0.2
phases:
pre_build:
commands:
- echo Logging in to Amazon ECR...
- aws --version
- 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
# Updated to your repository name: my-org/app
# Add $AWS_ACCOUNT_ID in CodeBuild environment
- REPOSITORY_URI=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/my-org/app
- COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
- IMAGE_TAG=${COMMIT_HASH:=latest}
build:
commands:
- echo Build started on `date`
- echo Building the Docker image...
- docker build -t $REPOSITORY_URI:latest .
- docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG
post_build:
commands:
- echo Build completed on `date`
- echo Pushing the Docker images...
- docker push $REPOSITORY_URI:latest
- docker push $REPOSITORY_URI:$IMAGE_TAG
- echo Writing image definitions file...
# IMPORTANT: See the note below regarding the "name" field
- printf '[{"name":"my-app-container","imageUri":"%s"}]' $REPOSITORY_URI:$IMAGE_TAG > imagedefinitions.json
artifacts:
files: imagedefinitions.json
To add Amazon ECR permissions to the CodeBuild role
- Open the IAM console at https://console.aws.amazon.com/iam/.
- In the left navigation pane, choose Roles.
- In the search box, type codebuild- and choose the role that was created by the CodePipeline wizard. For this tutorial, the role name is codebuild-hello-world-service-role.
- On the Summary page, choose Attach policies.
- Select the box to the left of the AmazonEC2ContainerRegistryPowerUser policy, and choose Attach policy.
- You will need to add
AWS_ACCOUNT_IDto your CodeBuild environment variables. (e.g.,123456789012)
With CodeBuild and CodeDeploy
- CodeBuild builds the Docker image, pushes it to ECR, and outputs an
imageDetail.jsonfile containing the new image URI. - CodeDeploy takes that image URI, plugs it into your Task Definition, and handles the deployment to your ECS Service (usually doing a Blue/Green deployment to shift traffic safely).
To make this work, three files in the root of your repository: your buildspec.yaml, an appspec.yaml, and your taskdef.json.
1. buildspec.yaml
For CodeDeploy to ECS, the standard artifact is imageDetail.json rather than imagedefinitions.json.
version: 0.2
phases:
pre_build:
commands:
- echo Logging in to Amazon ECR...
- aws --version
- 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
- REPOSITORY_URI=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/my-org/app
- COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
- IMAGE_TAG=${COMMIT_HASH:=latest}
build:
commands:
- echo Build started on `date`
- echo Building the Docker image...
- docker build -t $REPOSITORY_URI:latest .
- docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG
post_build:
commands:
- echo Build completed on `date`
- echo Pushing the Docker images...
- docker push $REPOSITORY_URI:latest
- docker push $REPOSITORY_URI:$IMAGE_TAG
- echo Writing image detail file for CodeDeploy...
# CodeDeploy uses imageDetail.json to map the new image URI to task definition
- printf '{"ImageURI":"%s"}' $REPOSITORY_URI:$IMAGE_TAG > imageDetail.json
artifacts:
files:
- imageDetail.json
- appspec.yaml
- taskdef.json
2. appspec.yaml
CodeDeploy requires an AppSpec file to know which Task Definition and Container to update. Must create this file in the root of your repository alongside your buildspec.yaml.
version: 0.0
Resources:
- TargetService:
Type: AWS::ECS::Service
Properties:
TaskDefinition: "<TASK_DEFINITION>" # CodeDeploy will automatically replace this dynamically
LoadBalancerInfo:
ContainerName: "my-app-container" # IMPORTANT: This must match the container name in your taskdef.json exactly
ContainerPort: 80 # Update this to the port your app actually listens on (e.g., 80, 8080, 3000)
(Note: Do not hardcode my-app-taskdef in the <TASK_DEFINITION> placeholder above. Leave it exactly as <TASK_DEFINITION>. CodeDeploy dynamically injects the correct ARN during the pipeline run).
3. taskdef.json
Need your task definition saved as a JSON file in your repository. You can get your current one by running:
aws ecs describe-task-definition --task-definition my-app-taskdef --query taskDefinition > taskdef.json
Make sure the container name in this JSON matches the ContainerName in your appspec.yaml (e.g., "name": "my-app-container"). You can leave the "image" field as a placeholder (like "<IMAGE1_NAME>"); CodeDeploy will overwrite it with the URI from your imageDetail.json.
4. Tying it together in CodePipeline
When you set up your AWS CodePipeline via the console:
- Source Stage: Connect to GitHub/Bitbucket/CodeCommit.
- Build Stage: Point it to your CodeBuild project. Ensure the output artifact is named something like
BuildArtifact. - Deploy Stage: * Action Provider: Choose Amazon ECS (Blue/Green) (which uses CodeDeploy under the hood).
- Input Artifacts: Select
BuildArtifact. - Amazon ECS cluster:
my-cluster - Amazon ECS service:
my-app-service - AppSpec file: Choose
BuildArtifact, and typeappspec.yaml. - Task Definition: Choose
BuildArtifact, and typetaskdef.json. - Dynamically update task definition image: Choose
BuildArtifact, and enterIMAGE1_NAMEas the placeholder, and selectimageDetail.jsonas the file.
- Input Artifacts: Select
Lambda CI/CD Pipeline
Step 1: Create IAM Roles
Create the following IAM roles.
1. Lambda Execution Role
Create a role named:
lab-lambda-role
Attach managed policy:
AWSLambdaBasicExecutionRole
Purpose:
Allows Lambda to write logs to CloudWatch.
2. CodeBuild Role
Create:
lab-codebuild-role
Attach the following custom policy.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:GetObjectVersion",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::lab-artifact-bucket-258109618781/*"
},
{
"Effect": "Allow",
"Action": [
"codecommit:GitPull"
],
"Resource": "arn:aws:codecommit:us-east-1:258109618781:lab-sample-repo"
},
{
"Effect": "Allow",
"Action": [
"lambda:UpdateFunctionCode",
"lambda:PublishVersion",
"lambda:GetAlias"
],
"Resource": "arn:aws:lambda:us-east-1:258109618781:function:lab-sample-lambda"
}
]
}
Purpose:
- Pull source code from CodeCommit
- Package Lambda code
- Publish new Lambda versions
- Upload artifacts to S3
3. CodeDeploy Role
Create:
lab-codedeploy-role
Attach AWS managed policy:
AWSCodeDeployRoleForLambda
Additional S3 access:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:GetObjectVersion"
],
"Resource": "arn:aws:s3:::lab-artifact-bucket-258109618781/*"
}
]
}
4. CodePipeline Role
Create:
lab-codepipeline-role
Attach the following policy.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:GetObjectVersion",
"s3:GetBucketVersioning",
"s3:PutObject"
],
"Resource": [
"arn:aws:s3:::lab-artifact-bucket-258109618781",
"arn:aws:s3:::lab-artifact-bucket-258109618781/*"
]
},
{
"Effect": "Allow",
"Action": [
"codecommit:GetBranch",
"codecommit:GetCommit",
"codecommit:GetUploadArchiveStatus",
"codecommit:UploadArchive",
"codecommit:GitPull"
],
"Resource": "arn:aws:codecommit:us-east-1:258109618781:lab-sample-repo"
},
{
"Effect": "Allow",
"Action": [
"codebuild:BatchGetBuilds",
"codebuild:StartBuild"
],
"Resource": "arn:aws:codebuild:us-east-1:258109618781:project/lab-sample-build"
},
{
"Effect": "Allow",
"Action": [
"codedeploy:CreateDeployment",
"codedeploy:GetDeployment",
"codedeploy:GetDeploymentConfig",
"codedeploy:GetApplicationRevision",
"codedeploy:RegisterApplicationRevision",
"codedeploy:GetApplication"
],
"Resource": "*"
}
]
}
Step 2: Create an S3 Artifact Bucket
Create an S3 bucket.
lab-artifact-bucket-258109618781
Purpose:
CodePipeline stores source code and build artifacts in this bucket.
Step 3: Create the Lambda Function
Create a Lambda function.
Name: lab-sample-lambda
Runtime: Python 3.9
Execution Role: lab-lambda-role
Sample code:
def lambda_handler(event, context):
return {
"statusCode": 200,
"body": "Hello World"
}
After creation:
- Publish Version 1.
- Create alias:
Alias Name: live
Version: 1
The alias is important because CodeDeploy shifts traffic between versions using this alias.
Step 4: Create a CodeCommit Repository
Create repository:
lab-sample-repo
Clone it locally:
git clone <repository-clone-url>
cd lab-sample-repo
Step 5: Add Lambda Source Code
Project structure:
lab-sample-repo/
│
├── index.py
├── buildspec.yml
└── appspec.yml
Step 6: Create buildspec.yml
This file tells CodeBuild what to do.
version: 0.2
phases:
install:
runtime-versions:
python: 3.9
build:
commands:
- echo "Getting current version..."
- export CURRENT_VERSION=$(aws lambda get-alias --function-name lab-sample-lambda --name live --query "FunctionVersion" --output text)
- echo "Current version is $CURRENT_VERSION"
- echo "Packaging Lambda function..."
- zip -r lambda.zip index.py
- echo "Updating Lambda code and publishing version..."
- aws lambda update-function-code --function-name lab-sample-lambda --zip-file fileb://lambda.zip --publish > result.json
- export NEW_VERSION=$(jq -r '.Version' result.json)
- echo "New version is $NEW_VERSION"
- sed -i "s/CURRENT_VERSION/$CURRENT_VERSION/g" appspec.yml
- sed -i "s/TARGET_VERSION/$NEW_VERSION/g" appspec.yml
artifacts:
files:
- appspec.yml
What happens here?
- Read current Lambda alias version.
- Package source code.
- Update Lambda code.
- Publish a new Lambda version.
- Replace placeholders in
appspec.yml. - Send deployment artifact to CodeDeploy.
Step 7: Create appspec.yml
CodeDeploy uses this file to know what to deploy.
version: 0.0
Resources:
- myLambdaFunction:
Type: AWS::Lambda::Function
Properties:
Name: "lab-sample-lambda"
Alias: "live"
CurrentVersion: "CURRENT_VERSION"
TargetVersion: "TARGET_VERSION"
During build:
CURRENT_VERSION -> 1
TARGET_VERSION -> 2
CodeDeploy then moves the alias from version 1 to version 2.
Step 8: Create CodeBuild Project
Create project:
Name: lab-sample-build
Source Provider: CodePipeline
Environment: Managed Image
Operating System: Amazon Linux
Runtime: Standard
Service Role: lab-codebuild-role
Artifacts: CodePipeline
No source repository needs to be specified because CodePipeline will supply it.
Step 9: Create CodeDeploy Application
Create application.
Name: lab-sample-deploy
Compute Platform: AWS Lambda
Step 10: Create Deployment Group
Create deployment group.
Name: lab-sample-dg
Application: lab-sample-deploy
Service Role: lab-codedeploy-role
Deployment Config:
CodeDeployDefault.LambdaAllAtOnce
Deployment style:
Blue/Green
For production systems you may prefer:
CodeDeployDefault.LambdaLinear10PercentEvery1Minute
or
CodeDeployDefault.LambdaCanary10Percent5Minutes
Step 11: Create CodePipeline
Create pipeline:
Pipeline Name: lab-sample-pipeline
Role: lab-codepipeline-role
Artifact Store: lab-artifact-bucket-258109618781
Add stages:
Source Stage
Provider: AWS CodeCommit
Repository: lab-sample-repo
Branch: main
Build Stage
Provider: AWS CodeBuild
Project: lab-sample-build
Deploy Stage
Provider: AWS CodeDeploy
Application: lab-sample-deploy
Deployment Group: lab-sample-dg
Save pipeline.
Step 12: Push Code
Commit and push.
git add .
git commit -m "Initial commit"
git push origin main
The pipeline should start automatically.
Step 13: Verify the Deployment
Open CodePipeline.
Expected flow:
Source -> Succeeded
Build -> Succeeded
Deploy -> Succeeded
Open Lambda.
You should see:
Version 1
Version 2
Version 3
...
Alias:
live -> latest deployed version
Invoke the Lambda:
{
"statusCode": 200,
"body": "Hello from CI/CD Pipeline!"
}