GitHub Actions - Azure Container Apps build/deploy
Tags: github, azure, containerapps
Published: 2023-02-07 06:59:33

This is a tutorial on how to deploy Azure Container Apps using GitHub Actions. I will provide examples and step-by-step instructions on how to build custom Github Actions workflows for deploying container apps to Azure. This will show you how to create your own actions for container deployments based on your needs.

**Some assumptions:**

- You have an [Azure account](https://portal.azure.com/).
- You have a [GitHub account](https://github.com/).
- You have the [Azure CLI](https://learn.microsoft.com/en-gb/cli/azure/?view=azure-cli-latest).
- You have access to create resources and service principles in the Azure subscription you’re going to deploy to.
- You have an image repository where you can store container images.
- You have code to deploy.

Here is an example of an action workflow that I use to deploy my container apps:

```yaml
# The name of the deployment
name: Trigger auto deployment for containerapps

# When this action will be executed
on:
  # Automatically trigger it when detected changes in repo
  push:
    # Branched to look out for changes on
    branches: 
      [ main ]

  # Allow manually trigger of the jobs
  workflow_dispatch:      

jobs:
  # Job run image.
    build:
      runs-on: ubuntu-latest

   # Steps that need to happen in this job run.
      steps:
        # Check out the code
        - name: Check out code
          uses: actions/checkout@v2

        # Log in to Azure CLI
        - name: Log in to Azure
          uses: azure/login@v1
          with:
      # Azure CLI credentials
            creds: ${{ secrets.AZURE_CREDENTIALS }}

        # Build and deploy the container app
        - name: Build and deploy Container App
          uses: azure/container-apps-deploy-action@v0
          with:
            appSourcePath: ${{ github.workspace }}
            acrName: <Container-Registery-Name>
            acrUsername: ${{ secrets.REGISTRY_USERNAME }}
            acrPassword: ${{ secrets.REGISTRY_PASSWORD }}
            containerAppName: <Container-App-Name>
            containerAppEnvironment: <Container-App-Environment-Name>
            resourceGroup: <Container-App-Resource-Group>
            imageToBuild: <Container-Registry>/<Image-Name>:<Tag e.g. ${{ github.sha }}>
            dockerfilePath: /Path/to/Dockerfile
```

This is quite a lot so let’s unpack it.

## GitHub Actions File structure

### Workflow Name

`name: Trigger auto deployment for containerapps`

This is the name you want to give your deployment run. 

### **Workflow triggers**

 

```yaml
# When this action will be executed
on:
  # Automatically trigger it when detected changes in repo
  push:
    # Branched to look out for changes on
    branches: 
      [ main ]

  # Allow manually trigger of the jobs
  workflow_dispatch:
```

The worflow trigger is how you choose to trigger your deploy. This example creates a trigger based on me pushing code to my main branch; you can set this based on the branch you trigger deploys from. It’s also possible to use wildcards, for example:

```yaml
branches:
      - main
      - 'releases/**'
```

The `workflow_dispatch` allows you to manually trigger the workflow in GitHub actions. I found this particularly useful when I’ve made changes to the infrastructure and I need to trigger a deployment again.

You can read more about workflow triggers here:

[Events that trigger workflows - GitHub Docs](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows)

### Jobs

This is a grouping of steps that need to run. You can have one or multiple jobs. 

The example above only has 1 job called "build" which builds and deploys the project and runs on  the ubuntu image.

These jobs run in parallel by default but you can create a dependency on other jobs by using the needs: keyword. You can also conditionally run jobs.

```yaml
jobs:
  review:
    name: Deploy/Review

  staging:
    name: Deploy/Staging

  production:
    name: Deploy/Production
    if: ${{ always() }}
    needs: [staging, review]
```


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4psuurpu00s7zvv6nzju.png)

In the above image the Deploy/Staging, Deploy/Review and Deploy/Production blocks are jobs.

You can read more about jobs here:

[Using jobs in a workflow - GitHub Docs](https://docs.github.com/en/actions/using-jobs/using-jobs-in-a-workflow)

### Steps

These are tasks that need to run as part of the job. In this case, we have 3 steps that need to run as part of the job.

1. Check out code
2. Log in to Azure
3. Build and deploy Container App

These can consist of actions from the marketplace or custom actions, but in this case, we are using standard marketplace actions.

The “Check out code” step checks out the code from the repo you’re running this action from into the container that the build/deploy is running in.

The“ Log in to Azure” step logs you into the azure CLI and gives the deployment an access token to deploy the container app.

The “Build and deploy Container App” builds and deploys the container app in the coming section we’ll get into more detail on what that does.

## Credentials and secret setup

We need to first of all, create a service principal in Azure. The best way would be to use this Azure CLI.

*Make sure you’re logged into the subscription you want to deploy the container app.*

You then run the following command to create the service principle:

```
az ad sp create-for-rbac --name "myApp" --role contributor \
                            --scopes /subscriptions/{subscription-id}/resourceGroups/{resource-group} \
                            --sdk-auth
```

 

Replace the `{subscription-id}` and `{resource-group}` with the subscription Id and the resource group, you want to deploy to respectively. You should also replace `myApp` with your service principles name.

This command will create an RBAC (role-based access control) service principle which then has contributor access to the subscription and resource group.

You will then get an output that looks like the one below. You should be careful **not to share** this as it is sensitive information. You will need to **keep this** as you will use it soon.

```json
{
   "clientId": "<GUID>",
   "clientSecret": "<STRING>",
   "subscriptionId": "<GUID>",
   "tenantId": "<GUID>",
   "resourceManagerEndpointUrl": "<URL>"
   (...)
 }
```

Now to create the Secret in GitHub on the repo so it can be used for the action.

1. Click on the repo you want to deploy.
2. Navigate to Settings
3. Then under Security expand Secrets and variables
4. Click “Actions”
5. While on the secrets tab click on “New repository secret”
6. Create a secret called “**AZURE_CREDENTIALS**”
7. Make the secret value the value you got above from creating the service principle.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ovy4c5n83atyh98z5dh8.png)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/stcc6xvew60in875vh5y.png)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5dfxoomec13ks0u8bj9m.png)

This part is only required if you want to use a container registry.

While you are creating secrets you will need 2 more. The “**REGISTRY_USERNAME**” and “**REGISTRY_PASSWORD**”. This can be obtained from your Azure container registry. 

REGISTRY_USERNAME is the username of the registry user. 

REGISTRY_PASSWORD is the password of the registry user. 

## The bare minimum

Your first half of the workflow page should look something like this: 

```yaml
# The name of the deployment
name: Trigger auto deployment for containerapps

# When this action will be executed
on:
  # Automatically trigger it when detected changes in repo
  push:
    # Branched to look out for changes on
    branches: 
      [ main ]

  # Allow mannually trigger of the jobs
  workflow_dispatch:      

jobs:
		# Job run image.
    build:
      runs-on: ubuntu-latest
```

The following steps you add will be based on different scenarios.
So lets start with the first three. 

1.) You are deploying an existing container image with public registry access. 

```yaml
steps:

  - name: Log in to Azure
    uses: azure/login@v1
    with:
      creds: ${{ secrets.AZURE_CREDENTIALS }}

  - name: Build and deploy Container App
    uses: azure/container-apps-deploy-action@v0
    with:
      imageToDeploy: mcr.microsoft.com/azuredocs/containerapps-helloworld:latest
```

2.) You are building and deploying an image. 

```yaml
steps:

  - name: Log in to Azure
    uses: azure/login@v1
    with:
      creds: ${{ secrets.AZURE_CREDENTIALS }}

  - name: Build and deploy Container App
    uses: azure/container-apps-deploy-action@v0
    with:
      appSourcePath: ${{ github.workspace }}
      acrName: mytestacr
```

3.) You don’t have a dockerfile or built image but you have the runtime stack. 

```yaml
steps:

  - name: Log in to Azure
    uses: azure/login@v1
    with:
      creds: ${{ secrets.AZURE_CREDENTIALS }}

  - name: Build and deploy Container App
    uses: azure/container-apps-deploy-action@v0
    with:
      appSourcePath: ${{ github.workspace }}
      acrName: mytestacr
      runtimeStack: 'dotnetcore:7.0'
```

With these scenarios if you don’t have the container app, container environment, or resource group created either manually or by a previous run, it will create these resources for you. 

The resources will be named as follows:

- **Container App**: github-action-container-app-\<github-run-id>-\<github-run-attempt>
- **Resource Group:** \<container-app-name>-rg
- **Container App Environment:** \<container-app-name>-env

I have found this to not be ideal in most cases, so let’s see what we need to give it to not create these. 

## Additional arguments

So to prevent the action from creating these resources, you will need to give it the name of existing resources or give it names of resources you want to initally create and subsequently re-use.

You can then add these arguments to your “Build and deploy Container App” step.

```yaml
containerAppName: <Container-App-Name>
containerAppEnvironment: <Container-App-Environment-Name>
resourceGroup: <Container-App-Resource-Group>
```

If you have a custom Docker image you want to build and deploy, you need to use these arguments to build the image and deploy it. This is if you have an existing `Dockerfile`

```yaml
imageToBuild: <Container-Registry>/<Image-Name>:<Tag e.g. ${{ github.sha }}>
dockerfilePath: /Path/to/Dockerfile
```

You will also need to log into your container registry to push the image, so you will need these below arguments as well. You will also need these if you’re pulling an image from a private image repository.

```yaml
acrName: <Container-Registery-Name>
acrUsername: ${{ secrets.REGISTRY_USERNAME }}
acrPassword: ${{ secrets.REGISTRY_PASSWORD }}
```

Some of the other ones I have not used but can be useful are:

- `environmentVariables` A list of environment variable(s) for the container. Space-separated values in 'key=value' format. Empty string to clear existing values. Prefix value with 'secretref:' to reference a secret.
- `targetPort` The target port that the Container App will listen on. If not provided, this value will be "80" for Python applications and "8080" for all other supported platforms.

Well done! You’ve made it all the way to the end.

You’ve now unlocked the skill to deploy Azure container apps using GitHub action

If you like this post consider following me here or on Twitter @bassonrichard

If you want to do some more reading here is the documentation for the GitHub Action

[Azure Container Apps Build and Deploy - GitHub Marketplace](https://github.com/marketplace/actions/azure-container-apps-build-and-deploy)