Edit

Share via


Tutorial: Deploy self-hosted CI/CD runners and agents with Azure Container Apps jobs

GitHub Actions and Azure Pipelines let you run CI/CD workflows with self-hosted runners and agents. You can run self-hosted runners and agents by using event-driven Azure Container Apps jobs.

Self-hosted runners are useful when you need to run workflows that require access to local resources or tools that aren't available to a cloud-hosted runner. For example, a self-hosted runner in a Container Apps job allows your workflow to access resources inside the job's virtual network that a cloud-hosted runner can't access.

Running self-hosted runners as event-driven jobs lets you take advantage of the serverless nature of Azure Container Apps. Jobs execute automatically when a workflow is triggered and exit when the job completes.

You pay only for the time that the job is running.

In this tutorial, you learn how to run GitHub Actions runners as an event-driven Container Apps job.

  • Create a Container Apps environment to deploy your self-hosted runner
  • Create a GitHub repository for running a workflow that uses a self-hosted runner
  • Build a container image that runs a GitHub Actions runner
  • Deploy the runner as a job to the Container Apps environment
  • Create a workflow that uses the self-hosted runner and verify that it runs

Important

Self-hosted runners are only recommended for private repositories. Using them with public repositories can allow dangerous code to execute on your self-hosted runner. For more information, see Self-hosted runner security.

Note

Every personal access token (PAT) has an expiration date. Make sure to regularly rotate PATs before their expiration date. For more information about managing your PAT, see Use personal access tokens.

In this tutorial, you learn how to run Azure Pipelines agents as an event-driven Container Apps job.

  • Create a Container Apps environment to deploy your self-hosted agent
  • Create an Azure DevOps organization and project
  • Build a container image that runs an Azure Pipelines agent
  • Use a manual job to create a placeholder agent in the Container Apps environment
  • Deploy the agent as a job to the Container Apps environment
  • Create a pipeline that uses the self-hosted agent and verify that it runs

Important

Self-hosted agents are only recommended for private projects. Using them with public projects can allow dangerous code to execute on your self-hosted agent. For more information, see Self-hosted agent security.

Note

Every personal access token (PAT) has an expiration date. Make sure to regularly rotate PATs before their expiration date. For more information about managing your PAT, see Use personal access tokens.

Note

Container apps and jobs don't support running Docker in containers. Any steps in your workflows that use Docker commands fail when run on a self-hosted runner or agent in a Container Apps job.

Prerequisites

  • Azure DevOps organization: If you don't have a DevOps organization with an active subscription, you can create one for free.

Refer to jobs restrictions for a list of limitations.

Setup

To sign in to Azure from the CLI, run the following command and follow the prompts to complete the authentication process.

az login

To ensure you're running the latest version of the CLI, run the upgrade command.

az upgrade

Next, install or update the Azure Container Apps extension for the CLI.

If you receive errors about missing parameters when you run az containerapp commands in Azure CLI or cmdlets from the Az.App module in PowerShell, be sure you have the latest version of the Azure Container Apps extension installed.

az extension add --name containerapp --upgrade

Note

Starting in May 2024, Azure CLI extensions no longer enable preview features by default. To access Container Apps preview features, install the Container Apps extension with --allow-preview true.

az extension add --name containerapp --upgrade --allow-preview true

Now that the current extension or module is installed, register the Microsoft.App and Microsoft.OperationalInsights namespaces.

az provider register --namespace Microsoft.App
az provider register --namespace Microsoft.OperationalInsights

Create environment variables

Now that your Azure CLI setup is complete, you can define the environment variables that are used throughout this article.

RESOURCE_GROUP="jobs-sample"
LOCATION="northcentralus"
ENVIRONMENT="env-jobs-sample"
JOB_NAME="github-actions-runner-job"
RESOURCE_GROUP="jobs-sample"
LOCATION="northcentralus"
ENVIRONMENT="env-jobs-sample"
JOB_NAME="azure-pipelines-agent-job"
PLACEHOLDER_JOB_NAME="placeholder-agent-job"

Create a Container Apps environment

The Azure Container Apps environment acts as a secure boundary around container apps and jobs so they can share the same network and communicate with each other.

Note

To create a Container Apps environment that's integrated with an existing virtual network, see Provide a virtual network to an Azure Container Apps environment.

  1. Create a resource group with the following command.

    az group create \
        --name "$RESOURCE_GROUP" \
        --location "$LOCATION"
    
  2. Create the Container Apps environment with the following command.

    az containerapp env create \
        --name "$ENVIRONMENT" \
        --resource-group "$RESOURCE_GROUP" \
        --location "$LOCATION"
    

Create a GitHub repository for running a workflow

To run a workflow, you need to create a GitHub repository that contains the workflow definition.

  1. Go to GitHub and sign in.

  2. Create a new repository by entering the following values.

    Setting Value
    Owner Select your GitHub username.
    Repository name Enter a name for your repository.
    Visibility Select Private.
    Initialize this repository with Select Add a README file.

    Use the default values for the other settings.

  3. Select Create repository.

  4. In your new repository, select Actions.

  5. Search for the Simple workflow template and select Configure.

  6. Select Commit changes to add the workflow to your repository.

The workflow runs on the ubuntu-latest GitHub-hosted runner and prints a message to the console. Later, you replace the GitHub-hosted runner with a self-hosted runner.

Get a GitHub personal access token

To run a self-hosted runner, you need to create a personal access token (PAT) in GitHub. Each time a runner starts, the PAT generates a token to register the runner with GitHub. The GitHub Actions runner scale rule uses the PAT to monitor the repository's workflow queue and start runners as needed.

Note

Personal Access Tokens (PATs) expire. Regularly rotate your tokens to keep them valid and maintain uninterrupted service.

  1. In GitHub, select your profile picture in the upper-right corner and select Settings.

  2. Select Developer settings.

  3. Under Personal access tokens, select Fine-grained tokens.

  4. Select Generate new token.

  5. In the New fine-grained personal access token screen, enter the following values.

    Setting Value
    Token name Enter a name for your token.
    Expiration Select 30 days.
    Repository access Select Only select repositories and select the repository you created.

    Enter the following values for Repository permissions.

    Setting Value
    Actions Select Read-only.
    Administration Select Read and write.
    Metadata Select Read-only.
  6. Select Generate token.

  7. Copy the token value.

  8. Define variables that you use to configure the runner and scale rule later.

    GITHUB_PAT="<GITHUB_PAT>"
    REPO_OWNER="<REPO_OWNER>"
    REPO_NAME="<REPO_NAME>"
    REGISTRATION_TOKEN_API_URL="<YOUR_REGISTRATION_TOKEN_API_URL>"
    

    Replace the placeholders with the following values:

    Placeholder Value
    <GITHUB_PAT> The GitHub PAT you generated.
    <REPO_OWNER> The owner of the repository you created earlier. This value is usually your GitHub username.
    <REPO_NAME> The name of the repository you created earlier. This value is the same name you entered in the Repository name field.
    <YOUR_REGISTRATION_TOKEN_API_URL> The registration token API URL in the entrypoint.sh file. For example, 'https://myapi.example.com/get-token'

Build the GitHub Actions runner container image

To create a self-hosted runner, you need to build a container image that executes the runner. In this section, you build the container image and push it to a container registry.

Note

The image you build in this tutorial contains a basic self-hosted runner that's suitable for running as a Container Apps job. You can customize it to include other tools or dependencies that your workflows require.

  1. Define a name for your container image and registry.

    CONTAINER_IMAGE_NAME="github-actions-runner:1.0"
    CONTAINER_REGISTRY_NAME="<CONTAINER_REGISTRY_NAME>"
    

    Replace <CONTAINER_REGISTRY_NAME> with a unique name for creating a container registry. Container registry names must be unique within Azure and be from 5 to 50 characters in length containing numbers and lowercase letters only.

  2. Create a container registry.

    az acr create \
        --name "$CONTAINER_REGISTRY_NAME" \
        --resource-group "$RESOURCE_GROUP" \
        --location "$LOCATION" \
        --sku Basic
    
  3. Your container registry must allow Azure Resource Manager (ARM) audience tokens for authentication to use managed identity to pull images.

    Use the following command to check if ARM tokens are allowed to access your Azure Container Registry (ACR).

    az acr config authentication-as-arm show --registry "$CONTAINER_REGISTRY_NAME"
    

    If ARM tokens are allowed, the command outputs the following.

    {
      "status": "enabled"
    }
    

    If the status is disabled, use the following command to allow ARM tokens.

    az acr config authentication-as-arm update --registry "$CONTAINER_REGISTRY_NAME" --status enabled
    
  4. The Dockerfile for creating the runner image is available on GitHub. Run the following command to clone the repository and build the container image in the cloud by using the az acr build command.

    az acr build \
        --registry "$CONTAINER_REGISTRY_NAME" \
        --image "$CONTAINER_IMAGE_NAME" \
        --file "Dockerfile.github" \
        "https://github.com/Azure-Samples/container-apps-ci-cd-runner-tutorial.git"
    

    The image is now available in the container registry.

Create a user-assigned managed identity

To avoid using administrative credentials, pull images from private repositories in Microsoft Azure Container Registry by using managed identities for authentication. When possible, use a user-assigned managed identity to pull images.

  1. Create a user-assigned managed identity. Before you run the following commands, choose a name for your managed identity and replace the \<PLACEHOLDER\> with the name.

    IDENTITY="<YOUR_IDENTITY_NAME>"
    
    az identity create \
        --name $IDENTITY \
        --resource-group $RESOURCE_GROUP
    
  2. Get the identity's resource ID.

    IDENTITY_ID=$(az identity show \
        --name $IDENTITY \
        --resource-group $RESOURCE_GROUP \
        --query id \
        --output tsv)
    

Deploy a self-hosted runner as a job

You can now create a job that uses the container image. In this section, you create a job that runs the self-hosted runner and authenticates with GitHub by using the PAT you generated earlier. The job uses the github-runner scale rule to create job executions based on the number of pending workflow runs.

  1. Create a job in the Container Apps environment.

    az containerapp job create \
        --name "$JOB_NAME" \
        --resource-group "$RESOURCE_GROUP" \
        --environment "$ENVIRONMENT" \
        --trigger-type Event \
        --replica-timeout 1800 \
        --replica-retry-limit 0 \
        --replica-completion-count 1 \
        --parallelism 1 \
        --image "$CONTAINER_REGISTRY_NAME.azurecr.io/$CONTAINER_IMAGE_NAME" \
        --min-executions 0 \
        --max-executions 10 \
        --polling-interval 30 \
        --scale-rule-name "github-runner" \
        --scale-rule-type "github-runner" \
        --scale-rule-metadata "githubAPIURL=https://api.github.com" "owner=$REPO_OWNER" "runnerScope=repo" "repos=$REPO_NAME" "targetWorkflowQueueLength=1" \
        --scale-rule-auth "personalAccessToken=personal-access-token" \
        --cpu "2.0" \
        --memory "4Gi" \
        --secrets "personal-access-token=$GITHUB_PAT" \
        --env-vars "GITHUB_PAT=secretref:personal-access-token" "GH_URL=https://github.com/$REPO_OWNER/$REPO_NAME" "REGISTRATION_TOKEN_API_URL=https://api.github.com/repos/$REPO_OWNER/$REPO_NAME/actions/runners/registration-token" \
        --registry-server "$CONTAINER_REGISTRY_NAME.azurecr.io" \
        --mi-user-assigned "$IDENTITY_ID" \
        --registry-identity "$IDENTITY_ID"
    

    The following table describes the key parameters used in the command.

    Parameter Description
    --replica-timeout The maximum duration a replica can execute.
    --replica-retry-limit The number of times to retry a failed replica.
    --replica-completion-count The number of replicas that must complete successfully before a job execution is considered successful.
    --parallelism The number of replicas to start per job execution.
    --min-executions The minimum number of job executions to run per polling interval.
    --max-executions The maximum number of job executions to run per polling interval.
    --polling-interval The polling interval at which to evaluate the scale rule.
    --scale-rule-name The name of the scale rule.
    --scale-rule-type The type of scale rule to use. To learn more about the GitHub runner scaler, see the KEDA documentation.
    --scale-rule-metadata The metadata for the scale rule. If you're using GitHub Enterprise, update githubAPIURL with its API URL.
    --scale-rule-auth The authentication for the scale rule.
    --secrets The secrets to use for the job.
    --env-vars The environment variables to use for the job.
    --registry-server The container registry server to use for the job. For an Azure Container Registry, the command automatically configures authentication.
    --mi-user-assigned The resource ID of the user-assigned managed identity to assign to the job.
    --registry-identity The resource ID of a managed identity to authenticate with the registry server instead of using a username and password. If possible, an 'acrpull' role assignment is created for the identity automatically.

    The scale rule configuration defines the event source to monitor. Rules are evaluated on each polling interval to determine how many job executions to trigger. For more information, see Set scaling rules.

The event-driven job is now created in the Container Apps environment.

Run a workflow and verify the job

The job is configured to evaluate the scale rule every 30 seconds. During each evaluation, it checks the number of pending workflow runs that require a self-hosted runner and starts a new job execution for each pending workflow, up to a configured maximum of 10 executions.

To verify the job configuration, modify the workflow to use a self-hosted runner and trigger a workflow run. You can then view the job execution logs to see the workflow run.

  1. In the GitHub repository, go to the workflow you generated earlier. It's a YAML file in the .github/workflows directory.

  2. Select Edit in place.

  3. Update the runs-on property to self-hosted:

    runs-on: self-hosted
    
  4. Select Commit changes....

  5. Select Commit changes.

  6. Go to the Actions tab.

    A new workflow is now queued. Within 30 seconds, the job execution starts and the workflow completes soon after.

    Wait for the action to complete before going on the next step.

  7. List the executions of the job to confirm a job execution was created and completed successfully.

    az containerapp job execution list \
        --name "$JOB_NAME" \
        --resource-group "$RESOURCE_GROUP" \
        --output table \
        --query '[].{Status: properties.status, Name: name, StartTime: properties.startTime}'
    

Create an Azure DevOps project and repository

To run a pipeline, you need an Azure DevOps project and repository.

  1. Go to Azure DevOps and sign in to your account.

  2. Select an existing organization or create a new one.

  3. In the organization overview page, select New project and enter the following values.

    Setting Value
    Project name Enter a name for your project.
    Visibility Select Private.
  4. Select Create.

  5. From the side navigation, select Repos.

  6. Under Initialize main branch with a README or .gitignore, select Add a README.

  7. Keep the default values for the rest of the settings and select Initialize.

Create a new agent pool

Create a new agent pool to run the self-hosted runner.

  1. In your Azure DevOps project, expand the left navigation bar and select Project settings.

    Screenshot of the Azure DevOps project settings button.

  2. Under the Pipelines section in the Project settings navigation menu, select Agent pools.

    Screenshot of Azure DevOps agent pools button.

  3. Select Add pool and enter the following values.

    Setting Value
    Pool to link Select New.
    Pool type Select Self-hosted.
    Name Enter container-apps.
    Grant access permission to all pipelines Select this checkbox.
  4. Select Create.

Get an Azure DevOps personal access token

To run a self-hosted runner, you need to create a personal access token (PAT) in Azure DevOps. Use the PAT to authenticate the runner with Azure DevOps. The scale rule also uses the token to check the number of pending pipeline runs and trigger new job executions.

Note

Personal Access Tokens (PATs) expire. Regularly rotate your tokens to keep them valid and maintain uninterrupted service.

  1. In Azure DevOps, select User settings next to your profile picture in the upper-right corner.

  2. Select Personal access tokens.

  3. In the Personal access tokens page, select New Token and enter the following values.

    Setting Value
    Name Enter a name for your token.
    Organization Select the organization you chose or created earlier.
    Scopes Select Custom defined.
    Show all scopes Select Show all scopes.
    Agent Pools (Read & manage) Select Agent Pools (Read & manage).

    Leave all other scopes unselected.

  4. Select Create.

  5. Copy the token value to a secure location.

    You can't retrieve the token after you leave the page.

  6. Define variables that you use to configure the Container Apps jobs later.

    AZP_TOKEN="<AZP_TOKEN>"
    ORGANIZATION_URL="<ORGANIZATION_URL>"
    AZP_POOL="container-apps"
    

    Replace the placeholders with the following values:

    Placeholder Value Comments
    <AZP_TOKEN> The Azure DevOps PAT you generated.
    <ORGANIZATION_URL> The URL of your Azure DevOps organization. Make sure no trailing / is present at the end of the URL. For example, https://dev.azure.com/myorg or https://myorg.visualstudio.com.

Build the Azure Pipelines agent container image

To create a self-hosted agent, you need to build a container image that runs the agent. In this section, you build the container image and push it to a container registry.

Note

The image you build in this tutorial contains a basic self-hosted agent that's suitable for running as a Container Apps job. You can customize it to include other tools or dependencies that your pipelines require.

Important

If your pipelines build .NET Framework applications, you might need to include Mono in your container image. The sample Dockerfile includes .NET SDK but not Mono. Consider using a Windows-based container or adding Mono dependencies if needed.

  1. Back in your terminal, define a name for your container image and registry.

    CONTAINER_IMAGE_NAME="azure-pipelines-agent:1.0"
    CONTAINER_REGISTRY_NAME="<CONTAINER_REGISTRY_NAME>"
    

    Replace <CONTAINER_REGISTRY_NAME> with a unique name for creating a container registry.

    Container registry names must be unique within Azure and be from 5 to 50 characters in length containing numbers and lowercase letters only.

  2. Create a container registry.

    az acr create \
        --name "$CONTAINER_REGISTRY_NAME" \
        --resource-group "$RESOURCE_GROUP" \
        --location "$LOCATION" \
        --sku Basic \
        --admin-enabled true
    
  3. The Dockerfile for creating the runner image is available on GitHub. Run the following command to clone the repository and build the container image in the cloud by using the az acr build command.

    az acr build \
        --registry "$CONTAINER_REGISTRY_NAME" \
        --image "$CONTAINER_IMAGE_NAME" \
        --file "Dockerfile.azure-pipelines" \
        "https://github.com/Azure-Samples/container-apps-ci-cd-runner-tutorial.git"
    

    The image is now available in the container registry.

Create a placeholder self-hosted agent

Before you can run a self-hosted agent in your new agent pool, you need to create a placeholder agent. The placeholder agent ensures the agent pool is available. Pipelines that use the agent pool fail when there's no placeholder agent.

You can run a manual job to register an offline placeholder agent. The job runs once and you can delete it. The placeholder agent doesn't consume any resources in Azure Container Apps or Azure DevOps.

  1. Create a manual job in the Container Apps environment that creates the placeholder agent.

    az containerapp job create -n "$PLACEHOLDER_JOB_NAME" -g "$RESOURCE_GROUP" --environment "$ENVIRONMENT" \
        --trigger-type Manual \
        --replica-timeout 300 \
        --replica-retry-limit 0 \
        --replica-completion-count 1 \
        --parallelism 1 \
        --image "$CONTAINER_REGISTRY_NAME.azurecr.io/$CONTAINER_IMAGE_NAME" \
        --cpu "2.0" \
        --memory "4Gi" \
        --secrets "personal-access-token=$AZP_TOKEN" "organization-url=$ORGANIZATION_URL" \
        --env-vars "AZP_TOKEN=secretref:personal-access-token" "AZP_URL=secretref:organization-url" "AZP_POOL=$AZP_POOL" "AZP_PLACEHOLDER=1" "AZP_AGENT_NAME=placeholder-agent" \
        --registry-server "$CONTAINER_REGISTRY_NAME.azurecr.io"
    

    The following table describes the key parameters used in the command.

    Parameter Description
    --replica-timeout The maximum duration a replica can execute.
    --replica-retry-limit The number of times to retry a failed replica.
    --replica-completion-count The number of replicas that must complete successfully before a job execution is considered successful.
    --parallelism The number of replicas to start per job execution.
    --secrets The secrets to use for the job.
    --env-vars The environment variables to use for the job.
    --registry-server The container registry server to use for the job. For an Azure Container Registry, the command automatically configures authentication.

    Setting the AZP_PLACEHOLDER environment variable configures the agent container to register as an offline placeholder agent without running a job.

  2. Execute the manual job to create the placeholder agent.

    az containerapp job start -n "$PLACEHOLDER_JOB_NAME" -g "$RESOURCE_GROUP"
    
  3. List the executions of the job to confirm a job execution was created and completed successfully.

    az containerapp job execution list \
        --name "$PLACEHOLDER_JOB_NAME" \
        --resource-group "$RESOURCE_GROUP" \
        --output table \
        --query '[].{Status: properties.status, Name: name, StartTime: properties.startTime}'
    
  4. Verify the placeholder agent was created in Azure DevOps.

    1. In Azure DevOps, go to your project.
    2. Select Project settings > Agent pools > container-apps > Agents.
    3. Confirm that a placeholder agent named placeholder-agent is listed and its status is offline.
  5. You don't need the job again. You can delete it.

    az containerapp job delete -n "$PLACEHOLDER_JOB_NAME" -g "$RESOURCE_GROUP"
    

Create a self-hosted agent as an event-driven job

After you create a placeholder agent, you can create a self-hosted agent. In this section, you create an event-driven job that runs a self-hosted agent when a pipeline is triggered.

Important

Ensure that your Azure DevOps organization URL is correct and accessible. The KEDA azure-pipelines scaler requires proper authentication and network connectivity to monitor the pipeline queue.

az containerapp job create -n "$JOB_NAME" -g "$RESOURCE_GROUP" --environment "$ENVIRONMENT" \
    --trigger-type Event \
    --replica-timeout 1800 \
    --replica-retry-limit 0 \
    --replica-completion-count 1 \
    --parallelism 1 \
    --image "$CONTAINER_REGISTRY_NAME.azurecr.io/$CONTAINER_IMAGE_NAME" \
    --min-executions 0 \
    --max-executions 10 \
    --polling-interval 30 \
    --scale-rule-name "azure-pipelines" \
    --scale-rule-type "azure-pipelines" \
    --scale-rule-metadata "poolName=$AZP_POOL" "targetPipelinesQueueLength=1" \
    --scale-rule-auth "personalAccessToken=personal-access-token" "organizationURL=organization-url" \
    --cpu "2.0" \
    --memory "4Gi" \
    --secrets "personal-access-token=$AZP_TOKEN" "organization-url=$ORGANIZATION_URL" \
    --env-vars "AZP_TOKEN=secretref:personal-access-token" "AZP_URL=secretref:organization-url" "AZP_POOL=$AZP_POOL" \
    --registry-server "$CONTAINER_REGISTRY_NAME.azurecr.io"

The following table describes the scale rule parameters used in the command.

Parameter Description
--min-executions The minimum number of job executions to run per polling interval.
--max-executions The maximum number of job executions to run per polling interval.
--polling-interval The polling interval at which to evaluate the scale rule.
--scale-rule-name The name of the scale rule.
--scale-rule-type The type of scale rule to use. To learn more about the Azure Pipelines scaler, see the KEDA documentation.
--scale-rule-metadata The metadata for the scale rule.
--scale-rule-auth The authentication for the scale rule.

The scale rule configuration defines the event source to monitor. Rules are evaluated on each polling interval to determine how many job executions to trigger. For more information, see Set scaling rules.

The event-driven job is now created in the Container Apps environment.

Run a pipeline and verify the job

After you configure a self-hosted agent job, run a pipeline to verify that it works correctly.

  1. In the left-hand navigation of your Azure DevOps project, go to Pipelines.

  2. Select Create pipeline.

  3. Select Azure Repos Git as the location of your code.

  4. Select the repository you created earlier.

  5. Select Starter pipeline.

  6. In the pipeline YAML, change the pool from vmImage: ubuntu-latest to name: container-apps.

    pool:
      name: container-apps
    
  7. Select Save and run.

    The pipeline runs and uses the self-hosted agent job you created in the Container Apps environment.

  8. List the executions of the job to confirm a job execution was created and completed successfully.

    az containerapp job execution list \
        --name "$JOB_NAME" \
        --resource-group "$RESOURCE_GROUP" \
        --output table \
        --query '[].{Status: properties.status, Name: name, StartTime: properties.startTime}'
    

Troubleshooting

If you encounter issues with your self-hosted agents, try the following troubleshooting steps.

Pipeline jobs remain queued and don't trigger Container Apps jobs

If your pipelines stay in a queued state and don't trigger job executions, then try these steps:

  1. Verify the scale rule configuration. Check that the scale rule metadata matches your Azure DevOps setup.

    az containerapp job show \
        --name "$JOB_NAME" \
        --resource-group "$RESOURCE_GROUP" \
        --query "properties.configuration.eventTriggerConfig.scale.rules[0]"
    
  2. Ensure your personal access token has the correct permissions and isn't expired.

  3. The Container Apps environment must be able to reach your Azure DevOps organization URL, so verify network connectivity in your environment.

  4. Check the polling interval. The default polling interval is 30 seconds. You can increase it if needed, but this change might delay job execution.

  5. Check if there are any error messages in the job execution logs.

    az containerapp job execution list \
        --name "$JOB_NAME" \
        --resource-group "$RESOURCE_GROUP" \
        --output table
    

Missing dependencies in container image

If you encounter errors like "Unable to locate executable file: 'mono'" when building .NET Framework applications:

  1. Use appropriate base images. Ensure that your container image includes all required dependencies for your build process.

  2. If you need to build .NET Framework applications on Linux, add Mono to your container image with the following command:

    # Add to your Dockerfile
    RUN apt-get update && apt-get install -y mono-complete
    
  3. Consider Windows containers. For .NET Framework applications, consider using Windows-based container images instead of Linux.

  4. For versions, use .NET Core/5+. Modern .NET versions don't require Mono and work better with Linux containers.

Agent registration issues

If agents fail to register with Azure DevOps:

  1. Check token permissions. Ensure the PAT has "Agent Pools (Read & manage)" permissions.

  2. Verify organization URL. Make sure the organization URL is correct and doesn't have trailing slashes.

  3. Check agent pool name. Confirm the agent pool name matches exactly between Azure DevOps and your job configuration.

Tip

Having issues? Let us know on GitHub by opening an issue in the Azure Container Apps repo.

Clean up resources

When you're done, run the following command to delete the resource group that contains your Container Apps resources.

Caution

The following command deletes the specified resource group and all resources contained within it. If the resource group contains resources outside the scope of this tutorial, the command deletes those resources too.

az group delete \
    --resource-group $RESOURCE_GROUP

To delete your GitHub repository, see Deleting a repository.

Next steps