Setting up AWS CodePipeline with Terraform
A comprehensive guide to configuring AWS CodePipeline for continuous delivery using Terraform Infrastructure as Code
Setting up AWS CodePipeline with Terraform
AWS CodePipeline is a fully managed continuous delivery service. This guide shows how to set up CodePipeline using Terraform.
Prerequisites
- AWS CLI configured
- Terraform installed
- Source code repository
- Build and deployment requirements defined
Project Structure
aws-codepipeline-terraform/
├── main.tf
├── variables.tf
├── outputs.tf
└── terraform.tfvars
Basic CodePipeline Configuration
# main.tf
provider "aws" {
region = var.aws_region
}
# S3 Bucket for Artifacts
resource "aws_s3_bucket" "artifacts" {
bucket = "${var.project_name}-pipeline-artifacts"
tags = {
Environment = var.environment
}
}
# CodePipeline Role
resource "aws_iam_role" "codepipeline_role" {
name = "${var.project_name}-pipeline-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "codepipeline.amazonaws.com"
}
}
]
})
}
# CodePipeline Policy
resource "aws_iam_role_policy" "codepipeline_policy" {
name = "${var.project_name}-pipeline-policy"
role = aws_iam_role.codepipeline_role.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:GetObjectVersion",
"s3:GetBucketVersioning",
"s3:PutObject"
]
Resource = [
aws_s3_bucket.artifacts.arn,
"${aws_s3_bucket.artifacts.arn}/*"
]
},
{
Effect = "Allow"
Action = [
"codecommit:CancelUploadArchive",
"codecommit:GetBranch",
"codecommit:GetCommit",
"codecommit:GetUploadArchiveStatus",
"codecommit:UploadArchive"
]
Resource = "*"
},
{
Effect = "Allow"
Action = [
"codebuild:BatchGetBuilds",
"codebuild:StartBuild"
]
Resource = "*"
}
]
})
}
# CodeBuild Project
resource "aws_codebuild_project" "main" {
name = "${var.project_name}-build"
description = "Build project for ${var.project_name}"
build_timeout = "30"
service_role = aws_iam_role.codebuild_role.arn
artifacts {
type = "CODEPIPELINE"
}
environment {
compute_type = "BUILD_GENERAL1_SMALL"
image = "aws/codebuild/standard:5.0"
type = "LINUX_CONTAINER"
image_pull_credentials_type = "CODEBUILD"
environment_variable {
name = "ENVIRONMENT"
value = var.environment
}
}
source {
type = "CODEPIPELINE"
buildspec = "buildspec.yml"
}
tags = {
Environment = var.environment
}
}
# CodePipeline
resource "aws_codepipeline" "main" {
name = "${var.project_name}-pipeline"
role_arn = aws_iam_role.codepipeline_role.arn
artifact_store {
location = aws_s3_bucket.artifacts.bucket
type = "S3"
}
stage {
name = "Source"
action {
name = "Source"
category = "Source"
owner = "AWS"
provider = "CodeCommit"
version = "1"
output_artifacts = ["source_output"]
configuration = {
RepositoryName = var.repository_name
BranchName = var.branch_name
}
}
}
stage {
name = "Build"
action {
name = "Build"
category = "Build"
owner = "AWS"
provider = "CodeBuild"
input_artifacts = ["source_output"]
version = "1"
configuration = {
ProjectName = aws_codebuild_project.main.name
}
}
}
stage {
name = "Deploy"
action {
name = "Deploy"
category = "Deploy"
owner = "AWS"
provider = "CloudFormation"
input_artifacts = ["build_output"]
version = "1"
configuration = {
ActionMode = "CREATE_UPDATE"
StackName = "${var.project_name}-stack"
TemplatePath = "build_output::template.yaml"
Capabilities = "CAPABILITY_IAM"
RoleArn = aws_iam_role.cloudformation_role.arn
}
}
}
}
CodeBuild Role Configuration
# CodeBuild Role
resource "aws_iam_role" "codebuild_role" {
name = "${var.project_name}-build-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "codebuild.amazonaws.com"
}
}
]
})
}
# CodeBuild Policy
resource "aws_iam_role_policy" "codebuild_policy" {
name = "${var.project_name}-build-policy"
role = aws_iam_role.codebuild_role.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Resource = [
"*"
]
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
},
{
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:GetObjectVersion",
"s3:GetBucketVersioning",
"s3:PutObject"
]
Resource = [
aws_s3_bucket.artifacts.arn,
"${aws_s3_bucket.artifacts.arn}/*"
]
}
]
})
}
Variables Configuration
# variables.tf
variable "aws_region" {
description = "AWS region"
type = string
default = "us-west-2"
}
variable "project_name" {
description = "Project name"
type = string
}
variable "environment" {
description = "Environment name"
type = string
default = "dev"
}
variable "repository_name" {
description = "CodeCommit repository name"
type = string
}
variable "branch_name" {
description = "Branch name"
type = string
default = "main"
}
Best Practices
-
Pipeline Management
- Use meaningful stage names
- Implement proper error handling
- Configure notifications
- Regular pipeline reviews
-
Security
- Implement proper IAM roles
- Use encryption for artifacts
- Enable logging
- Regular security reviews
-
Cost Optimization
- Monitor build minutes
- Clean up artifacts
- Use appropriate build environments
- Regular cost reviews
-
Performance
- Optimize build steps
- Use caching effectively
- Monitor build times
- Regular performance reviews
Pipeline with Multiple Environments
# Pipeline with Staging and Production
resource "aws_codepipeline" "multi_env" {
name = "${var.project_name}-multi-env-pipeline"
role_arn = aws_iam_role.codepipeline_role.arn
artifact_store {
location = aws_s3_bucket.artifacts.bucket
type = "S3"
}
stage {
name = "Source"
action {
name = "Source"
category = "Source"
owner = "AWS"
provider = "CodeCommit"
version = "1"
output_artifacts = ["source_output"]
configuration = {
RepositoryName = var.repository_name
BranchName = var.branch_name
}
}
}
stage {
name = "Build"
action {
name = "Build"
category = "Build"
owner = "AWS"
provider = "CodeBuild"
input_artifacts = ["source_output"]
version = "1"
configuration = {
ProjectName = aws_codebuild_project.main.name
}
}
}
stage {
name = "DeployToStaging"
action {
name = "Deploy"
category = "Deploy"
owner = "AWS"
provider = "CloudFormation"
input_artifacts = ["build_output"]
version = "1"
configuration = {
ActionMode = "CREATE_UPDATE"
StackName = "${var.project_name}-staging"
TemplatePath = "build_output::template.yaml"
Capabilities = "CAPABILITY_IAM"
RoleArn = aws_iam_role.cloudformation_role.arn
ParameterOverrides = jsonencode({
Environment = "staging"
})
}
}
}
stage {
name = "ApprovalForProduction"
action {
name = "Approval"
category = "Approval"
owner = "AWS"
provider = "Manual"
version = "1"
}
}
stage {
name = "DeployToProduction"
action {
name = "Deploy"
category = "Deploy"
owner = "AWS"
provider = "CloudFormation"
input_artifacts = ["build_output"]
version = "1"
configuration = {
ActionMode = "CREATE_UPDATE"
StackName = "${var.project_name}-production"
TemplatePath = "build_output::template.yaml"
Capabilities = "CAPABILITY_IAM"
RoleArn = aws_iam_role.cloudformation_role.arn
ParameterOverrides = jsonencode({
Environment = "production"
})
}
}
}
}
Monitoring Configuration
# CloudWatch Events Rule
resource "aws_cloudwatch_event_rule" "pipeline_status" {
name = "${var.project_name}-pipeline-status"
description = "Capture pipeline status changes"
event_pattern = jsonencode({
source = ["aws.codepipeline"]
detail-type = ["CodePipeline Pipeline Execution State Change"]
detail = {
pipeline = [aws_codepipeline.main.name]
}
})
}
resource "aws_cloudwatch_event_target" "sns" {
rule = aws_cloudwatch_event_rule.pipeline_status.name
target_id = "SendToSNS"
arn = var.sns_topic_arn
input_transformer {
input_paths = {
pipeline = "$.detail.pipeline"
state = "$.detail.state"
}
input_template = "\"Pipeline <pipeline> changed state to <state>\""
}
}
# CloudWatch Dashboard
resource "aws_cloudwatch_dashboard" "pipeline" {
dashboard_name = "${var.project_name}-pipeline-dashboard"
dashboard_body = jsonencode({
widgets = [
{
type = "metric"
x = 0
y = 0
width = 12
height = 6
properties = {
metrics = [
["AWS/CodeBuild", "BuildDuration", "ProjectName", aws_codebuild_project.main.name],
["AWS/CodeBuild", "FailedBuilds", "ProjectName", aws_codebuild_project.main.name]
]
period = 300
stat = "Average"
region = var.aws_region
title = "CodeBuild Metrics"
}
}
]
})
}
Deployment Steps
- Initialize Terraform:
terraform init
- Plan the deployment:
terraform plan
- Apply the configuration:
terraform apply
Clean Up
Remove all resources when done:
terraform destroy
Common Use Cases
- Docker Build Pipeline
resource "aws_codebuild_project" "docker" {
name = "${var.project_name}-docker-build"
description = "Docker build project"
build_timeout = "30"
service_role = aws_iam_role.codebuild_role.arn
artifacts {
type = "CODEPIPELINE"
}
environment {
compute_type = "BUILD_GENERAL1_SMALL"
image = "aws/codebuild/standard:5.0"
type = "LINUX_CONTAINER"
image_pull_credentials_type = "CODEBUILD"
privileged_mode = true
environment_variable {
name = "AWS_DEFAULT_REGION"
value = var.aws_region
}
environment_variable {
name = "AWS_ACCOUNT_ID"
value = data.aws_caller_identity.current.account_id
}
environment_variable {
name = "IMAGE_REPO_NAME"
value = var.ecr_repository_name
}
}
source {
type = "CODEPIPELINE"
buildspec = "buildspec.yml"
}
}
- Cross-Account Deployment
resource "aws_codepipeline" "cross_account" {
# ... other configuration ...
stage {
name = "DeployToProd"
action {
name = "Deploy"
category = "Deploy"
owner = "AWS"
provider = "CloudFormation"
input_artifacts = ["build_output"]
version = "1"
region = var.prod_region
role_arn = var.prod_role_arn
configuration = {
ActionMode = "CREATE_UPDATE"
StackName = "${var.project_name}-prod"
TemplatePath = "build_output::template.yaml"
Capabilities = "CAPABILITY_IAM"
RoleArn = var.prod_cloudformation_role_arn
}
}
}
}
Conclusion
This setup provides a comprehensive foundation for deploying CodePipeline using Terraform. Remember to:
- Plan your pipeline stages carefully
- Implement proper security measures
- Monitor pipeline performance
- Keep your configurations versioned
- Test thoroughly before production deployment
The complete code can be customized based on your specific requirements and use cases.