Simple CI/CD for Azure Web App with Docker-compose and GitHub Actions ☁️🐋
In this article, we will explore how to set up a Continuous Integration and Continuous Deployment (CI/CD) pipeline using GitHub Actions to build and deploy a Dockerized application to an Azure Web App. The pipeline will handle both front-end and back-end builds, Docker image creation, and deployment to Azure. Let’s dive into the details.
Prerequisites
Before getting started, ensure you have the following:
- An Azure account
- An Azure Container Registry (ACR)
- An Azure Web App for Containers
- A GitHub repository with your application code
- GitHub Secrets configured with your Azure credentials
Folder Structure
Your project should have the following structure:
├── .github
│ └── workflows
│ └── azure-cicd-dev.yml
│
├── front
│ ├── Dockerfile
│ └── # other front-end related files
│
├── back
│ ├── Dockerfile
│ └── # other back-end related files
│
├── docker-compose-azure-dev.yml
└── # other project files
- The
front
directory contains the Dockerfile and other files related to the front-end application. - The
back
directory contains the Dockerfile and other files related to the back-end application. - The
docker-compose-azure-dev.yml
file is at the root of the project and defines the services and their configurations for the Docker Compose setup.
Front-End Dockerfile (front/Dockerfile
)
# Use the official node image as the base image
FROM node:14-alpine
# Set the working directory
WORKDIR /app
# Copy package.json and package-lock.json
COPY package*.json ./
# Install dependencies
RUN npm ci
# Copy the rest of the application code
COPY . .
# Build the application
RUN npm run build
# Expose the port the app runs on
EXPOSE 3000
# Command to run the app
CMD ["npm", "start"]
Back-End Dockerfile (back/Dockerfile
)
# Use the official node image as the base image
FROM node:14-alpine
# Set the working directory
WORKDIR /app
# Copy package.json and package-lock.json
COPY package*.json ./
# Install dependencies
RUN npm ci
# Copy the rest of the application code
COPY . .
# Expose the port the app runs on
EXPOSE 8080
# Command to run the app
CMD ["npm", "start"]
Docker Compose File (docker-compose-azure-dev.yml
)
version: '3.8'
services:
frontend:
image: myacr.azurecr.io/frontend:latest
ports:
- "80:3000"
environment:
- NODE_ENV=dev
backend:
image: myacr.azurecr.io/backend:latest
ports:
- "8080:8080"
environment:
- NODE_ENV=dev
Setting Up the GitHub Actions Workflow
We’ll create a GitHub Actions workflow file named azure-cicd-dev.yml
in the .github/workflows
directory of your repository. This workflow will have two main jobs: npm-build
and docker-build-and-deploy
.
Workflow Dispatch Inputs
The workflow can be manually triggered with the following inputs:
run_npm_builds
: A boolean to decide whether to run npm builds.additional_string_for_docker_tag
: An optional string to append to the Docker tag.
name: DEV compose build and deploy to Azure Web App
on:
workflow_dispatch:
inputs:
run_npm_builds:
description: 'Run npm builds'
required: true
type: choice
default: 'false'
options:
- 'true'
- 'false'
additional_string_for_docker_tag:
description: 'Additional string for Docker tag'
required: false
type: string
Job 1: NPM Build
The npm-build
job runs only if run_npm_builds
is set to true
. It checks out the code and builds both the front-end and back-end using npm.
jobs:
npm-build:
runs-on: ubuntu-latest
if: ${{ github.event.inputs.run_npm_builds == 'true' }}
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Build frontend
run: |
cd front
npm ci
npm run build
- name: Build backend
run: |
cd back
npm ci
npm run build
Job 2: Docker Build and Deploy
The docker-build-and-deploy
job depends on the npm-build
job and runs after it, or immediately if npm-build
is not executed. This job involves several steps:
- Check out the code.
- Login to Azure CLI.
- Set environment variables for Docker tags and the agent’s IP address.
- Whitelist the agent IP in the Azure Container Registry (ACR).
- Build and push Docker images for both the front-end and back-end.
- Update the Docker Compose file with the new image tags.
- Deploy the Docker Compose file to Azure Web App.
- Commit and push changes to the repository.
- Logout from Azure CLI.
docker-build-and-deploy:
runs-on: ubuntu-latest
needs: npm-build
if: ${{ github.event.inputs.run_npm_builds == 'false' || success() }}
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
ref: ${{ github.ref }}
- name: AZ CLI login
uses: azure/login@v1
with:
creds: '{"clientId":"${{ secrets.CLIENT_ID }}","clientSecret":"${{ secrets.CLIENT_SECRET }}","subscriptionId":"${{ secrets.SUBSCRIPTION_ID }}","tenantId":"${{ secrets.TENANT_ID }}"}'
- name: Set variables
id: vars
run: |
echo "::set-output name=docker_tag::$(git rev-parse --short HEAD)-$(TZ='Europe/Paris' date +%Y-%m-%d-%H-%M)"
echo "::set-output name=agent_ip::$(dig +short myip.opendns.com @resolver1.opendns.com)"
- name: Whitelist agent ip
run: |
if [ -z "$(az acr network-rule list --name ${{ vars.ACR_REPO_SHORT }} --resource-group ${{ vars.ACR_REPO_RG }} | grep ${{ steps.vars.outputs.agent_ip }} )"]
then
echo "Adding agent IP ${{ steps.vars.outputs.agent_ip }} to Azure Container Registry firewall whitelist"
az acr network-rule add --name ${{ vars.ACR_REPO_SHORT }} --resource-group ${{ vars.ACR_REPO_RG }} --ip-address ${{ steps.vars.outputs.agent_ip }}
else
echo "Agent is already whitelisted; skipping."
fi
- uses: azure/docker-login@v1
with:
login-server: ${{ vars.ACR_REPO }}
username: ${{ secrets.DOCKERIO_USERNAME }}
password: ${{ secrets.DOCKERIO_PASSWORD }}
- run: |
docker build -t ${{ vars.ACR_REPO }}/frontend:${{ steps.vars.outputs.docker_tag }}${{ github.event.inputs.additional_string_for_docker_tag }} -f front/Dockerfile .
docker push ${{ vars.ACR_REPO }}/frontend:${{ steps.vars.outputs.docker_tag }}${{ github.event.inputs.additional_string_for_docker_tag }}
docker build -t ${{ vars.ACR_REPO }}/backend:${{ steps.vars.outputs.docker_tag }}${{ github.event.inputs.additional_string_for_docker_tag }} -f back/Dockerfile .
docker push ${{ vars.ACR_REPO }}/backend:${{ steps.vars.outputs.docker_tag }}${{ github.event.inputs.additional_string_for_docker_tag }}
- name: Remove whitelisted agent ip
if: always()
run: |
echo "Removing agent IP ${{ steps.vars.outputs.agent_ip }} from Azure Container Registry firewall whitelist"
az acr network-rule remove --name ${{ vars.ACR_REPO_SHORT }} --resource-group ${{ vars.ACR_REPO_RG }} --ip-address ${{ steps.vars.outputs.agent_ip }} --only-show-errors --output none
- name: Update the Dockercompose file
env:
FRONT_NEW_VERSION: '${{ vars.ACR_REPO }}\/frontend:${{ steps.vars.outputs.docker_tag }}${{ github.event.inputs.additional_string_for_docker_tag }}'
BACK_NEW_VERSION: '${{ vars.ACR_REPO }}\/backend:${{ steps.vars.outputs.docker_tag }}${{ github.event.inputs.additional_string_for_docker_tag }}'
run: |
sed -i 's/^ *image: .*frontend:.*$/ image: '$FRONT_NEW_VERSION'/' docker-compose-azure-dev.yml
sed -i 's/^ *image: .*backend:.*$/ image: '$BACK_NEW_VERSION'/' docker-compose-azure-dev.yml
- name: Deploy to Azure Web App
uses: azure/webapps-deploy@v2
with:
app-name: ${{ vars.DEV_APP_NAME }}
configuration-file: 'docker-compose-azure-dev.yml'
- name: Commit files
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git commit -m "Bump versions in docker-compose-azure" docker-compose-azure-dev.yml
- name: Push changes
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.PAT }}
- name: AZ CLI logout
if: always()
run: |
az logout
Full Code
Here is the full code for the GitHub Actions workflow file azure-cicd-dev.yml
:
name: DEV compose build and deploy to Azure Web App
on:
workflow_dispatch:
inputs:
run_npm_builds:
description: 'Run npm builds'
required: true
type: choice
default: 'fasle'
options:
- 'true'
- 'false'
additional_string_for_docker_tag:
description: 'Additional string for Docker tag'
required: false
type: string
jobs:
npm-build:
runs-on: ubuntu-latest
if: ${{ github.event.inputs.run_npm_builds == 'true' }}
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Build frontend
run: |
cd front
npm ci
npm run build
- name: Build backend
run: |
cd back
npm ci
npm run build
docker-build-and-deploy:
runs-on: ubuntu-latest
needs: npm-build
if: ${{ github.event.inputs.run_npm_builds == 'false' || success() }}
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
ref: ${{ github.ref }}
- name: AZ CLI login
uses: azure/login@v1
with:
creds: '{"clientId":"${{ secrets.CLIENT_ID }}","clientSecret":"${{ secrets.CLIENT_SECRET }}","subscriptionId":"${{ secrets.SUBSCRIPTION_ID }}","tenantId":"${{ secrets.TENANT_ID }}"}'
- name: Set variables
id: vars
run: |
echo "::set-output name=docker_tag::$(git rev-parse --short HEAD)-$(TZ='Europe/Paris' date +%Y-%m-%d-%H-%M)"
echo "::set-output name=agent_ip::$(dig +short myip.opendns.com @resolver1.opendns.com)"
- name: Whitelist agent ip
run: |
if [ -z "$(az acr network-rule list --name ${{ vars.ACR_REPO_SHORT }} --resource-group ${{ vars.ACR_REPO_RG }} | grep ${{ steps.vars.outputs.agent_ip }} )"]
then
echo "Adding agent IP ${{ steps.vars.outputs.agent_ip }} to Azure Container Registry firewall whitelist"
az acr network-rule add --name ${{ vars.ACR_REPO_SHORT }} --resource-group ${{ vars.ACR_REPO_RG }} --ip-address ${{ steps.vars.outputs.agent_ip }}
else
echo "Agent is already whitelisted; skipping."
fi
- uses: azure/docker-login@v1
with:
login-server: ${{ vars.ACR_REPO }}
username: ${{ secrets.DOCKERIO_USERNAME }}
password: ${{ secrets.DOCKERIO_PASSWORD }}
- run: |
docker build -t ${{ vars.ACR_REPO }}/frontend:${{ steps.vars.outputs.docker_tag }}${{ github.event.inputs.additional_string_for_docker_tag }} -f front/Dockerfile .
docker push ${{ vars.ACR_REPO }}/frontend:${{ steps.vars.outputs.docker_tag }}${{ github.event.inputs.additional_string_for_docker_tag }}
docker build -t ${{ vars.ACR_REPO }}/backend:${{ steps.vars.outputs.docker_tag }}${{ github.event.inputs.additional_string_for_docker_tag }} -f back/Dockerfile .
docker push ${{ vars.ACR_REPO }}/backend:${{ steps.vars.outputs.docker_tag }}${{ github.event.inputs.additional_string_for_docker_tag }}
- name: Remove whitelisted agent ip
if: always()
run: |
echo "Removing agent IP ${{ steps.vars.outputs.agent_ip }} from Azure Container Registry firewall whitelist"
az acr network-rule remove --name ${{ vars.ACR_REPO_SHORT }} --resource-group ${{ vars.ACR_REPO_RG }} --ip-address ${{ steps.vars.outputs.agent_ip }} --only-show-errors --output none
- name: Update the Dockercompose file
env:
FRONT_NEW_VERSION: '${{ vars.ACR_REPO }}\/frontend:${{ steps.vars.outputs.docker_tag }}${{ inputs.additional_string_for_docker_tag }}'
BACK_NEW_VERSION: '${{ vars.ACR_REPO }}\/backend:${{ steps.vars.outputs.docker_tag }}${{ inputs.additional_string_for_docker_tag }}'
run: |
sed -i 's/^ *image: .*frontend:.*$/ image: '$FRONT_NEW_VERSION'/' docker-compose-azure-dev.yml
sed -i 's/^ *image: .*backend:.*$/ image: '$BACK_NEW_VERSION'/' docker-compose-azure-dev.yml
- name: Deploy to Azure Web App
uses: azure/webapps-deploy@v2
with:
app-name: ${{ vars.DEV_APP_NAME }}
configuration-file: 'docker-compose-azure-dev.yml'
- name: Commit files
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git commit -m "Bump versions in docker-compose-azure" docker-compose-azure-dev.yml
- name: Push changes
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.PAT }}
- name: AZ CLI logout
if: always()
run: |
az logout
Summary
This GitHub Actions workflow enables a seamless CI/CD process for deploying a Dockerized application to an Azure Web App. By setting up the necessary jobs and steps, you can automate the build, push, and deployment processes, ensuring that your application is always up-to-date and running smoothly in the cloud.
Feel free to customize this workflow further to fit your specific needs, and happy deploying! 🚢
…
🙌🏻 Thank you for reading! Your time and attention are greatly appreciated.
🚀 I’m grateful you stayed with me until the end. If you have questions or feedback about this blog, I’d love to hear from you!
💼 Connect with me on LinkedIn: Dömötör Lugosi
See you in the next post!