What we started with
When we first started developing the MFI Core application, we followed most of the Microsoft documentation on deploying a Docker image to an Azure App Service. It worked and was fine, but we have a small IT team and are always seeking to reduce complexity whenever possible.
Shout out to the Nonprofit Team1 at GitHub for the free organization membership.
Here is what our process looked like when we first had everything wired up:
Step | Platform | Trigger |
---|---|---|
Push Code | Github | Developer |
Publish Build | Azure DevOps | Github integration |
Store Image | Azure Container Registry | Push from build pipeline |
Trigger Release | Azure DevOps | Successful build |
Pull Image from ACR | Azure App Service | Integration with Azure DevOps Release |
This worked, but we had to bounce between Github, Azure DevOps, and Azure portal to check the progress of the image and the final deployment.
Cutting out Azure DevOps
Our first step for reducing complexity was to remove the Azure DevOps release pipeline and call an App Service webhook2 from the Azure Container Registry that would send a request to the App Service to let it know a new build was available with the appropriate tag. This was wonderful because our release pipeline really wasn’t doing much. However, it now seemed a little silly to have to log into the DevOps portal just to maintain a build pipeline. So we started to investigate GitHub actions for building and pushing the images to ACR.
Having little experience with using GitHub actions, I was impressed by how easy it was. We just included a main.yaml
file in /.github/workflows/
of our project and GitHub handled the rest. To start out we were using the example from the Azure team found here: https://github.com/Azure/docker-login.
1
2
3
4
5
6
7
8
9
10
### snippet for building and pushing
- uses: azure/docker-login@v1
with:
login-server: contoso.azurecr.io
username: $
password: $
- run: |
docker build . -t contoso.azurecr.io/k8sdemo:$
docker push contoso.azurecr.io/k8sdemo:$
To get this to work properly, you need to get your username and password from ACR and store them as secrets in your GitHub repository under REGISTRY_USERNAME
and REGISTRY_PASSWORD
.
With this change, we has now cut out the need for Azure DevOps.
Step | Platform | Trigger |
---|---|---|
Push Code | Github | Developer |
Publish Build | Github | Github Action |
Store Image | Azure Container Registry | Push from GitHub Action |
Pull Image from ACR | Azure App Service | Webhook call from ACR |
Cutting out Azure Container Registry
While investigating GitHub actions, we came across this template: github.com/actions/starter-workflows/blob/master/ci/docker-push.yml. This template was created to build and push Docker images to GitHub’s Package service. Similar to GitHub Actions, we had no exposure to GitHub Packages. We had seen the product announcement from GitHub but had no reason to dig any deeper. Luckily, modifying our GitHub action was a simple as switching out some YAML, so we gave this starter template a shot.
One thing we instantly liked was being able to remove the ACR secrets from our repository. The template uses a token generated at runtime by GitHub called GITHUB_TOKEN
to authenticate against the package repository. You’ll see shortly that this is just a temporary win, we will eventually need the App Service credentials in our webhook call to deploy the package.
NOTE: We did run into issues with the
GITHUB_TOKEN
after a few days. It seemed like the auto-generated token lost permission scope or something, so we had to create a personal access token with the correct permissions until GitHub fixed it. Issue: denied: Resource not accessible by integration 3
Our final main.yml
ended up looking like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
name: Docker
on:
push:
branches:
- master
tags:
- v*
env:
IMAGE_NAME: mficore
jobs:
push:
runs-on: ubuntu-latest
if: github.event_name == 'push'
steps:
- uses: actions/checkout@v2
- name: Build image
run: docker build . --file Dockerfile --tag image
- name: Log into registry
run: echo "$" | docker login docker.pkg.github.com -u $ --password-stdin
- name: Push image
run: |
IMAGE_ID=docker.pkg.github.com/$/$IMAGE_NAME
# Strip git ref prefix from version
VERSION=$(echo "$" | sed -e 's,.*/\(.*\),\1,')
# Strip "v" prefix from tag name
[[ "$" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
# Use Docker `latest` tag convention
[ "$VERSION" == "master" ] && VERSION=latest
echo IMAGE_ID=$IMAGE_ID
echo VERSION=$VERSION
docker tag image $IMAGE_ID:$VERSION
docker push $IMAGE_ID:$VERSION
Once the action completed, we were able to see the package in our repo and also pull it down and run it locally.
Everything seemed easy breezy, but you could tell that GitHub Packages and Actions still have some kinks to be ironed out. As I mentioned above, we ran into an issue with the authentication token for our package repo. It also seems like we have no way of deleting old versions or packages, which we are still investigting. Despite these hiccups, we were happy enough with the way everything worked to set up a webhook call from GitHub and cut out ACR. Being a small nonprofit, it is always nice to save the money on our Azure for Nonprofits4 grant for running applications.
To configure your Azure App Service webhook endpoint, you need to go to the Container settings
section of your App Service and select the Private Registry
tab.
Server URL
sould behttps://docker.pkg.github.com
Login
should be your GitHub accountPassword
would be a GitHub Personal Access Token5- creating-a-personal-access-token-for-the-command-line) with
read:packages
in scope Image and optional tag
should come from the pull instructions on your package repoContinuous Deployment
should beOn
Next, you will want to copy the WebHooks URL so that you can paste it into the GitHub webhook configuration.
- The
Payload URL
should be the URL you copied from Azure App Service Content Type
should beapplication/json
- No secret needed, credentials are in the
Payload URL
- Select
Let me select individual events
and check thePackages
option
Once everything is set up, you should be able to make a push or merge into the master branch and see your package update and the App Service pull the image shortly after.
End Result
Now that we have all of the pieces in place, our steps look like this:
Step | Platform | Trigger |
---|---|---|
Push Code | Github | Developer |
Publish Build | Github | Github Actions |
Store Image | Github | Push from Github Action to Packages |
Pull Image from ACR | Azure App Service | Github Webhook |
We no longer have to monitor Azure DevOps or pay for Azure Container Registry. We can make our GitHUb Actions as complicated as we like and add in tests for the builds.