title | description | author | ms.author | ms.topic | ms.service | ms.date | ms.custom |
---|---|---|---|---|---|---|---|
Build custom virtual machine images with GitHub Actions and Azure |
Learn how to build custom virtual machine images with GitHub Actions and Azure |
juliakm |
jukullam |
quickstart |
azure |
05/23/2022 |
github-actions-azure, devx-track-azurecli, mode-portal |
Get started with the GitHub Actions by creating a workflow to build a virtual machine image.
With GitHub Actions, you can speed up your CI/CD process by creating custom virtual machine images with artifacts from your workflows. You can both build images and distribute them to a Shared Image Gallery.
You can then use these images to create virtual machines and virtual machine scale sets.
The build virtual machine image action uses the Azure Image Builder service.
- An Azure account with an active subscription. Create an account for free.
- A GitHub account with an active repository. If you don't have one, sign up for free.
- This example uses the Java Spring PetClinic Sample Application.
- A Shared Image Gallery.
- Create a Shared Image Gallery with the Azure CLI
- Create an Azure Shared Image Gallery using the portal (Windows, Linux)
A workflow is defined by a YAML (.yml) file in the /.github/workflows/
path in your repository. This definition contains the various steps and parameters that make up the workflow.
The file has three sections:
Section | Tasks |
---|---|
Authentication | 1. Add a user-managed identity. 2. Set up a service principal or Open ID Connect. 3. Create a GitHub secret. |
Build | 1. Set up the environment. 2. Build the app. |
Image | 1. Create a VM Image. 2. Create a virtual machine. |
You'll need a user-managed identity for Azure Image Builder(AIB) to distribute images. Your Azure user-assigned managed identity will be used during the image build to read and write images to a Shared Image Gallery.
-
Create a user-managed identity with Azure CLI or the Azure portal. Write down the name of your managed identity.
-
Customize this JSON code. Replace the placeholders for
{subscriptionID}
and{rgName}
with your subscription ID and resource group name.{ "properties": { "roleName": "Image Creation Role", "IsCustom": true, "description": "Azure Image Builder access to create resources for the image build", "assignableScopes": [ "/subscriptions/{subscriptionID}/resourceGroups/{rgName}" ], "permissions": [ { "actions": [ "Microsoft.Compute/galleries/read", "Microsoft.Compute/galleries/images/read", "Microsoft.Compute/galleries/images/versions/read", "Microsoft.Compute/galleries/images/versions/write", "Microsoft.Compute/images/write", "Microsoft.Compute/images/read", "Microsoft.Compute/images/delete" ], "notActions": [], "dataActions": [], "notDataActions": [] } ] } } ```
-
Use this JSON code to create a new custom role with JSON.
To use Azure login, you'll need an Azure service principal or Open ID Connect.
In this example, you'll create a secret named AZURE_CREDENTIALS
that you can use to authenticate with Azure.
-
If you do not have an existing application, register a new Active Directory application to use with your service principal.
appName="myApp" az ad app create \ --display-name $appName \ --homepage "http://localhost/$appName" \ --identifier-uris http://localhost/$appName
-
Create a new service principal in the Azure portal for your app.
az ad sp create-for-rbac --name "myApp" --role contributor \ --scopes /subscriptions/{subscription-id}/resourceGroups/{resource-group} \ --sdk-auth
-
Copy the JSON object for your service principal.
{ "clientId": "<GUID>", "clientSecret": "<GUID>", "subscriptionId": "<GUID>", "tenantId": "<GUID>", (...) }
-
Open your GitHub repository and go to Settings.
:::image type="content" source="media/github-repo-settings.png" alt-text="Select Settings in the navigation.":::
-
Select Secrets and then New Secret.
:::image type="content" source="media/select-secrets.png" alt-text="Choose to add a secret.":::
-
Paste in your JSON object for your service principal with the name
AZURE_CREDENTIALS
.:::image type="content" source="media/azure-secret-add.png" alt-text="Add a secret in GitHub.":::
-
Save by selecting Add secret.
Open ID Connect is an authentication method that uses short-lived tokens. Setting up OpenID Connect with GitHub Actions is more complex process that offers hardened security.
-
If you do not have an existing application, register a new Active Directory application and service principal that can access resources. Create the Active Directory application.
az ad app create --display-name myApp
This command will output JSON with an
appId
that is yourclient-id
. Save the value to use as theAZURE_CLIENT_ID
GitHub secret later.You'll use the
objectId
value when creating federated credentials with Graph API and reference it as theAPPLICATION-OBJECT-ID
. -
Create a service principal. Replace the
$appID
with the appId from your JSON output.This command generates JSON output with a different
objectId
and will be used in the next step. The newobjectId
is theassignee-object-id
.Copy the
appOwnerTenantId
to use as a GitHub secret forAZURE_TENANT_ID
later.az ad sp create --id $appId
-
Create a new role assignment by subscription and object. By default, the role assignment will be tied to your default subscription. Replace
$subscriptionId
with your subscription ID,$resourceGroupName
with your resource group name, and$assigneeObjectId
with the generatedassignee-object-id
. Learn how to manage Azure subscriptions with the Azure CLI.az role assignment create --role contributor --subscription $subscriptionId --assignee-object-id $assigneeObjectId --assignee-principal-type ServicePrincipal --scopes /subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Web/sites/
-
Run the following command to create a new federated identity credential for your active directory application.
- Replace
APPLICATION-OBJECT-ID
with the objectId (generated while creating app) for your Active Directory application. - Set a value for
CREDENTIAL-NAME
to reference later. - Set the
subject
. The value of this is defined by GitHub depending on your workflow:- Jobs in your GitHub Actions environment:
repo:< Organization/Repository >:environment:< Name >
- For Jobs not tied to an environment, include the ref path for branch/tag based on the ref path used for triggering the workflow:
repo:< Organization/Repository >:ref:< ref path>
. For example,repo:n-username/ node_express:ref:refs/heads/my-branch
orrepo:n-username/ node_express:ref:refs/tags/my-tag
. - For workflows triggered by a pull request event:
repo:< Organization/Repository >:pull_request
.
- Jobs in your GitHub Actions environment:
az rest --method POST --uri 'https://graph.microsoft.com/beta/applications/<APPLICATION-OBJECT-ID>/federatedIdentityCredentials' --body '{"name":"<CREDENTIAL-NAME>","issuer":"https://token.actions.githubusercontent.com","subject":"repo:organization/repository:ref:refs/heads/main","description":"Testing","audiences":["api://AzureADTokenExchange"]}'
- Replace
-
Open your GitHub repository and go to Settings.
:::image type="content" source="media/github-repo-settings.png" alt-text="Select Settings in the navigation.":::
-
Select Secrets and then New Secret.
:::image type="content" source="media/select-secrets.png" alt-text="Choose to add a secret.":::
-
Create secrets for
AZURE_CLIENT_ID
,AZURE_TENANT_ID
, andAZURE_SUBSCRIPTION_ID
. Use these values from your Active Directory application for your GitHub secrets:GitHub Secret Active Directory Application AZURE_CLIENT_ID Application (client) ID AZURE_TENANT_ID Directory (tenant) ID AZURE_SUBSCRIPTION_ID Subscription ID -
Save each secret by selecting Add secret.
To learn how to create a Create an active directory application, service principal, and federated credentials in Azure portal, see Connect GitHub and Azure.
Use your GitHub secret with the Azure Login action to authenticate to Azure.
In this workflow, you authenticate using the Azure login action with the service principal details stored in secrets.AZURE_CREDENTIALS
. Then, you run an Azure CLI action. For more information about referencing GitHub secrets in a workflow file, see Using encrypted secrets in a workflow in GitHub Docs.
on: [push]
name: Create Custom VM Image
jobs:
build-image:
runs-on: ubuntu-latest
steps:
- name: Log in with Azure
uses: azure/login@v1
with:
creds: '${{ secrets.AZURE_CREDENTIALS }}'
For Open ID Connect you'll use a federated credential associated with your Active Directory app.
For more information about referencing GitHub secrets in a workflow file, see Using encrypted secrets in a workflow in GitHub Docs.
on: [push]
name: Create Custom VM Image
jobs:
build-image:
runs-on: ubuntu-latest
steps:
- name: Log in with Azure
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
Set up the Java environment with the Java Setup SDK action. For this example, you'll set up the environment, build with Maven, and then output an artifact.
GitHub artifacts are a way to share files in a workflow between jobs. You'll create an artifact to hold the JAR file and then add it to the virtual machine image.
on: [push]
name: Create Custom VM Image
jobs:
build-image:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Login via Az module
uses: azure/login@v1
with:
creds: ${{secrets.AZURE_CREDENTIALS}}
- name: Setup Java 1.8.x
uses: actions/setup-java@v1
with:
java-version: '1.8.x'
- name: Build Java
run: mvn --batch-mode --update-snapshots verify
- run: mkdir staging && cp target/*.jar staging
- uses: actions/upload-artifact@v2
with:
name: Package
path: staging
on: [push]
name: Create Custom VM Image
jobs:
build-image:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Login via Az module
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Setup Java 1.8.x
uses: actions/setup-java@v1
with:
java-version: '1.8.x'
- name: Build Java
run: mvn --batch-mode --update-snapshots verify
- run: mkdir staging && cp target/*.jar staging
- uses: actions/upload-artifact@v2
with:
name: Package
path: staging
Use the Build Azure Virtual Machine Image action to create a custom virtual machine image.
Replace the placeholders for {subscriptionID}
, {rgName}
and {Identity}
with your subscription ID, resource group name, and managed identity name. Replace the values of {galleryName}
and {imageName}
with your image gallery name and your image name.
- name: Create App Baked Image
id: imageBuilder
uses: azure/build-vm-image@v0
with:
location: 'eastus2'
resource-group-name: '{rgName}'
managed-identity: '{Identity}' # Managed identity
source-os-type: 'windows'
source-image-type: 'platformImage'
source-image: MicrosoftWindowsServer:WindowsServer:2019-Datacenter:latest #unique identifier of source image
dist-type: 'SharedImageGallery'
dist-resource-id: '/subscriptions/{subscriptionID}/resourceGroups/{rgName}/providers/Microsoft.Compute/galleries/{galleryName}/images/{imageName}/versions/0.1.${{ GITHUB.RUN_ID }}' #Replace with the resource id of your shared image gallery's image definition
dist-location: 'eastus2'
Input | Required | Description |
---|---|---|
resource-group-name |
Yes | The resource group used for storage and saving artifacts during the build process. |
image-builder-template-name |
No | The name of the image builder template resource used. |
location |
Yes | The location where Azure Image Builder will run. See supported locations. |
build-timeout-in-minutes |
No | Time after which the build is canceled. Defaults to 240. |
vm-size |
Optional | By default, Standard_D1_v2 will be used. See virtual machine sizes. |
managed-identity |
Yes | The user-managed identity you created earlier. Use the full identifier if your identity is in a different resources group. Use the name if it is in the same resource group. |
source-os |
Yes | The OS type of the base image (Linux or Windows) |
source-image-type |
Yes | The base image type that will be used for creating the custom image. |
source-image |
Yes | The resource identifier for base image. A source image should be present in the same Azure region set in the input value of location. |
customizer-source |
No | The directory where you can keep all the artifacts that need to be added to the base image for customization. By default, the value is ${{ GITHUB.WORKSPACE }}/workflow-artifacts. |
customizer-destination |
No | This is the directory in the customized image where artifacts are copied to. |
customizer-windows-update |
No | For Windows only. Boolean value. If true , the image builder will run Windows update at the end of the customizations. |
dist-location |
No | For SharedImageGallery, this is the dist-type . |
dist-image-tags |
No | These are user-defined tags that are added to the custom image created (example: version:beta ). |
As a last step, create a virtual machine from your image.
-
Replace the placeholders for
{rgName}
with your resource group name. -
Add a GitHub secret with the virtual machine password (
VM_PWD
). Be sure to write down the password because you will not be able to see it again. The username ismyuser
.
- name: CREATE VM
uses: azure/CLI@v1
with:
azcliversion: 2.0.72
inlineScript: |
az vm create --resource-group ghactions-vMimage --name "app-vm-${{ GITHUB.RUN_NUMBER }}" --admin-username myuser --admin-password "${{ secrets.VM_PWD }}" --location eastus2 \
--image "${{ steps.imageBuilder.outputs.custom-image-uri }}"
on: [push]
name: Create Custom VM Image
jobs:
build-image:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Login via Az module
uses: azure/login@v1
with:
creds: ${{secrets.AZURE_CREDENTIALS}}
- name: Setup Java 1.8.x
uses: actions/setup-java@v1
with:
java-version: '1.8.x'
- name: Build Java
run: mvn --batch-mode --update-snapshots verify
- run: mkdir staging && cp target/*.jar staging
- uses: actions/upload-artifact@v2
with:
name: Package
path: staging
- name: Create App Baked Image
id: imageBuilder
uses: azure/build-vm-image@v0
with:
location: 'eastus2'
resource-group-name: '{rgName}'
managed-identity: '{Identity}' # Managed identity
source-os-type: 'windows'
source-image-type: 'platformImage'
source-image: MicrosoftWindowsServer:WindowsServer:2019-Datacenter:latest #unique identifier of source image
dist-type: 'SharedImageGallery'
dist-resource-id: '/subscriptions/{subscriptionID}/resourceGroups/{rgName}/providers/Microsoft.Compute/galleries/{galleryName}/images/{imageName}/versions/0.1.${{ GITHUB.RUN_ID }}' #Replace with the resource id of your shared image gallery's image definition
dist-location: 'eastus2'
- name: CREATE VM
uses: azure/CLI@v1
with:
azcliversion: 2.0.72
inlineScript: |
az vm create --resource-group ghactions-vMimage --name "app-vm-${{ GITHUB.RUN_NUMBER }}" --admin-username myuser --admin-password "${{ secrets.VM_PWD }}" --location eastus2 \
--image "${{ steps.imageBuilder.outputs.custom-image-uri }}"
on: [push]
name: Create Custom VM Image
jobs:
build-image:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Login via Az module
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Setup Java 1.8.x
uses: actions/setup-java@v1
with:
java-version: '1.8.x'
- name: Build Java
run: mvn --batch-mode --update-snapshots verify
- run: mkdir staging && cp target/*.jar staging
- uses: actions/upload-artifact@v2
with:
name: Package
path: staging
- name: Create App Baked Image
id: imageBuilder
uses: azure/build-vm-image@v0
with:
location: 'eastus2'
resource-group-name: '{rgName}'
managed-identity: '{Identity}' # Managed identity
source-os-type: 'windows'
source-image-type: 'platformImage'
source-image: MicrosoftWindowsServer:WindowsServer:2019-Datacenter:latest #unique identifier of source image
dist-type: 'SharedImageGallery'
dist-resource-id: '/subscriptions/{subscriptionID}/resourceGroups/{rgName}/providers/Microsoft.Compute/galleries/{galleryName}/images/{imageName}/versions/0.1.${{ GITHUB.RUN_ID }}' #Replace with the resource id of your shared image gallery's image definition
dist-location: 'eastus2'
- name: CREATE VM
uses: azure/CLI@v1
with:
azcliversion: 2.0.72
inlineScript: |
az vm create --resource-group ghactions-vMimage --name "app-vm-${{ GITHUB.RUN_NUMBER }}" --admin-username myuser --admin-password "${{ secrets.VM_PWD }}" --location eastus2 \
--image "${{ steps.imageBuilder.outputs.custom-image-uri }}"
- Learn how to deploy to Azure.