Blog
September 7, 2017 Marie H.

CI/CD with AWS CodePipeline and CodeBuild

CI/CD with AWS CodePipeline and CodeBuild

I have a complicated relationship with Jenkins. It works, it's flexible, and every team I've joined already has one running. It's also a full-time job to maintain, the plugin ecosystem is a disaster zone, and "who broke the Jenkins master" is a sentence I've said more than once at 2am. When AWS released CodePipeline and CodeBuild, I was skeptical — AWS-native CI tools tend to be limited and awkward. These are... less bad than I expected. For teams already deep in AWS, they're worth a serious look.

The Pipeline Model

A CodePipeline pipeline has stages, and each stage has actions. The typical setup:

  1. Source — watch a GitHub repo or CodeCommit for pushes
  2. Build — run CodeBuild to test, lint, and produce an artifact
  3. Deploy — push to S3, trigger CodeDeploy, or update an ECS service

You can add approval stages, parallel actions within a stage, and conditional paths. It's not as expressive as a Jenkinsfile but it covers 80% of what most pipelines actually need.

The buildspec.yml

This is CodeBuild's equivalent of a Jenkinsfile. It lives at the root of your repo. Here's a real one for a Python Flask app:

version: 0.2

env:
  variables:
    ENVIRONMENT: "staging"
  parameter-store:
    DB_PASSWORD: "/myapp/staging/db_password"

phases:
  install:
    commands:
      - echo Installing dependencies...
      - pip install -r requirements.txt
      - pip install pytest flake8

  pre_build:
    commands:
      - echo Running linter...
      - flake8 app/ --max-line-length=120
      - echo Running tests...
      - pytest tests/ -v --tb=short

  build:
    commands:
      - echo Build started on `date`
      - echo Creating deployment package...
      - zip -r myapp-${CODEBUILD_BUILD_NUMBER}.zip app/ requirements.txt Procfile

  post_build:
    commands:
      - echo Uploading artifact to S3...
      - aws s3 cp myapp-${CODEBUILD_BUILD_NUMBER}.zip
          s3://my-artifacts-bucket/myapp/myapp-${CODEBUILD_BUILD_NUMBER}.zip
      - echo Build completed on `date`

artifacts:
  files:
    - myapp-*.zip
  discard-paths: yes

cache:
  paths:
    - '/root/.cache/pip/**/*'

The parameter-store block under env is genuinely handy — it pulls values from AWS Systems Manager Parameter Store and injects them as environment variables at build time. No hardcoded secrets in the buildspec.

CODEBUILD_BUILD_NUMBER is one of several built-in environment variables. CODEBUILD_RESOLVED_SOURCE_VERSION gives you the Git commit SHA, which I use for tagging Docker images.

IAM Role for CodeBuild

CodeBuild runs under a service role. At minimum it needs:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:us-east-1:123456789012:log-group:/aws/codebuild/*"
    },
    {
      "Effect": "Allow",
      "Action": ["s3:PutObject", "s3:GetObject"],
      "Resource": "arn:aws:s3:::my-artifacts-bucket/*"
    },
    {
      "Effect": "Allow",
      "Action": ["ssm:GetParameter"],
      "Resource": "arn:aws:ssm:us-east-1:123456789012:parameter/myapp/staging/*"
    }
  ]
}

Least privilege. The build doesn't need admin access to your entire account, and you'll regret it if you give it that.

Setting Up the Pipeline

You can do this in the console, but Terraform is cleaner for anything you want to reproduce. Here's the pipeline configuration:

resource "aws_codepipeline" "myapp" {
  name     = "myapp-pipeline"
  role_arn = aws_iam_role.codepipeline.arn

  artifact_store {
    location = aws_s3_bucket.artifacts.bucket
    type     = "S3"
  }

  stage {
    name = "Source"
    action {
      name             = "Source"
      category         = "Source"
      owner            = "ThirdParty"
      provider         = "GitHub"
      version          = "1"
      output_artifacts = ["source"]

      configuration = {
        Owner      = "myorg"
        Repo       = "myapp"
        Branch     = "master"
        OAuthToken = var.github_token
      }
    }
  }

  stage {
    name = "Build"
    action {
      name             = "Build"
      category         = "Build"
      owner            = "AWS"
      provider         = "CodeBuild"
      version          = "1"
      input_artifacts  = ["source"]
      output_artifacts = ["build"]

      configuration = {
        ProjectName = aws_codebuild_project.myapp.name
      }
    }
  }

  stage {
    name = "Deploy"
    action {
      name            = "Deploy"
      category        = "Deploy"
      owner           = "AWS"
      provider        = "S3"
      version         = "1"
      input_artifacts = ["build"]

      configuration = {
        BucketName = aws_s3_bucket.deployment.bucket
        Extract    = "true"
      }
    }
  }
}

CodePipeline vs Jenkins: When to Use Which

Here's my opinionated take: use CodePipeline if your team lives in AWS and you don't want to maintain another server. The managed experience is genuinely good — automatic CloudWatch Logs, IAM integration, no plugin hell. Use Jenkins if you have complex build graphs, need to run builds on specialized hardware, have a huge existing Jenkins investment, or need something that runs outside AWS.

Where CodePipeline falls short: parallelism is limited, the feedback loop for debugging is slower (you're watching a pipeline in a console instead of tail -f on a terminal), and the Groovy DSL in Jenkinsfiles is actually more expressive than the pipeline definition format once you learn it.

For a greenfield project on AWS? CodePipeline + CodeBuild + CodeDeploy is a solid, zero-maintenance choice. For anything complex, Jenkins or GitLab CI will serve you better.

Wrapping Up

The thing that surprised me most was how well the SSM Parameter Store integration works in CodeBuild. Being able to pull secrets at build time without any custom code is a genuinely nice feature. Start with a simple pipeline, get comfortable with the buildspec format, and add stages incrementally. It's not perfect but it's a lot less painful than keeping Jenkins healthy.