Rapid CI/CD with CodeBuild to deploy PHP and Docker

Michael Wittig – 01 Oct 2019

This blog post is an excerpt of our book Rapid Docker on AWS where you can find examples for other programming languages as well.

There are many options available when you are looking for ways to implement a deployment pipeline. You might have heard about Jenkins, CircleCi, BitBucket Pipelines, GitLab Pipelines, and many others. AWS, on the other hand, offers services for CI/CD itself: CodeBuild and CodePipeline.

Rapid CI/CD with AWS

AWS CodePipeline orchestrates deployment pipelines. Unfortunately, the learning curve is steep and the implementation is often complicated. Therefore, I recommend a more simple approach: use CodeBuild. In general, CodeBuild feels like CircleCI or GitLab Pipelines. However, CodePipeline offers tighter security controls and excellent integration into your AWS infrastructure.

You can also listen to this topic in our podcast!

The following figure demonstrates how a simple CI/CD pipeline built with CodeBuild works.

Simple CI/CD with CodeBuild

AWS CodeBuild fetches code from CodeCommit and executes commands that you define in a file called buildspec.yml placed in the root directory of your project folder and repository. The following buildspec.yml changes the directory and runs the npm i command.

version: 0.2
phases:
build:
commands:
- cd aws
- npm i

The version attribute specifies the schema of the buildspec.yml and is defined by AWS. Multiple phases are supported and run in the following order:

  1. install
  2. pre_build (if install succeeded)
  3. build (if pre_build succeeded)
  4. post_build (if build failed or succeeded)

Each phase runs a series of commands — defined by you — using the same instance of the default shell (we will use Bash) as the execution environment. Besides the environment variables that CodeBuild provides out of the box, you can also define custom environment variables to keep your buildspec.yml more flexible.

Deployment steps defined in buildspec.yml

The following steps are needed to deploy a dockerized PHP application to an infrastructure managed by CloudFormation based on cfn-modules:

Cover of Rapid Docker on AWS

Become a Docker on AWS professional!

Our book Rapid Docker on AWS is designed for DevOps engineers and web developers who want to run dockerized web applications on AWS. We lead you with many examples: From dockerizing your application to Continuous Deployment and Infrastructure as Code on AWS. No prior knowledge of Docker and AWS is required. Get the first chapter for free!

  1. Build the nginx Docker image
  2. Build the php-fpm Docker image
  3. Push both Docker images to ECR
  4. Install cfn-modules by executing npm i in the aws folder
  5. Package the CloudFormation template (aws cloudformation package)
  6. Deploy the CloudFormation template with the new Docker images (aws cloudformation deploy)

The following buildspec.yml installs Docker, executes the docker login command returned by aws ecr get-login and finally, runs the Bash script: build.sh. The Bash script is the place for your own customizations and helps us to keep the buildspec.yml clear.

version: 0.2
phases:
install:
runtime-versions:
docker: 18
pre_build:
commands:
- $(aws ecr get-login --no-include-email)
build:
commands:
- bash build.sh

Let’s look at build.sh to see how steps 1-6 are implemented. The build.sh script is invoked by the buildspec.yml with bash build.sh. Therefore, the script lives side-by-side with the buildspec.yml file. The first two lines state that this is a Bash script that ends execution if one of the commands fails.

#!/bin/bash
set -e

The next lines create the names and tags for the Docker images:

NGINX_NAME_TAG="${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/\
${NGINX_REPO_NAME}:${CODEBUILD_RESOLVED_SOURCE_VERSION}"
FPM_NAME_TAG="${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/\
${FPM_REPO_NAME}:${CODEBUILD_RESOLVED_SOURCE_VERSION}"

Next, the script executes docker build to build the two Docker images, followed by docker push to upload the Docker images to ECR. Depending on the web application that you want to dockerize, you might want to add additional commands here (e.g., producing a JAR file, downloading dependencies, running unit tests or static analysis).

docker build -t "${NGINX_NAME_TAG}" \
-f docker/nginx/Dockerfile .
docker build -t "${FPM_NAME_TAG}" \
-f docker/php-fpm/Dockerfile .
docker push "${NGINX_NAME_TAG}"
docker push "${FPM_NAME_TAG}"

After that, we need to change to the aws directory, which contains the Infrastructure as Code, install the cfn-modules, and package the CloudFormation template:

cd aws
npm i
aws cloudformation package --template-file template.yml \
--s3-bucket "${BUCKET_NAME}" --output-template-file .template.yml

Finally, the CloudFormation template is deployed and the new Docker images are passed as parameters:

aws cloudformation deploy --template-file .template.yml \
--stack-name php-basic --capabilities CAPABILITY_IAM \
--parameter-overrides "AppImage=${FPM_NAME_TAG}" \
"ProxyImage=${NGINX_NAME_TAG}"

You can find the complete buildspec.yml and build.sh files in the Rapid Docker on AWS source code directory inside php-basic-pipeline.

Creating a CodeBuild project

So far, we have transformed the manual deployment steps into a repeatable Shell script. Next, you’ll configure CodeBuild to connect the dots. A CodeBuild project fetches code from CodeCommit and executes the commands defined in the buildspec.yml. Whenever you push a new commit to your CodeCommit repository, CodeBuild will start automatically. The following snippet shows how to create a CodeBuild project with CloudFormation. Doing so allows you to use Infrastructure as Code to set up your deployment pipeline. A more detailed explanation will follow.

Resources:
[...]
Project:
Type: 'AWS::CodeBuild::Project'
Properties:
Artifacts:
Type: NO_ARTIFACTS
Environment:
ComputeType: BUILD_GENERAL1_SMALL
EnvironmentVariables:
- Name: ACCOUNT_ID
Type: PLAINTEXT
Value: !Ref 'AWS::AccountId'
- Name: REGION
Type: PLAINTEXT
Value: !Ref 'AWS::Region'
- Name: NGINX_REPO_NAME
Type: PLAINTEXT
Value: !Ref RepositoryNginx
- Name: FPM_REPO_NAME
Type: PLAINTEXT
Value: !Ref RepositoryFpm
- Name: BUCKET_NAME
Type: PLAINTEXT
Value: !Ref BucketArtifacts
Image: 'aws/codebuild/standard:2.0'
PrivilegedMode: true # required to build Docker images
Type: LINUX_CONTAINER
LogsConfig:
CloudWatchLogs:
GroupName: !Ref ProjectLogGroup
Status: ENABLED
ServiceRole: !GetAtt 'ProjectRole.Arn'
Source:
Location: !Sub >
https://git-codecommit.${AWS::Region}.amazonaws.com/
v1/repos/${CodeCommitRepositoryName}
Type: CODECOMMIT
TimeoutInMinutes: 30

The important pieces of the CloudFormation snippet are:

  • The Environment property, which defines the custom environment variables that are used to keep the buildspec.yml file more flexible. Add more variables here if you need them in your modified build.sh script.
  • The LogsConfig property, which specifies the CloudWatch Logs location where logs are stored that you can use to debug problems with the pipeline.
  • The ServiceRole property, which points to the IAM role that is used when executing buildspec.yml. The role needs permissions to download the source code from CodeCommit, write to CloudWatch Logs, deploy the CloudFormation template, and push images to ECR. If you want to call other AWS services in your build.sh script, you likely need to add additional permissions here.

Sounds complicated? Our book Rapid Docker on AWS contains many examples helping you to set up all the missing steps in about 15 minutes.

Michael Wittig

Michael Wittig

I’m an independent consultant, technical writer, and programming founder. All these activities have to do with AWS. I’m writing this blog and all other projects together with my brother Andreas.

In 2009, we joined the same company as software developers. Three years later, we were looking for a way to deploy our software—an online banking platform—in an agile way. We got excited about the possibilities in the cloud and the DevOps movement. It’s no wonder we ended up migrating the whole infrastructure of Tullius Walden Bank to AWS. This was a first in the finance industry, at least in Germany! Since 2015, we have accelerated the cloud journeys of startups, mid-sized companies, and enterprises. We have penned books like Amazon Web Services in Action and Rapid Docker on AWS, we regularly update our blog, and we are contributing to the Open Source community. Besides running a 2-headed consultancy, we are entrepreneurs building Software-as-a-Service products.

We are available for projects.

You can contact me via Email, Twitter, and LinkedIn.

Briefcase icon
Hire me