About Blog Contact
 
 

RSS

Fri, May 6, 2022
5 min read

Automate build and push to ECR via GitHub Actions

Let's build a simple containerised PHP application and a GitHub Actions workflow to build and push the images to ECR, which is the container registry from AWS.

Prerequisites

  • an IAM credentials with permissions to create and push to ECR repositories
  • GitHub repository with the IAM credentials declared as secrets
  • Configured awscli with the account to create the ECR repository

The application

For the sake of this demo we keep the php application minimal and reduce it to the following tree.

1│ composer.json
2└─public
3│ │ index.php
4└─src
5│ │ Application.php

The composer.json file has the following content:

1{
2 "autoload": {
3 "psr-4": {
4 "Acme\\Service\\": "src/"
5 }
6 }
7}

As said, we keep it minimal. This composer.json does only include one autoloader definition.

Here is the src/Application.php:

1<?php
2 
3namespace Acme\Service;
4 
5class Application
6{
7 public function run(): string
8 {
9 return 'it works!';
10 }
11}

and here the public/index.php:

1<?php
2 
3require_once dirname(__DIR__) . '/vendor/autoload.php';
4 
5$app = new \Acme\Service\Application();
6echo $app->run();
7 
8exit(0);

That's the application. As stated in the beginning, we keep it minimal. Nevertheless, extending afterwards will be a no-brainer.

Dockerfile

Let's focus next on the Dockerfile. It will contain a multiple stages. We will build two distinct images out of it. One for php-fpm and the other for nginx.
Here is the content of the Dockerfile:

1# The first stage named `base` contains the needed/wanted
2# steps to execute our application in production
3FROM php:8.1-fpm-alpine as base
4
5WORKDIR /var/www/html
6
7RUN apk --no-cache add bash nano
8
9# The second stage named `develop` contains the software which is needed to execute
10# our application in development as well as build out software for production
11FROM base as develop
12
13RUN apk add --no-cache git \
14 && curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
15 && mkdir -p /.composer /.config \
16 && chmod -R 2777 /.composer /.config
17
18# The third stage named `build` will execute the build steps needed to execute
19# our application in production. Currently only `composer install`
20FROM develop as build
21
22COPY . .
23
24RUN composer install --no-interaction --optimize-autoloader --no-dev --prefer-dist
25
26# The fourth stage named `production` inherits from our `base` stage
27# and copies the built application files from the `build` stage
28FROM base as production
29
30COPY --from=build /var/www /var/www
31
32# The fifth stage named `nginx` is our custom nginx image, since nginx needs the content
33# of our document root. Currently only the `index.php` which is needed to proxy the
34# request to our `php-fpm` container, but could potentially contain any kind
35# of assets like CSS, JavaScript or images, which we need to serve
36FROM nginxinc/nginx-unprivileged:1.20-alpine as nginx
37
38COPY --from=build /var/www/html/public /var/www/html/public

I have added comments to all stages which describe what's happening there.

ECR repository

To create a new ECR repository with the awscli, you have to execute the following command:

1aws ecr create-repository --repository-name acme-service

GitHub Action

Let's first commit and push everything into the main branch of our GitHub repository before we start with our GitHub Action workflow.

Next we have to declare

To define a workflow we need to create a YAML file in .github/workflows in our repository:

1mkdir -p .github/workflows
2touch .github/workflows/on-push-main-branch.yml

Here is the complete content of the workflow:

1name: Build and push docker images
2 
3on:
4 push:
5 branches:
6 - main
7 
8jobs:
9 build-and-deploy:
10 runs-on: ubuntu-latest
11 steps:
12 - name: Checkout
13 uses: actions/checkout@v2
14 - name: Configure AWS credentials
15 uses: aws-actions/configure-aws-credentials@v1
16 with:
17 aws-access-key-id: ${{ secrets.AWS_DEPLOY_ACCESS_KEY_ID }}
18 aws-secret-access-key: ${{ secrets.AWS_DEPLOY_SECRET_ACCESS_KEY }}
19 aws-region: eu-central-1
20 - name: Login to Amazon ECR
21 uses: aws-actions/amazon-ecr-login@v1
22 id: login-ecr
23 - name: Build & Push Images
24 run: |
25 CURRENT_SHA=${GITHUB_SHA::8}
26 REPOSITORY=${{ steps.login-ecr.outputs.registry }}/acme-service
27 
28 docker build . --target production -t ${REPOSITORY}:latest -t ${REPOSITORY}:${CURRENT_SHA}
29 docker build . --target nginx -t ${REPOSITORY}:nginx-latest -t ${REPOSITORY}:nginx-${CURRENT_SHA}
30 
31 docker push --all-tags ${REPOSITORY}

We will go through the on-push-main-branch.yml step by step:

First comes the name and trigger declaration:

1name: Build latest tag and deploy to staging
2 
3on:
4 push:
5 branches:
6 - main
7# ...

This workflow will only be executed by a push to the main branch.

Next we have the jobs declaration:

1# ...
2jobs:
3 build-and-deploy:
4 runs-on: ubuntu-latest
5 steps:
6# ...

Our workflow contains only one job named build-and-deploy. It will run on ubuntu-latest.

The build-and-deploy job contains multiple steps.

Step 1: Checkout code

1- name: Checkout
2 uses: actions/checkout@v2

This step will checkout the coe of the repository and makes it available in the working directory

Step 2: Configure AWS credentials

1- name: Configure AWS credentials
2 uses: aws-actions/configure-aws-credentials@v1
3 with:
4 aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
5 aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
6 aws-region: eu-central-1

Here we configure the access to AWS via the added secret from the prerequisites. You may adapt the region to your needs.

Step 3: Login to our ECR registry

1- name: Login to Amazon ECR
2 uses: aws-actions/amazon-ecr-login@v1
3 id: login-ecr

This actions as well as the one before are provided by AWS and make our life really easy. We can access the registry url in the next step via the id.

Step 4: Build and push Docker images

This is the last step in our workflow which will finally build and push the needed docker images.

1- name: Build & Push Images
2 run: |
3 CURRENT_SHA=${GITHUB_SHA::8}
4 REPOSITORY=${{ steps.login-ecr.outputs.registry }}/acme-service
5 
6 docker build . --target production -t ${REPOSITORY}:latest -t ${REPOSITORY}:${CURRENT_SHA}
7 docker build . --target nginx -t ${REPOSITORY}:nginx-latest -t ${REPOSITORY}:nginx-${CURRENT_SHA}
8 
9 docker push --all-tags ${REPOSITORY}

First we declare two variables which helps us to keep the following commands short. CURRENT_SHA contains trimmed current commit sha and REPOSITORY contains the registry url (from the step before) suffixed by /acme-service which is the absolute url to our ECR repository.
Now we build our docker images with the right target from our multi-stage Dockerfile and add two tags. The first tag is the latest tag and the second tag is the unique CURRENT_SHA. For our nginx image we prepend the tags with nginx- to be able to distinguish between both images. Finally, we push all local tags for the repository.

Summary

We build a small PHP application consisting of two docker containers built out of a single multi-stage Dockerfile and configured a GitHub Actions workflow to build and push the images automatically on every push to our main branch.

Thanks for reading. I hope you can use this small tutorial as a starting point for your container journey!

Legal Notice  |   Privacy  |   RSS  |   © 2025 christlieb.eu