{
  "$type": "site.standard.document",
  "canonicalUrl": "https://johnnyreilly.com/posts/azure-container-apps-build-and-deploy-with-bicep-and-github-actions",
  "description": "Learn how to deploy a web app to Azure Container Apps using Bicep and GitHub Actions. This post covers the configuration and deployment of secrets.",
  "path": "/posts/azure-container-apps-build-and-deploy-with-bicep-and-github-actions",
  "publishedAt": "2021-12-27T00:00:00.000Z",
  "site": "at://did:plc:yy3apqjlms24kso7ahn7lbmb/site.standard.publication/3mova7c4nho2b",
  "tags": [
    "bicep",
    "github actions",
    "azure container apps"
  ],
  "textContent": "This post shows how to build and deploy a simple web application to Azure Container Apps using Bicep and GitHub Actions. This includes the configuration and deployment of secrets.\n\nThis post follows on from the previous post which deployed infrastructure and a \"hello world\" container, this time introducing the building of an image and storing it in the GitHub container registry so it can be deployed.\n\nIf you'd like to learn more about using dapr with Azure Container Apps then you might want to read this post.\n\n\n\nUpdated 02/05/2022\n\nThis post has been updated to reflect the migration of Azure Container Apps from the Microsoft.Web namespace to the Microsoft.App namespace in March 2022. See: https://github.com/microsoft/azure-container-apps/issues/109\n\nThe containerised convent\n\nI learn the most about a technology when I'm using it to build something. It so happens that I have an aunt that's a nun, and long ago she persuaded me to build her convent a website. I'm a good nephew and I complied. Since that time I've been merrily overengineering it for fun and non-profit.\n\nMy aunts website is a pretty vanilla node app. Significantly it is already containerised and runs on Azure App Service Web App for Containers. Given it lives in the context of a container, this makes it a great candidate for porting to Azure Container Apps.\n\nSo that's what we'll do in this post. But where I'm building and deploying my aunt's container, you could equally be substituting your own; with some minimal changes.\n\nBicep\n\nLet's begin with the Bicep required to deploy our Azure Container App.\n\nIn our repository we'll create an infra directory, into which we'll place a main.bicep file which will contain our Bicep template:\n\nLet's talk through this template. The environment, workspace and app insights resources are fairly self explanatory. The containerApp resource is where the action is. We'll drill into that resource and the parameters used to configure it.\n\nThe node container app\n\nWe're going to create a single container app for our node web application. This is configured with these parameters:\n\nThe above parameters relate to the node application that represents the website. The nodeImage is the container image which should be deployed to a container app. The nodePort is the port from the app which should be exposed (3000 in our case). nodeIsExternalIngress is whether the container should be accessible on the internet. (Always true incidentally.)\n\nWhen these parameters are applied to the containerApp resource, it looks like this:\n\nAccessing the GitHub Container Registry\n\nGiven that we've told Bicep to deploy an image, we're going to need to tell it what registry it can use to acquire that image. Our template takes these parameters:\n\nWith the exception of the tags object which is metadata to apply to resources, these parameters are related to the container registry where our images will be stored. GitHub's in our case. Remember, what we deploy to Azure Container Apps are container images. To get something running in an ACA, it first has to reside in a container registry. There's a multitude of container registries out there and we're using the one directly available in GitHub. As an alternative, we could use an Azure Container Registry, or Docker Hub - or something else entirely.\n\nDo note the @secure() decorator. This marks the containerRegistryPassword parameter as secure. The value for a secure parameter isn't saved to the deployment history and isn't logged. Typically you'll want to mark secrets with the @secure() decorator for this very reason.\n\nWe use the parameters to configure the registries property of our container app. This tells the ACA where it can go to collect the image it needs. You can also see our first usage of secrets here. We declare the containerRegistryPassword as a secret which is stored against the ref 'container-registry-password'; captured as the variable containerRegistryPasswordRef. That variable is then referenced in the passwordSecretRef property - thus telling ACA where it can find the password.\n\nSecrets / Configuration\n\nThe final collection of parameters are unrelated to the infrastructure of deployment, rather they are the things required to configure our running application:\n\nAgain we've got a secret marked with @secure() in the form of our APPSETTINGS_API_KEY. Just as we did with containerRegistryPassword, we declare APPSETTINGS_API_KEY to be a secret, which is stored against the ref 'mailgun-api-key'; captured as the variable mailgunApiKeyRef.\n\nAll of our configuration is exposed to the running application through environment variables. By and large this is achieved through the mechanism of key / value pairs (well technically name / value) with a slight variation for secrets. Similar to the passwordSecretRef mechanism we used for the registry password, we use a secretref in place of value when passing a secret, and the value will be the ref that was set up in the secrets section; mailgunApiKeyRef in this case.\n\nSetting up a resource group\n\nWith our Bicep in place, we're going to need a resource group to send it to. Right now, Azure Container Apps aren't available everywhere. So we're going to create ourselves a resource group in North Europe which does support ACAs:\n\nSecrets for GitHub Actions\n\nWe're aiming to set up a GitHub Action to handle our deployment. This will depend upon a number of secrets:\n\nWe'll need to create each of these secrets.\n\nAZURE_CREDENTIALS - GitHub logging into Azure\n\nSo GitHub Actions can interact with Azure on our behalf, we need to provide it with some credentials. We'll use the Azure CLI to create these:\n\nRemember to replace the {subscription-id} with your subscription id and {resource-group} with the name of your resource group (rg-aca if you're following along). This command will pump out a lump of JSON that looks something like this:\n\nTake this and save it as the AZURE_CREDENTIALS secret in Azure.\n\nPACKAGES_TOKEN - Azure accessing the GitHub container registry\n\nWe also need a secret for accessing packages from Azure. We're going to be publishing packages to the GitHub container registry. Azure is going to need to be able to access this when we're deploying. ACA deployment works by telling Azure where to look for an image and providing any necessary credentials to do the acquisition. To facilitate this we'll set up a PACKAGES_TOKEN secret. This is a GitHub personal access token with the read:packages scope. Follow the instructions here to create the token.\n\nSecrets for the app\n\nAlongside these infrastructure / deployment related secrets, we'll need ones to configure the app at runtime:\n\n- APPSETTINGS_API_KEY - an API key for Mailgun which will be used to send emails\n- APPSETTINGS_DOMAIN - the domain for the email eg mg.poorclaresarundel.org\n- APPSETTINGS_FROM_EMAIL - who automated emails should come from eg noreply@mg.poorclaresarundel.org\n- APPSETTINGS_RECIPIENT_EMAIL - the email address emails should be sent to\n\nStrictly speaking, only the API key is a secret. But to simplify this post we'll configure all of these as secrets in GitHub.\n\nDeploying with GitHub Actions\n\nWith our secrets configured, we're now well placed to write our GitHub Action. We'll create a .github/workflows/deploy.yaml file in our repository and populate it thusly:\n\nThere's a lot in this workflow. Let's dig into the build and deploy jobs to see what's happening.\n\nbuild - building our image\n\nThe build job is all about building our container images and pushing then to the GitHub registry. It's heavily inspired by Jeff Hollan's Azure sample app GHA. When we look at the strategy we can see a matrix of services consisting of a single service; our node app:\n\nThis is a matrix because a typical use case of an Azure Container App will be multi-container, so we're starting generic from the beginning. The outputs pumps out the details of our containerImage-node image to be used later:\n\nWith that understanding in place, let's examine what each of the steps in the build job does\n\n- Log into registry - logs into the GitHub container registry\n- Extract Docker metadata - acquire tags which will be used for versioning\n- Build and push Docker image - build the docker image and if this is not a PR: tag, label and push it to the registry\n- Output image tag - write out the image tag for usage in deployment\n\ndeploy - shipping our image to Azure\n\nThe deploy job does two possible things with our Bicep template; main.bicep.\n\nIn the case of a pull request, it runs the az deployment group what-if - this allows us to see what the effect would be of applying a PR to our infrastructure.\n\nWhen it's not a pull request, it runs the az deployment group create command which performs a deployment of our main.bicep file.\n\nIn either case we pass the same set of parameters:\n\nThese are either:\n\n- secrets we set up earlier\n- environment variables declared at the start of the script or\n- outputs from the build step - this is where we acquire our node image\n\nRunning it\n\nWhen the GitHub Action has been run you'll find that Azure Container App is now showing up inside the Azure Portal in your resource group, alongside the other resources:\n\nAnd when we take a closer look at the container app, we find a URL we can navigate to:\n\nCongratulations! You've built and deployed a simple web app to Azure Container Apps with Bicep and GitHub Actions and secrets.",
  "title": "Azure Container Apps: build and deploy with Bicep and GitHub Actions"
}