Create & Deploy Azure Container App ARM Template

Creating and configuring Azure Container Apps can be done using the Azure Portal. However, I sometimes find this cumbersome because you need to navigate through several web pages. These are also manual actions, so if you find yourself repeatedly doing this, you might be interested in ARM templates. In this article, I will show you how to create a JSON ARM template that you can use to create a Log Analytics Workspace, a Container Environment for a containerized web app to run in and the Container App itself. As a result, you will have an ARM template that you can use to deploy a containerized web app for various environments with just a single command. And, perhaps even more importantly, you will have a deeper understanding of the various configuration settings that are available for Azure Container Apps.

Azure Container App Resources
Azure Container App Resources

Create A Blank ARM Template

Let’s first create a blank ARM template and learn what each JSON property is used for. Then, in the next chapters, you will fill some of these properties and work towards a complete template that you can deploy.

  1. Open Visual Studio Code and install the ARM Processor Tool extension.
  2. Create a new file and save it as azuredeploy.json.
  3. Copy and paste the following JSON in the file:
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {

  },
  "variables": {

  },
  "resources": [

  ],
  "outputs": {

  }
}

  • $schema – The $schema property specifies the location of the JSON schema which is used to describe the properties that are available within a template. The schema, for example, defines resources as one of the valid properties for a template.
  • Content-version – The content-version specifies the version of the template. You can provide any value for this property.
  • Parameters – Use parameters during deployment if you want to use the same template in different environments.
  • Variables – Use variables to reuse values in your templates. They can also be constructed from parameter values.
  • Resources – Use the resources property to define which resources you want to deploy.
  • Outputs – Use outputs to return values from deployed resources.

Add Resource Parameters

The resources to deploy are a Log Analytics Workspace, a Container Environment for the container to run in and the Container App itself. Each resource will require a name and a location where to deploy. Because we want all resources to be in the same location, the corresponding parameter will be used by all resources in the ARM template. However, each resource is also different and has its own specific configuration. For example, for the Container App, some resource specific settings are configured like the URL to the container image, the target port, the amount of CPU cores, memory size and replica settings. In order to make this template reusable for different environments, these values are passed as parameters.

The example below contains all the required parameters for the resources. Note that each parameter contains a type, default value and a description. You can also see that some parameters use template functions like format and uniqueString. The uniqueString function, which is wrapped by the format function, creates a hash string based on the values provided as parameters. The result is then used by the format function to create a unique name with a prefix for the resource. Looking further, you can see that the location parameter uses the resourceGroup function to retrieve the location of the resource group. All resources will use this location parameter to make sure they deploy in the same location. By the way, functions are used within expressions that start and end with brackets: [ … ].

Update your azuredeploy.json file with the parameters as shown in the example below.

"parameters": {
  "containerAppName": {
    "type": "string",
    "defaultValue": "[format('app-{0}', uniqueString(resourceGroup().id))]",
    "metadata": {
      "description": "Specifies the name of the container app."
    }
  },
  "containerAppEnvName": {
    "type": "string",
    "defaultValue": "[format('env-{0}', uniqueString(resourceGroup().id))]",
    "metadata": {
      "description": "Specifies the name of the container app environment."
    }
  },
  "containerAppLogAnalyticsName": {
    "type": "string",
    "defaultValue": "[format('log-{0}', uniqueString(resourceGroup().id))]",
    "metadata": {
      "description": "Specifies the name of the log analytics workspace."
    }
  },
  "location": {
    "type": "string",
    "defaultValue": "[resourceGroup().location]",
    "metadata": {
      "description": "Specifies the location for all resources."
    }
  },
  "containerImage": {
    "type": "string",
    "defaultValue": "mcr.microsoft.com/azuredocs/containerapps-helloworld:latest",
    "metadata": {
      "description": "Specifies the docker container image to deploy."
    }
  },
  "targetPort": {
    "type": "int",
    "defaultValue": 80,
    "metadata": {
      "description": "Specifies the container port."
    }
  },
  "cpuCore": {
    "type": "string",
    "defaultValue": "0.5",
    "allowedValues": [
      "0.25",
      "0.5",
      "0.75",
      "1",
      "1.25",
      "1.5",
      "1.75",
      "2"
    ],
    "metadata": {
      "description": "Number of CPU cores the container can use. Can be with a maximum of two decimals."
    }
  },
  "memorySize": {
    "type": "string",
    "defaultValue": "1",
    "allowedValues": [
      "0.5",
      "1",
      "1.5",
      "2",
      "3",
      "3.5",
      "4"
    ],
    "metadata": {
      "description": "Amount of memory (in gibibytes, GiB) allocated to the container up to 4GiB. Can be with a maximum of two decimals. Ratio with CPU cores must be equal to 2."
    }
  },
  "minReplicas": {
    "type": "int",
    "defaultValue": 1,
    "maxValue": 25,
    "minValue": 0,
    "metadata": {
      "description": "Minimum number of replicas that will be deployed"
    }
  },
  "maxReplicas": {
    "type": "int",
    "defaultValue": 3,
    "maxValue": 25,
    "minValue": 0,
    "metadata": {
      "description": "Maximum number of replicas that will be deployed"
    }
  }
}

Add Resource: Log Analytics Workspace

The first resource to add is the Log Analytics Workspace, which will be used by the container app to log data. As can be seen in the example below, the resource declaration starts with a type and API version property. These properties determine the other properties that are available for the resource. For this reason, they should be placed at the top. After that, you find the name and location properties that get their values from the corresponding parameters. There is also a Stock Keeping Unit (SKU) property that specifies the PerGB2018 pricing plan, which is a pay-as-you-go plan.

Add the Log Analytics Workspace resource to your azuredeploy.json file.

{
  "type": "Microsoft.OperationalInsights/workspaces",
  "apiVersion": "2022-10-01",
  "name": "[parameters('containerAppLogAnalyticsName')]",
  "location": "[parameters('location')]",
  "properties": {
    "sku": {
      "name": "PerGB2018"
    }
  }
}

Add Resource: Container Environment

The second resource to add is the Container Environment, which also gets its name and location via parameters. As can be seen by the sku property in the example below, the environment will be configured with the consumption plan, which is a serverless environment with support for scale-to-zero. As a result, you pay only for the resources you use.

The appLogsConfiguration and logAnalyticsConfiguration properties enable the log daemon to export logs to the Log Analytics Workspace. The log daemon authenticates itself using the customer id and shared key, which are configured in the Log Analytics Workspace. As can be seen in the example below, these values are obtained using template functions and by the name parameter of the log analytics workspace. For this reason, there is also a dependsOn property that refers to the log analytics workspace you added in the previous step.

Add the Container Enviroment resource to your azuredeploy.json file.

{
  "type": "Microsoft.App/managedEnvironments",
  "apiVersion": "2022-06-01-preview",
  "name": "[parameters('containerAppEnvName')]",
  "location": "[parameters('location')]",
  "sku": {
    "name": "Consumption"
  },
  "properties": {
    "appLogsConfiguration": {
      "destination": "log-analytics",
      "logAnalyticsConfiguration": {
        "customerId": "[reference(resourceId('Microsoft.OperationalInsights/workspaces', parameters('containerAppLogAnalyticsName'))).customerId]",
        "sharedKey": "[listKeys(resourceId('Microsoft.OperationalInsights/workspaces', parameters('containerAppLogAnalyticsName')), '2022-10-01').primarySharedKey]"
      }
    }
  },
  "dependsOn": [
    "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('containerAppLogAnalyticsName'))]"
  ]
}

Add Resource: Container App

The last resource to add is the Container App. As can be seen in the example below, the managedEnvironmentId refers to the container environment. For this reason, it also has a dependsOn property set to it.

Using the containers property, you can specify one or more containers you want to deploy. As can be seen in the example below, there’s only one. It gets its name and where to get the image from as parameters. If you don’t set the container image parameter, it will use the default value. This value refers to a container image from a public registry that runs a containerized web application. With the resources property, the CPU and memory values are set.

The maxReplicas property specifies the maximum number of container replicas that can run simultaneously. The minReplicas property specifies the minimum amount, which means the amount of container replicas that will run simultaneously under normal conditions.

Now let’s take a look at the ingress configuration, which allows us to expose the container app to the public web. This is done by setting the external property to true. If you are going to use the web application from the default container image, the targetPort property should be set to 80. By setting allowInsecure to false, only traffic over HTTPS is possible.

The traffic weight can be used to split traffic between active revisions, which is useful for testing purposes. With this you can gradually phase in a new revision in blue-green deployments or in A/B testing. As can be seen in the example below, the weight is set to 100 on the latest revision, which means all traffic is routed to the latest deployed revision.

Add the Container App resource to your azuredeploy.json file.

{
  "type": "Microsoft.App/containerApps",
  "apiVersion": "2022-06-01-preview",
  "name": "[parameters('containerAppName')]",
  "location": "[parameters('location')]",
  "properties": {
    "managedEnvironmentId": "[resourceId('Microsoft.App/managedEnvironments', parameters('containerAppEnvName'))]",
    "configuration": {
      "ingress": {
        "external": true,
        "targetPort": "[parameters('targetPort')]",
        "allowInsecure": false,
        "traffic": [
          {
            "latestRevision": true,
            "weight": 100
          }
        ]
      }
    },
    "template": {
      "revisionSuffix": "firstrevision",
      "containers": [
        {
          "name": "[parameters('containerAppName')]",
          "image": "[parameters('containerImage')]",
          "resources": {
            "cpu": "[json(parameters('cpuCore'))]",
            "memory": "[format('{0}Gi', parameters('memorySize'))]"
          }
        }
      ],
      "scale": {
        "minReplicas": "[parameters('minReplicas')]",
        "maxReplicas": "[parameters('maxReplicas')]",
        "rules": [
          {
            "name": "http-scale-rule",
            "http": {
              "metadata": {
                "concurrentRequests": "100"
              }
            }
          }
        ]
      }
    }
  },
  "dependsOn": [
    "[resourceId('Microsoft.App/managedEnvironments', parameters('containerAppEnvName'))]"
  ]
}

Add Output To Return FQDN

Now that all resources are added, the last thing to do is add an output property that returns the Fully Qualified Domain Name (FQDN). As can be seen in the example below, template functions are used to retrieve the FQDN from the container app configuration after deployment.

Add the following output to your azuredeploy.json file.

"outputs": {
  "containerAppFQDN": {
    "type": "string",
    "value": "[reference(resourceId('Microsoft.App/containerApps', parameters('containerAppName'))).configuration.ingress.fqdn]"
  }
}

Deploy Container App Using ARM Template

Now it’s time to deploy the resources to a resource group.

  1. First, make sure you installed the Azure CLI on your computer.
  2. Assuming  you use Windows, open a command prompt or PowerShell and log into your tenant using the following command:
    az login --tenant "<your-tenant-id>"
  3. Create a resource group using the following command:
    az group create --name "my-container-app" --location "westeurope"
  4. Navigate to the directory containing your azuredeploy.json file and deploy the resources using the following command:
    az deployment group create --name "test-deployment" --resource-group "my-container-app" --template-file "azuredeploy.json"
  5. After successful deployment, open the Azure Portal and navigate to the resource group.
  6. Then, go to your container app and click on the application URL to navigate to the homepage of the containerized web app.

As you might have noticed, there are no parameters specified in the deploy command. This means that all resources will be created using the default parameter values. However, if you do want to change some parameter values like the name of the container app, you can do so using the following command:

az deployment group create --name "test-deployment" --resource-group "my-container-app" --template-file "azuredeploy.json" --parameters containerAppName="app-mycontainerizedwebapp"

Conclusion

You started this article learning what a blank ARM template looks like and what the different JSON properties mean. After learning the basics, you added the required parameters to the ARM template. You’ve seen how template functions can be used to create unique names with a prefix for your resources. Then, you started learning about the different resources and added them to the template. Here you learned resource specific configuration settings and how resources refer to parameter values. You also learned how these resources depend on each other and how to specify this dependency in the template. After finishing the ARM template, you deployed the resources to a resource group with just a single command.

You now know how to create a JSON ARM template to automate the deployment of the following resources:

  • Log Analytics Workspace
  • Container Environment
  • Container App

Scroll to Top