Tutorial: Deploy environments in CI/CD with GitHub

Continuous integration and continuous delivery (CI/CD) is a software development approach that helps teams to automate the process of building, testing, and deploying software changes. CI/CD enables you to release software changes more frequently and with greater confidence.

In this tutorial, you'll Learn how to integrate Azure Deployment Environments into your CI/CD pipeline by using GitHub Actions. You can use any GitOps provider that supports CI/CD, like GitHub Actions, Azure Arc, GitLab, or Jenkins.

You use a workflow that features three branches: main, dev, and test.

  • The main branch is always considered production.
  • You create feature branches from the main branch.
  • You create pull requests to merge feature branches into main.

This workflow is a small example for the purposes of this tutorial. Real world workflows may be more complex.

Before beginning this tutorial, you can familiarize yourself with Deployment Environments resources and concepts by reviewing Key concepts for Azure Deployment Environments.

In this tutorial, you learn how to:

  • Create and configure a dev center
  • Create a key vault
  • Create and configure a GitHub repository
  • Connect the catalog to your dev center
  • Configure deployment identities
  • Configure GitHub environments
  • Test the CI/CD pipeline

Prerequisites

  • An Azure account with an active subscription.
  • Owner permissions on the Azure subscription.
  • A GitHub account.
    • If you don't have one, sign up for free.
  • Install Git.
  • Install the Azure CLI.

1. Create and configure a dev center

In this section, you create a dev center and project with three environment types; Dev, Test and Prod

  • The Prod environment type contains the single production environment
  • A new environment is created in Dev for each feature branch
  • A new environment is created in Test for each pull request

1.1 Setup the Azure CLI

To begin, sign in to Azure. Run the following command, and follow the prompts to complete the authentication process.

az login

Next, install the Azure Dev Center extension for the CLI.

az extension add --name devcenter --upgrade

Now that the current extension is installed, register the Microsoft.DevCenter namespace.

az provider register --namespace Microsoft.DevCenter

Tip

Throughout this tutorial, you'll save several values as environment variables to use later. You may also want to record these value elsewhere to ensure they are available when needed.

Get your user's ID and set it to an environment variable for later:

MY_AZURE_ID=$(az ad signed-in-user show --query id -o tsv)

Retrieve the subscription ID for your current subscription.

AZURE_SUBSCRIPTION_ID=$(az account show --query id --output tsv)

Retrieve the tenant ID for your current tenant.

AZURE_TENANT_ID=$(az account show --query tenantId --output tsv)

Set the following environment variables:

LOCATION="eastus"
AZURE_RESOURCE_GROUP="my-dev-center-rg"
AZURE_DEVCENTER="my-dev-center"
AZURE_PROJECT="my-project"
AZURE_KEYVAULT="myuniquekeyvaultname"

1.2 Create a dev center

A dev center is a collection of projects and environments that have similar settings. Dev centers provide access to a catalog of templates and artifacts that can be used to create environments. Dev centers also provide a way to manage access to environments and projects.

Create a Resource Group

az group create \
  --name $AZURE_RESOURCE_GROUP \
  --location $LOCATION

Create a new Dev Center

az devcenter admin devcenter create \
  --name $AZURE_DEVCENTER \
  --identity-type SystemAssigned \
  --resource-group $AZURE_RESOURCE_GROUP \
  --location $LOCATION

The previous command outputs JSON. Save the values for id and identity.principalId as environment variables to use later.

AZURE_DEVCENTER_ID=<id>
AZURE_DEVCENTER_PRINCIPAL_ID=<identity.principalId>

1.3 Assign dev center identity owner role on subscription

A dev center needs permissions to assign roles on subscriptions associated with environment types.

To reduce unnecessary complexity, in this tutorial, you use a single subscription for the dev center and all environment types. In practice, the dev center and target deployment subscriptions would likely be separate subscriptions with different policies applied.

az role assignment create \
  --scope /subscriptions/$AZURE_SUBSCRIPTION_ID \
  --role Owner \
  --assignee-object-id $AZURE_DEVCENTER_PRINCIPAL_ID \
  --assignee-principal-type ServicePrincipal

1.4 Create the environment types

At the dev center level, environment types define the environments that development teams can create, like dev, test, sandbox, preproduction, or production.

Create three new Environment types: Dev, Test, and Prod.

az devcenter admin environment-type create \
  --name Dev \
  --resource-group $AZURE_RESOURCE_GROUP \
  --dev-center $AZURE_DEVCENTER
az devcenter admin environment-type create \
  --name Test \
  --resource-group $AZURE_RESOURCE_GROUP \
  --dev-center $AZURE_DEVCENTER
az devcenter admin environment-type create \
  --name Prod \
  --resource-group $AZURE_RESOURCE_GROUP \
  --dev-center $AZURE_DEVCENTER

1.5 Create a project

A project is the point of access for the development team. Each project is associated with a dev center.

Create a new Project

az devcenter admin project create \
  --name $AZURE_PROJECT \
  --resource-group $AZURE_RESOURCE_GROUP \
  --location $LOCATION \
  --dev-center-id $AZURE_DEVCENTER_ID

The previous command outputs JSON. Save the id value as an environment variable to use later.

AZURE_PROJECT_ID=<id>

Assign yourself the "DevCenter Project Admin" role on the project

az role assignment create \
  --scope "$AZURE_PROJECT_ID" \
  --role "DevCenter Project Admin" \
  --assignee-object-id $MY_AZURE_ID \
  --assignee-principal-type User

1.6 Create project environment types

At the project level, platform engineers specify which environment types are appropriate for the development team.

Create a new Project Environment Type for each of the Environment Types we created on the dev center

az devcenter admin project-environment-type create \
  --name Dev \
  --roles "{\"b24988ac-6180-42a0-ab88-20f7382dd24c\":{}}" \
  --deployment-target-id /subscriptions/$AZURE_SUBSCRIPTION_ID \
  --resource-group $AZURE_RESOURCE_GROUP \
  --location $LOCATION \
  --project $AZURE_PROJECT \
  --identity-type SystemAssigned \
  --status Enabled
az devcenter admin project-environment-type create \
  --name Test \
  --roles "{\"b24988ac-6180-42a0-ab88-20f7382dd24c\":{}}" \
  --deployment-target-id /subscriptions/$AZURE_SUBSCRIPTION_ID \
  --resource-group $AZURE_RESOURCE_GROUP \
  --location $LOCATION \
  --project $AZURE_PROJECT \
  --identity-type SystemAssigned \
  --status Enabled
az devcenter admin project-environment-type create \
  --name Prod \
  --roles "{\"b24988ac-6180-42a0-ab88-20f7382dd24c\":{}}" \
  --deployment-target-id /subscriptions/$AZURE_SUBSCRIPTION_ID \
  --resource-group $AZURE_RESOURCE_GROUP \
  --location $LOCATION \
  --project $AZURE_PROJECT \
  --identity-type SystemAssigned \
  --status Enabled

2. Create a key vault

In this section, you create a new key vault. You use this key vault later in the tutorial to save a personal access token from GitHub.

az keyvault create \
  --name $AZURE_KEYVAULT \
  --resource-group $AZURE_RESOURCE_GROUP \
  --location $LOCATION \
  --enable-rbac-authorization true

Again, save the id from the previous command's JSON output as an environment variable.

AZURE_KEYVAULT_ID=<id>

Give yourself the "Key Vault Administrator" role on the new key vault.

az role assignment create \
  --scope $AZURE_KEYVAULT_ID \
  --role "Key Vault Administrator" \
  --assignee-object-id $MY_AZURE_ID \
  --assignee-principal-type User

Assign the dev center's identity the role of "Key Vault Secrets User"

az role assignment create \
  --scope $AZURE_KEYVAULT_ID \
  --role "Key Vault Secrets User" \
  --assignee-object-id $AZURE_DEVCENTER_PRINCIPAL_ID \
  --assignee-principal-type ServicePrincipal

3. Create and configure a GitHub repository

In this section, you create a new GitHub repository to store a catalog. Azure Deployment Environments supports both GitHub and Azure DevOps repositories. In this tutorial, you use GitHub.

3.1 Create a new GitHub repository

In this step, you create a new repository in your GitHub account that has a predefined directory structure, branches, and files. These items are generated from a sample template repository.

  1. Use this link to generate a new GitHub repository from the sample template.

    Screenshot showing the GitHub create repository from template page.

  2. If you don't have a paid GitHub account, set your repository to Public.

  3. Select Create repository from template.

  4. On the Actions tab, notice that the Create Environment action fails. This behavior is expected, you can proceed with the next step.

3.2 Protect the repository's main branch

You can protect important branches by setting branch protection rules. Protection rules define whether collaborators can delete or force push to the branch. They also set requirements for any pushes to the branch, such as passing status checks or a linear commit history.

Note

Protected branches are available in public repositories with GitHub Free and GitHub Free for organizations, and in public and private repositories with GitHub Pro, GitHub Team, GitHub Enterprise Cloud, and GitHub Enterprise Server. For more information, see "GitHub’s products".

  1. If it's not already open, navigate to the main page of your repository.

  2. Under your repository name, select Settings. If you can't see the "Settings" tab, select the ... dropdown menu, then select Settings.

    Screenshot showing the GitHub repository page with settings highlighted.

  3. In the Code and automation section of the sidebar, select Branches.

    Screenshot showing the settings page, with branches highlighted.

  4. Under Branch protection rules, select Add branch protection rule.

    Screenshot showing the branch protection rule page, with Add branch protection rule highlighted.

  5. Under Branch name pattern, enter <main>.

    Screenshot showing the branch name pattern text box, with main highlighted.

  6. Under Protect matching branches, select Require a pull request before merging.

    Screenshot showing protect matching branches with Require a pull request before merging selected and highlighted.

  7. Optionally, you can enable more protection rules.

  8. Select Create.

3.3 Configure repository variables

Note

Configuration variables for GitHub Actions are in beta and subject to change.

  1. In the Security section of the sidebar, select Secrets and variables, then select Actions.

  2. Select the Variables tab.

    Screenshot showing the Security section of the sidebar with Actions highlighted.

  3. For each item in the table:

    1. Select New repository variable.
    2. In the Name field, enter the variable name.
    3. In the Value field, enter the value described in the table.
    4. Select Add variable.
    Variable name Variable value
    AZURE_DEVCENTER Dev center name
    AZURE_PROJECT Project name
    AZURE_CATALOG Set to: Environments
    AZURE_CATALOG_ITEM Set to: FunctionApp
    AZURE_SUBSCRIPTION_ID Azure subscription ID (GUID)
    AZURE_TENANT_ID Azure tenant ID (GUID)

    Screenshot showing the variables page with the variables table.

3.4 Create a GitHub personal access token

Next, create a fine-grained personal access token to enable your dev center to connect to your repository and consume the environment catalog.

Note

Fine-grained personal access token are currently in beta and subject to change. To leave feedback, see the feedback discussion.

  1. In the upper-right corner of any page on GitHub.com, select your profile photo, then select Settings.

  2. In the left sidebar, select Developer settings.

  3. In the left sidebar, under Personal access tokens, select Fine-grained tokens, and then select Generate new token.

    Screenshot showing the GitHub personal access token options, with Fine-grained tokens and Generate new token highlighted.

  4. On the New fine-grained personal access token page, under Token name, enter a name for the token.

  5. Under Expiration, select an expiration for the token.

  6. Select your GitHub user under Resource owner.

  7. Under Repository access, select Only select repositories then in the Selected repositories dropdown, search and select the repository you created.

    Screenshot showing GitHub repository access options, with Only select repositories highlighted.

  8. Under Permissions, select Repository permissions, and change Contents to Read-only.

    Screenshot showing GitHub repository permissions with Contents highlighted.

  9. Select Generate token.

  10. Copy your personal access token now. You cannot view it again.

3.5 Save personal access token to key vault

Next, save the PAT token as a key vault secret named pat.

az keyvault secret set \
    --name pat \
    --vault-name $AZURE_KEYVAULT \
    --value "github_pat_..."

4. Connect the catalog to your dev center

A catalog is a repository that contains a set of environment definitions. Catalog items consist of an IaC template and a manifest file. The template defines the environment, and the manifest provides metadata about the template. Development teams use environment definitions from the catalog to create environments.

The template you used to create your GitHub repository contains a catalog in the Environments folder.

Add the catalog to your dev center

In the following command, replace < Organization/Repository > with your GitHub organization and repository name.

az devcenter admin catalog create \
    --name Environments \
    --resource-group $AZURE_RESOURCE_GROUP \
    --dev-center $AZURE_DEVCENTER \
    --git-hub path="/Environments" branch="main" secret-identifier="https://$AZURE_KEYVAULT.vault.azure.net/secrets/pat" uri="https://github.com/< Organization/Repository >.git"

5. Configure deployment identities

OpenID Connect with GitHub Actions is an authentication method that uses short-lived tokens to offer hardened security. It's the recommended way to authenticate GitHub Actions to Azure.

You can also authenticate a service principal directly using a secret, but that is out of scope for this tutorial.

5.1 Generate deployment identities

  1. Register new Active Directory applications and service principals for each of the three environment types.

    Create the Active Directory application for Dev.

    az ad app create --display-name "$AZURE_PROJECT-Dev"
    

    This command outputs JSON with an id that you use when creating federated credentials with Graph API, and an appId (also called client ID).

    Set the following environment variables:

    DEV_AZURE_CLIENT_ID=<appId>
    DEV_APPLICATION_ID=<id>
    

    Repeat for Test:

    az ad app create --display-name "$AZURE_PROJECT-Test"
    
    TEST_AZURE_CLIENT_ID=<appId>
    TEST_APPLICATION_ID=<id>
    

    And Prod:

    az ad app create --display-name "$AZURE_PROJECT-Prod"
    
    PROD_AZURE_CLIENT_ID=<appId>
    PROD_APPLICATION_ID=<id>
    
  2. Create a service principal for each application.

    Run the following command to create a new service principal for Dev.

     az ad sp create --id $DEV_AZURE_CLIENT_ID
    

    This command generates JSON output with a different id and will be used in the next step.

    Set the following environment variables:

    DEV_SERVICE_PRINCIPAL_ID=<id>
    

    Repeat for Test:

     az ad sp create --id $TEST_AZURE_CLIENT_ID
    
    TEST_SERVICE_PRINCIPAL_ID=<id>
    

    And Prod:

     az ad sp create --id $PROD_AZURE_CLIENT_ID
    
    PROD_SERVICE_PRINCIPAL_ID=<id>
    
  3. Run the following commands to create a new federated identity credentials for each active directory application.

    In each of the three following commands, replace < Organization/Repository > with your GitHub organization and repository name.

    Create the federated identity credential for Dev:

    az rest --method POST \
        --uri "https://graph.microsoft.com/beta/applications/$DEV_APPLICATION_ID/federatedIdentityCredentials" \
        --body '{"name":"ADEDev","issuer":"https://token.actions.githubusercontent.com","subject":"repo:< Organization/Repository >:environment:Dev","description":"Dev","audiences":["api://AzureADTokenExchange"]}'
    

    For Test:

    az rest --method POST \
        --uri "https://graph.microsoft.com/beta/applications/$TEST_APPLICATION_ID/federatedIdentityCredentials" \
        --body '{"name":"ADETest","issuer":"https://token.actions.githubusercontent.com","subject":"repo:< Organization/Repository >:environment:Test","description":"Test","audiences":["api://AzureADTokenExchange"]}'
    

    And Prod:

    az rest --method POST \
        --uri "https://graph.microsoft.com/beta/applications/$PROD_APPLICATION_ID/federatedIdentityCredentials" \
        --body '{"name":"ADEProd","issuer":"https://token.actions.githubusercontent.com","subject":"repo:< Organization/Repository >:environment:Prod","description":"Prod","audiences":["api://AzureADTokenExchange"]}'
    

5.2 Assign roles to deployment identities

  1. Assign each deployment identity the Reader role on the project.

    az role assignment create \
        --scope "$AZURE_PROJECT_ID" \
        --role Reader \
        --assignee-object-id $DEV_SERVICE_PRINCIPAL_ID \
        --assignee-principal-type ServicePrincipal
    
    az role assignment create \
        --scope "$AZURE_PROJECT_ID" \
        --role Reader \
        --assignee-object-id $TEST_SERVICE_PRINCIPAL_ID \
        --assignee-principal-type ServicePrincipal
    
    az role assignment create \
        --scope "$AZURE_PROJECT_ID" \
        --role Reader \
        --assignee-object-id $PROD_SERVICE_PRINCIPAL_ID \
        --assignee-principal-type ServicePrincipal
    
  2. Assign each deployment identity the "Deployment Environments User" role to it's corresponding environment type

    az role assignment create \
        --scope "$AZURE_PROJECT_ID/environmentTypes/Dev" \
        --role "Deployment Environments User" \
        --assignee-object-id $DEV_SERVICE_PRINCIPAL_ID \
        --assignee-principal-type ServicePrincipal
    
    az role assignment create \
        --scope "$AZURE_PROJECT_ID/environmentTypes/Test" \
        --role "Deployment Environments User" \
        --assignee-object-id $TEST_SERVICE_PRINCIPAL_ID \
        --assignee-principal-type ServicePrincipal
    
    az role assignment create \
        --scope "$AZURE_PROJECT_ID/environmentTypes/Prod" \
        --role "Deployment Environments User" \
        --assignee-object-id $PROD_SERVICE_PRINCIPAL_ID \
        --assignee-principal-type ServicePrincipal
    

6. Configure GitHub environments

With GitHub environments, you can configure environments with protection rules and secrets. A workflow job that references an environment must follow any protection rules for the environment before running or accessing the environment's secrets.

Create three environments: Dev, Test, and Prod to map to the project's environment types.

Note

Environments, environment secrets, and environment protection rules are available in public repositories for all products. For access to environments, environment secrets, and deployment branches in private or internal repositories, you must use GitHub Pro, GitHub Team, or GitHub Enterprise. For access to other environment protection rules in private or internal repositories, you must use GitHub Enterprise. For more information, see "GitHub’s products."

6.1 Create the Dev environment

  1. On GitHub.com, navigate to the main page of your repository.

  2. Under your repository name, select Settings. If you can't see the "Settings" tab, select the ... dropdown menu, then select Settings.

  3. In the left sidebar, select Environments.

  4. Select New environment and enter Dev for the environment name, then select Configure environment.

    Screenshot showing the Environments Add pane, with the environment name Dev, and Configure Environment highlighted.

  5. Under Environment secrets, select Add Secret and enter AZURE_CLIENT_ID for Name.

    Screenshot showing the Environment Configure Dev pane, with Add secret highlighted.

  6. For Value, enter the client ID (appId) for the Dev Azure AD app you created earlier (saved as the $DEV_AZURE_CLIENT_ID environment variable).

    Screenshot of the Add secret box with the name AZURE CLIENT ID, the value set to an ID number, and add secret highlighted.

  7. Select Add secret.

6.2 Create the Test environment

Return to the main environments page by selecting Environments in the left sidebar.

  1. Select New environment and enter Test for the environment name, then select Configure environment.

  2. Under Environment secrets, select Add Secret and enter AZURE_CLIENT_ID for Name.

  3. For Value, enter the client ID (appId) for the Test Azure AD app you created earlier (saved as the $TEST_AZURE_CLIENT_ID environment variable).

  4. Select Add secret.

6.3 Create the Prod environment

Once more, return to the main environments page by selecting Environments in the left sidebar

  1. Select New environment and enter Prod for the environment name, then select Configure environment.

  2. Under Environment secrets, select Add Secret and enter AZURE_CLIENT_ID for Name.

  3. For Value, enter the client ID (appId) for the Prod Azure AD app you created earlier (saved as the $PROD_AZURE_CLIENT_ID environment variable).

  4. Select Add secret.

Next, set yourself as a required reviewer for this environment. When attempting to deploy to Prod, the GitHub Actions wait for an approval before starting. While a job is awaiting approval, it has a status of "Waiting". If a job isn't approved within 30 days, it automatically fails.

For more information about environments and required approvals, see "Using environments for deployment."

  1. Select Required reviewers.

  2. Search for and select your GitHub user. You may enter up to six people or teams. Only one of the required reviewers needs to approve the job for it to proceed.

  3. Select Save protection rules.

Finally configure main as the deployment branch:

  1. In the Deployment branches dropdown, choose Selected branches.

  2. Select Add deployment branch rule and enter main for the Branch name pattern.

  3. Select Add rule.

7. Test the CI/CD pipeline

In this section, you make some changes to the repository and test the CI/CD pipeline.

7.1 Clone the repository

  1. In your terminal, cd into a folder where you'd like to clone your repository locally.

  2. Clone the repository. Be sure to replace < Organization/Repository > in the following command with your GitHub organization and repository name.

    git clone https://github.com/< Organization/Repository >.git
    
  3. Navigate into the cloned directory.

    cd Repository
    
  4. Next, create a new branch and publish it remotely.

    git checkout -b feature1
    
    git push -u origin feature1
    

    A new environment is created in Azure specific to this branch.

  5. Go to GitHub and navigate to the main page of your newly created repository.

  6. Under your repository name, select Actions.

    You should see a new Create Environment workflow running.

7.2 Make a change to the code

  1. Open the locally cloned repo in VS Code.

  2. In the ADE.Tutorial folder, make a change to a file.

  3. Save your change.

7.3 Push your changes to update the environment

  1. Stage your changes and push to the feature1 branch.

    git add .
    git commit -m '<commit message>'
    git push
    
  2. On your repository's Actions page, you see a new Update Environment workflow running.

7.4 Create a pull request

  1. Create a pull request on GitHub.com main <- feature1.

  2. On your repository's Actions page, you see a new workflow is started to create an environment specific to the PR using the Test environment type.

7.5 Merge the PR

  1. On GitHub, navigate to the pull request you created.

  2. Merge the PR.

    Your changes are published into the production environment, and the branch and pull request environments are deleted.

Clean up resources

If you don't plan to use any of the resources that you created, delete them so you don't incur any further charges. If you've deployed the sample application in a different resource group, you might want to repeat the following steps.

To delete resources by using the Azure portal:

  1. Select the menu button in the upper-left corner, and then select Resource groups.

  2. From the list, select the resource group that you created.

  3. Select Delete resource group.

  4. Enter the resource group name. Then select Delete.

To delete resources by using the Azure CLI, enter the following command:

az group delete --name <my-dev-center-rg>

Remember, deleting the resource group deletes all of the resources within it.

Next steps