title | description | author | ms.author | ms.topic | ms.service | ms.subservice | 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-virtual-machines |
imaging |
02/12/2025 |
github-actions-azure, devx-track-azurecli, mode-portal, devx-track-extended-java, linux-related-content |
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.
- An Azure Compute Gallery with an image.
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.
-
In Azure portal, open your Azure Compute Gallery and go to Access control (IAM).
-
Select Add role assignment and assign the Image Creation Role to your user-managed identity.
[!INCLUDE include]
[!INCLUDE include]
Use your GitHub secret with the Azure Login action to authenticate to Azure.
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@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
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@v2
with:
creds: '${{ secrets.AZURE_CREDENTIALS }}'
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
strategy:
matrix:
java: [ '17' ]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Login via Az module
uses: azure/login@v2
with:
creds: ${{secrets.AZURE_CREDENTIALS}}
- name: Set up JDK ${{matrix.java}}
uses: actions/setup-java@v2
with:
java-version: ${{matrix.java}}
distribution: 'adopt'
cache: maven
- name: Build with Maven Wrapper
run: ./mvnw -B package
- 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
strategy:
matrix:
java: [ '17' ]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Login via Az module
uses: azure/login@v2
with:
creds: ${{secrets.AZURE_CREDENTIALS}}
- name: Set up JDK ${{matrix.java}}
uses: actions/setup-java@v2
with:
java-version: ${{matrix.java}}
distribution: 'adopt'
cache: maven
- name: Build with Maven Wrapper
run: ./mvnw -B package
- 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.
Note
If the Create App Baked Image action fails with a permission error, verify that you have assigned the Image Creation Role to your user-managed identity.
- 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@v2
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 }}"
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@v2
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 }}"
- Learn how to deploy to Azure.