diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index dd858658cf..3c8807cd36 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -2,8 +2,9 @@
# [Choice] Python version: 3.11 3.12, 3.13
ARG VARIANT="3.12"
+ARG DEVCONTAINER_BASE_IMAGE=mcr.microsoft.com/vscode/devcontainers/python
ARG TARGETPLATFORM="linux/amd64"
-FROM --platform="${TARGETPLATFORM}" mcr.microsoft.com/vscode/devcontainers/python:dev-${VARIANT}-bookworm
+FROM --platform="${TARGETPLATFORM}" ${DEVCONTAINER_BASE_IMAGE}:dev-${VARIANT}-bookworm
# This will be set to true when running in VSCode
ARG INTERACTIVE="false"
diff --git a/.github/workflows/build_docker_images.yml b/.github/workflows/build_docker_images.yml
index 6589f0b9fe..1ea63e3098 100644
--- a/.github/workflows/build_docker_images.yml
+++ b/.github/workflows/build_docker_images.yml
@@ -21,9 +21,16 @@ jobs:
docker_build:
name: Build images
runs-on: ubuntu-latest
+ env:
+ PYTHON_TAG: 3.12-slim-bookworm
+ AIRLOCK_PYTHON_TAG: 4-python3.12
+ MAVEN_TAG: 3.9-eclipse-temurin-17-alpine
+ GUACD_TAG: 1.6.0
+ GITEA_TAG: 1.15
permissions:
contents: read
actions: write # For uploading artifacts
+ id-token: write # For Azure OIDC login when using ACR mirrors
pull-requests: read # For path filter
steps:
- name: Upload Event File
@@ -38,6 +45,68 @@ jobs:
with:
persist-credentials: false
+ - name: Determine ACR mirror availability
+ id: acr_mirror
+ env:
+ ACR_BASE_IMAGE_PREFIX: ${{ vars.ACR_BASE_IMAGE_PREFIX }}
+ ACR_NAME: ${{ secrets.ACR_NAME }}
+ AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
+ AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
+ AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
+ run: |
+ use_acr_mirrors=false
+
+ if [[ -n "${ACR_BASE_IMAGE_PREFIX}" &&
+ -n "${ACR_NAME}" &&
+ -n "${AZURE_CLIENT_ID}" &&
+ -n "${AZURE_TENANT_ID}" &&
+ -n "${AZURE_SUBSCRIPTION_ID}" ]]; then
+ use_acr_mirrors=true
+ fi
+
+ echo "use_acr_mirrors=${use_acr_mirrors}" >> "${GITHUB_OUTPUT}"
+
+ - name: Azure Login for ACR mirror
+ if: ${{ steps.acr_mirror.outputs.use_acr_mirrors == 'true' }}
+ uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0
+ with:
+ client-id: ${{ secrets.AZURE_CLIENT_ID }}
+ tenant-id: ${{ secrets.AZURE_TENANT_ID }}
+ subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
+ environment: ${{ (vars.AZURE_ENVIRONMENT != '' && vars.AZURE_ENVIRONMENT) || 'AzureCloud' }}
+
+ - name: Login to ACR mirror
+ if: ${{ steps.acr_mirror.outputs.use_acr_mirrors == 'true' }}
+ run: az acr login --name "${{ secrets.ACR_NAME }}"
+
+ - name: Select Docker base image sources
+ env:
+ ACR_BASE_IMAGE_PREFIX: ${{ vars.ACR_BASE_IMAGE_PREFIX }}
+ run: |
+ prefix="${ACR_BASE_IMAGE_PREFIX}"
+
+ python_base_image="python"
+ airlock_python_base_image="mcr.microsoft.com/azure-functions/python"
+ maven_base_image="maven"
+ guacd_base_image="guacamole/guacd"
+ gitea_base_image_repo="gitea/gitea"
+
+ if [[ "${{ steps.acr_mirror.outputs.use_acr_mirrors }}" == 'true' ]]; then
+ python_base_image="${prefix}/mirror/library/python"
+ airlock_python_base_image="${prefix}/mirror/mcr/azure-functions/python"
+ maven_base_image="${prefix}/mirror/library/maven"
+ guacd_base_image="${prefix}/mirror/guacamole/guacd"
+ gitea_base_image_repo="${prefix}/mirror/gitea/gitea"
+ fi
+
+ {
+ echo "PYTHON_BASE_IMAGE=${python_base_image}"
+ echo "AIRLOCK_PYTHON_BASE_IMAGE=${airlock_python_base_image}"
+ echo "MAVEN_BASE_IMAGE=${maven_base_image}"
+ echo "GUACD_BASE_IMAGE=${guacd_base_image}"
+ echo "GITEA_BASE_IMAGE_REPO=${gitea_base_image_repo}"
+ } >> "${GITHUB_ENV}"
+
- name: Filter changes
uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: filter
@@ -137,6 +206,9 @@ jobs:
with:
context: ./api_app/
file: ./api_app/Dockerfile
+ build-args: |
+ PYTHON_BASE_IMAGE=${{ env.PYTHON_BASE_IMAGE }}
+ PYTHON_TAG=${{ env.PYTHON_TAG }}
outputs: type=local,dest=test-results
target: test-results
cache-from: type=gha
@@ -157,6 +229,9 @@ jobs:
with:
context: ./api_app/
file: ./api_app/Dockerfile
+ build-args: |
+ PYTHON_BASE_IMAGE=${{ env.PYTHON_BASE_IMAGE }}
+ PYTHON_TAG=${{ env.PYTHON_TAG }}
cache-from: type=gha
cache-to: type=gha,mode=max
@@ -168,6 +243,9 @@ jobs:
with:
context: ./resource_processor
file: ./resource_processor/vmss_porter/Dockerfile
+ build-args: |
+ PYTHON_BASE_IMAGE=${{ env.PYTHON_BASE_IMAGE }}
+ PYTHON_TAG=${{ env.PYTHON_TAG }}
cache-from: type=gha
cache-to: type=gha,mode=max
@@ -179,6 +257,11 @@ jobs:
with:
context: ./templates/workspace_services/guacamole/guacamole-server
file: ./templates/workspace_services/guacamole/guacamole-server/docker/Dockerfile
+ build-args: |
+ MAVEN_BASE_IMAGE=${{ env.MAVEN_BASE_IMAGE }}
+ MAVEN_TAG=${{ env.MAVEN_TAG }}
+ GUACD_BASE_IMAGE=${{ env.GUACD_BASE_IMAGE }}
+ GUACD_TAG=${{ env.GUACD_TAG }}
outputs: type=local,dest=test-results
target: test-results
cache-from: type=gha
@@ -199,6 +282,11 @@ jobs:
with:
context: ./templates/workspace_services/guacamole/guacamole-server
file: ./templates/workspace_services/guacamole/guacamole-server/docker/Dockerfile
+ build-args: |
+ MAVEN_BASE_IMAGE=${{ env.MAVEN_BASE_IMAGE }}
+ MAVEN_TAG=${{ env.MAVEN_TAG }}
+ GUACD_BASE_IMAGE=${{ env.GUACD_BASE_IMAGE }}
+ GUACD_TAG=${{ env.GUACD_TAG }}
cache-from: type=gha
cache-to: type=gha,mode=max
@@ -210,6 +298,9 @@ jobs:
with:
context: ./templates/shared_services/gitea/docker
file: ./templates/shared_services/gitea/docker/Dockerfile
+ build-args: |
+ GITEA_BASE_IMAGE_REPO=${{ env.GITEA_BASE_IMAGE_REPO }}
+ GITEA_TAG=${{ env.GITEA_TAG }}
cache-from: type=gha
cache-to: type=gha,mode=max
@@ -227,6 +318,9 @@ jobs:
with:
context: ./airlock_processor/
file: ./airlock_processor/Dockerfile
+ build-args: |
+ PYTHON_BASE_IMAGE=${{ env.AIRLOCK_PYTHON_BASE_IMAGE }}
+ PYTHON_TAG=${{ env.AIRLOCK_PYTHON_TAG }}
outputs: type=local,dest=test-results
target: test-results
cache-from: type=gha
@@ -247,6 +341,9 @@ jobs:
with:
context: ./airlock_processor/
file: ./airlock_processor/Dockerfile
+ build-args: |
+ PYTHON_BASE_IMAGE=${{ env.AIRLOCK_PYTHON_BASE_IMAGE }}
+ PYTHON_TAG=${{ env.AIRLOCK_PYTHON_TAG }}
cache-from: type=gha
cache-to: type=gha,mode=max
diff --git a/.github/workflows/sync_acr_base_images.yml b/.github/workflows/sync_acr_base_images.yml
new file mode 100644
index 0000000000..3efd1a982a
--- /dev/null
+++ b/.github/workflows/sync_acr_base_images.yml
@@ -0,0 +1,161 @@
+---
+name: Sync ACR Base Images
+
+on: # yamllint disable-line rule:truthy
+ schedule:
+ - cron: "23 2 * * *"
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ id-token: write
+
+jobs:
+ sync:
+ name: Import newer upstream base images into ACR
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
+ with:
+ persist-credentials: false
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd
+
+ - name: Validate required secrets
+ run: |
+ if [ "${{ secrets.ACR_NAME }}" = '' ]; then
+ echo "Missing secret: ACR_NAME" && exit 1
+ fi
+ if [ "${{ secrets.AZURE_CLIENT_ID }}" = '' ]; then
+ echo "Missing secret: AZURE_CLIENT_ID" && exit 1
+ fi
+ if [ "${{ secrets.AZURE_TENANT_ID }}" = '' ]; then
+ echo "Missing secret: AZURE_TENANT_ID" && exit 1
+ fi
+ if [ "${{ secrets.AZURE_SUBSCRIPTION_ID }}" = '' ]; then
+ echo "Missing secret: AZURE_SUBSCRIPTION_ID" && exit 1
+ fi
+
+ - name: Azure Login
+ uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0
+ with:
+ client-id: ${{ secrets.AZURE_CLIENT_ID }}
+ tenant-id: ${{ secrets.AZURE_TENANT_ID }}
+ subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
+ environment: ${{ (vars.AZURE_ENVIRONMENT != '' && vars.AZURE_ENVIRONMENT) || 'AzureCloud' }}
+
+ - name: Login to ACR
+ run: az acr login --name "${{ secrets.ACR_NAME }}"
+
+ - name: Sync image mirrors
+ env:
+ ACR_NAME: ${{ secrets.ACR_NAME }}
+ run: |
+ set -euo pipefail
+
+ acr_domain_suffix=$(az cloud show --query suffixes.acrLoginServerEndpoint --output tsv)
+ ACR_LOGIN_SERVER="${ACR_NAME}${acr_domain_suffix}"
+
+ # source_repo|target_repo|tag
+ IMAGES=(
+ "docker.io/library/python|mirror/library/python|3.12-slim-bookworm"
+ "docker.io/library/maven|mirror/library/maven|3.9-eclipse-temurin-17-alpine"
+ "docker.io/guacamole/guacd|mirror/guacamole/guacd|1.6.0"
+ "docker.io/gitea/gitea|mirror/gitea/gitea|1.15"
+ "docker.io/gitea/gitea|mirror/gitea/gitea|1.17.3"
+ "mcr.microsoft.com/azure-functions/python|mirror/mcr/azure-functions/python|4-python3.12"
+ "docker.io/library/debian|mirror/library/debian|bookworm-slim"
+ "docker.io/library/debian|mirror/library/debian|bullseye-slim"
+ )
+
+ sync_image() {
+ local source_repo="$1"
+ local target_repo="$2"
+ local tag="$3"
+ local source_ref="${source_repo}:${tag}"
+ local target_ref="${ACR_LOGIN_SERVER}/${target_repo}:${tag}"
+ local digest_format='{{json .Manifest.Digest}}'
+
+ local source_digest
+ local target_digest
+
+ if ! source_digest=$(docker buildx imagetools inspect "${source_ref}" \
+ --format "${digest_format}" | tr -d '"'); then
+ echo "::error::Failed to inspect source image ${source_ref}. Aborting to avoid unintended forced import."
+ return 1
+ fi
+
+ if ! target_digest=$(docker buildx imagetools inspect "${target_ref}" \
+ --format "${digest_format}" | tr -d '"'); then
+ echo "::warning::Failed to inspect target image ${target_ref}. Proceeding with import."
+ target_digest=""
+ fi
+
+ if [[ -z "${source_digest}" ]]; then
+ echo "::error::Source digest for ${source_ref} is empty. Aborting to avoid unintended forced import."
+ return 1
+ fi
+
+ if [[ -z "${target_digest}" ]]; then
+ echo "::warning::Target digest for ${target_ref} is empty. Import will proceed."
+ fi
+
+ if [[ -n "${source_digest}" && -n "${target_digest}" && "${source_digest}" == "${target_digest}" ]]; then
+ echo "Up-to-date: ${target_repo}:${tag} (${source_digest})"
+ return 0
+ fi
+
+ echo "Importing ${source_ref} -> ${target_repo}:${tag}"
+ az acr import \
+ --name "${ACR_NAME}" \
+ --source "${source_ref}" \
+ --image "${target_repo}:${tag}" \
+ --force
+ }
+
+ for image in "${IMAGES[@]}"; do
+ IFS='|' read -r source_repo target_repo tag <<< "${image}"
+ sync_image "${source_repo}" "${target_repo}" "${tag}"
+ done
+
+ - name: Publish sync summary
+ env:
+ ACR_NAME: ${{ secrets.ACR_NAME }}
+ CURRENT_ACR_BASE_IMAGE_PREFIX: ${{ vars.ACR_BASE_IMAGE_PREFIX }}
+ run: |
+ set -euo pipefail
+
+ acr_domain_suffix=$(az cloud show --query suffixes.acrLoginServerEndpoint --output tsv)
+ expected_prefix="${ACR_NAME}${acr_domain_suffix}"
+
+ {
+ echo "## ACR base image sync summary"
+ echo
+ echo "- Expected ACR_BASE_IMAGE_PREFIX: ${expected_prefix}"
+
+ if [[ -n "${CURRENT_ACR_BASE_IMAGE_PREFIX}" ]]; then
+ echo "- Current ACR_BASE_IMAGE_PREFIX: ${CURRENT_ACR_BASE_IMAGE_PREFIX}"
+ else
+ echo "- Current ACR_BASE_IMAGE_PREFIX: (not set)"
+ fi
+
+ echo
+ if [[ -z "${CURRENT_ACR_BASE_IMAGE_PREFIX}" ]]; then
+ echo "Repository variable ACR_BASE_IMAGE_PREFIX is not set."
+ echo "Please set it to ${expected_prefix}."
+ echo "See: Repository Settings > Secrets and variables > Actions > Variables"
+ elif [[ "${CURRENT_ACR_BASE_IMAGE_PREFIX}" != "${expected_prefix}" ]]; then
+ echo "Repository variable ACR_BASE_IMAGE_PREFIX does not match the expected value."
+ echo "Please update it to ${expected_prefix}."
+ else
+ echo "Repository variable ACR_BASE_IMAGE_PREFIX is correctly configured."
+ fi
+ } >> "${GITHUB_STEP_SUMMARY}"
+
+ if [[ -z "${CURRENT_ACR_BASE_IMAGE_PREFIX}" ]]; then
+ echo "::warning::Repository variable ACR_BASE_IMAGE_PREFIX is not set. Expected: ${expected_prefix}"
+ elif [[ "${CURRENT_ACR_BASE_IMAGE_PREFIX}" != "${expected_prefix}" ]]; then
+ echo "::warning::Repository variable ACR_BASE_IMAGE_PREFIX mismatch. Current: ${CURRENT_ACR_BASE_IMAGE_PREFIX}, Expected: ${expected_prefix}"
+ fi
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d14373da30..f8fc046dbf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,12 +9,14 @@ ENHANCEMENTS:
* Add Windows Server 2025 image support to Guacamole. ([#4890](https://github.com/microsoft/AzureTRE/issues/4890))
* Add support for setting resource processor VMSS SKU via environment variables ([#4936](https://github.com/microsoft/AzureTRE/issues/4936))
* Exclude recovery service vaults from e2e tests ([#4920](https://github.com/microsoft/AzureTRE/issues/4920))
+* Add ACR base-image mirroring workflow and configurable Docker base-image arguments for CI builds ([#4952](https://github.com/microsoft/AzureTRE/pull/4952))
* Update API, CLI, and UI dependencies to address high-severity Dependabot alerts, including `PyJWT`, `Vite`, `lodash`, `fast-uri`, `flatted`, `immutable`, and `minimatch`.
* Update dependencies to address Dependabot security alerts: `aiohttp` to 3.14.1, `Pygments` to 2.20.0, `esbuild`, `ws`, `js-yaml`, `@babel/core`, `flatted` (via vitest upgrade), and `react-router-dom`. ([#4950](https://github.com/microsoft/AzureTRE/issues/4950))
* Added support for formatting UI code via `pre-commit` and fixed existing formatting issues. ([#4955](https://github.com/microsoft/AzureTRE/issues/4955))
* Updated the version of `super-linter` used in the `build_validation_develop` workflow ([#4957](https://github.com/microsoft/AzureTRE/issues/4957))
BUG FIXES:
+* Fix Docker image workflows to authenticate before using private ACR mirrors and skip redundant runtime-image imports when the target tag is already present. ([#4952](https://github.com/microsoft/AzureTRE/pull/4952))
* Fix UI TypeScript deprecation warning by updating `moduleResolution` to `bundler` in `tsconfig.json`. ([#4968](https://github.com/microsoft/AzureTRE/issues/4968))
* Fix API timeout and name collision failures on workspace creation by checking storage account name availability and improved logging. ([#4946](https://github.com/microsoft/AzureTRE/pull/4946))
* Fix error handling in airlock processor ([#4929](https://github.com/microsoft/AzureTRE/pull/4929))
diff --git a/airlock_processor/Dockerfile b/airlock_processor/Dockerfile
index 68d1174ffc..faa443e4ec 100644
--- a/airlock_processor/Dockerfile
+++ b/airlock_processor/Dockerfile
@@ -1,6 +1,8 @@
# To enable ssh & remote debugging on app service change the base image to the one below
# FROM mcr.microsoft.com/azure-functions/python:4-python3.8-appservice as base
-FROM mcr.microsoft.com/azure-functions/python:4-python3.12 AS base
+ARG PYTHON_TAG=4-python3.12
+ARG PYTHON_BASE_IMAGE=mcr.microsoft.com/azure-functions/python
+FROM ${PYTHON_BASE_IMAGE}:${PYTHON_TAG} AS base
COPY requirements.txt /
RUN pip install --no-cache-dir -r /requirements.txt
diff --git a/airlock_processor/_version.py b/airlock_processor/_version.py
index 1d16920cdb..cb4382b891 100644
--- a/airlock_processor/_version.py
+++ b/airlock_processor/_version.py
@@ -1 +1 @@
-__version__ = "0.8.11"
+__version__ = "0.8.12"
diff --git a/api_app/Dockerfile b/api_app/Dockerfile
index fc8b93c676..8ee5f281f5 100644
--- a/api_app/Dockerfile
+++ b/api_app/Dockerfile
@@ -1,4 +1,6 @@
-FROM python:3.12-slim-bookworm AS base
+ARG PYTHON_TAG=3.12-slim-bookworm
+ARG PYTHON_BASE_IMAGE=python
+FROM ${PYTHON_BASE_IMAGE}:${PYTHON_TAG} AS base
COPY requirements.txt /.
RUN pip3 install --no-cache-dir -r requirements.txt
diff --git a/api_app/_version.py b/api_app/_version.py
index 605b3cd20e..5a30a412a8 100644
--- a/api_app/_version.py
+++ b/api_app/_version.py
@@ -1 +1 @@
-__version__ = "0.25.29"
+__version__ = "0.25.30"
diff --git a/devops/scripts/bundle_runtime_image_build.sh b/devops/scripts/bundle_runtime_image_build.sh
index f527245b37..5dee9698b8 100755
--- a/devops/scripts/bundle_runtime_image_build.sh
+++ b/devops/scripts/bundle_runtime_image_build.sh
@@ -10,11 +10,21 @@ if [ "$(yq eval ".custom.runtime_image.import" porter.yaml)" != "null" ]; then
image_name=$(yq eval ".custom.runtime_image.name" porter.yaml)
source_image=$(yq eval ".custom.runtime_image.import.source" porter.yaml)
version=$(yq eval ".custom.runtime_image.import.tag" porter.yaml)
+ target_image="${image_name}:${version}"
- echo "Importing ${source_image}:${version} to ACR as ${image_name}:${version}..."
+ existing_digest=$(az acr manifest show-metadata -r "${ACR_NAME}" -n "${target_image}" \
+ --query digest \
+ --output tsv 2>/dev/null || true)
+
+ if [ -n "${existing_digest}" ] && [ "${existing_digest}" != "null" ]; then
+ echo "Image ${target_image} already exists in ACR with digest ${existing_digest}. Skipping import."
+ exit 0
+ fi
+
+ echo "Importing ${source_image}:${version} to ACR as ${target_image}..."
az acr import --name "${ACR_NAME}" \
--source "${source_image}:${version}" \
- --image "${image_name}:${version}" \
+ --image "${target_image}" \
--force
echo "Image imported successfully"
exit 0
@@ -58,4 +68,3 @@ fi
${DOCKER_BUILD_COMMAND} --build-arg BUILDKIT_INLINE_CACHE=1 \
-t "${FULL_IMAGE_NAME_PREFIX}/${image_name}:${version}" \
"${docker_cache[@]}" -f "${docker_file}" "${docker_context}"
-
diff --git a/docs/tre-admins/setup-instructions/cicd-pre-deployment-steps.md b/docs/tre-admins/setup-instructions/cicd-pre-deployment-steps.md
index 68e8aa7ad7..0c8ad5b55a 100644
--- a/docs/tre-admins/setup-instructions/cicd-pre-deployment-steps.md
+++ b/docs/tre-admins/setup-instructions/cicd-pre-deployment-steps.md
@@ -95,6 +95,7 @@ Configure the following **variables** in your github environment:
| ----------- | ----------- |
| `AZURE_ENVIRONMENT` | Optional. The Azure cloud environment. Default is `AzureCloud`. Use `AzureUSGovernment` for US Government cloud |
| `LOCATION` | The Azure location (region) for all resources. E.g. `westeurope` |
+| `ACR_BASE_IMAGE_PREFIX` | Optional. ACR login server prefix used by image build workflows to pull mirrored base images instead of directly pulling from Docker Hub. Example value: `myacr.azurecr.io`. |
| `TERRAFORM_STATE_CONTAINER_NAME` | Optional. The name of the blob container to hold the Terraform state. Default value is `tfstate`. |
| `CORE_ADDRESS_SPACE` | Optional. The address space for the Azure TRE core virtual network. Default value is `10.0.0.0/22`. |
| `TRE_ADDRESS_SPACE` | Optional. The address space for the whole TRE environment virtual network where workspaces networks will be created (can include the core network as well). Default value is `10.0.0.0/16` |
@@ -108,6 +109,20 @@ Configure the following **variables** in your github environment:
| `CUSTOM_DOMAIN` | Optional. Custom domain name to access the Azure TRE portal. See [Custom domain name](../custom-domain.md). |
| `ENABLE_CMK_ENCRYPTION` | Optional. Default is `false`, if set to `true` customer-managed key encryption will be enabled for all supported resources. |
+### Optional: Mirror Docker Hub base images into ACR for CI
+
+To reduce Docker Hub rate-limit exposure in CI, Azure TRE includes a dedicated workflow to mirror selected upstream base images into your ACR and use those mirrors during image builds.
+
+1. Set repository/environment variable `ACR_BASE_IMAGE_PREFIX` to your ACR login server (for example, `myacr.azurecr.io`).
+2. Ensure the following secrets are configured for the mirror workflow:
+- `ACR_NAME`
+- `AZURE_CLIENT_ID`
+- `AZURE_TENANT_ID`
+- `AZURE_SUBSCRIPTION_ID`
+3. Run `/.github/workflows/sync_acr_base_images.yml` manually once to seed mirrored images, then allow the scheduled run to keep them up to date.
+
+The Docker image build workflow (`/.github/workflows/build_docker_images.yml`) automatically uses mirrored base images when `ACR_BASE_IMAGE_PREFIX` is set, and falls back to upstream image sources when it is not set.
+
### Configure Authentication Secrets
In a previous [Setup Auth configuration](./setup-auth-entities.md) step authentication configuration was added in `config.yaml` file. Go to this file and add those env vars to your github environment:
diff --git a/docs/tre-admins/setup-instructions/workflows.md b/docs/tre-admins/setup-instructions/workflows.md
index 9176768f9c..8c32e99de5 100644
--- a/docs/tre-admins/setup-instructions/workflows.md
+++ b/docs/tre-admins/setup-instructions/workflows.md
@@ -12,12 +12,12 @@ Before you can run the `deploy_tre.yml` workflow there are some one-time configu
In some of the steps below, you are asked to configure repository secrets. Follow the [GitHub guide](https://docs.github.com/en/actions/security-guides/encrypted-secrets) on creating repository secrets if you are unfamiliar with this step.
1. Create a service principal for the subscription so that the workflow can provision Azure resources.
-1. Decide on a TRE ID and the location for the Azure resources
-1. Create app registrations for API authentication
-1. Create app registrations and a user for the E2E tests
-1. Create a workspace app registration for setting up workspaces (for the E2E tests)
-1. Configure repository secrets
-1. Deploy the TRE using the workflow
+2. Decide on a TRE ID and the location for the Azure resources
+3. Create app registrations for API authentication
+4. Create app registrations and a user for the E2E tests
+5. Create a workspace app registration for setting up workspaces (for the E2E tests)
+6. Configure repository secrets
+7. Deploy the TRE using the workflow
### Create a service principal and configure OIDC authentication
@@ -33,7 +33,7 @@ Before you can run the `deploy_tre.yml` workflow there are some one-time configu
See [Sign in with Azure CLI](https://docs.microsoft.com/cli/azure/authenticate-azure-cli) for more details.
-1. Create a service principal
+2. Create a service principal
A service principal needs to be created to authorize CI/CD workflows to provision resources for the TRE workspaces and workspace services.
@@ -46,7 +46,7 @@ Before you can run the `deploy_tre.yml` workflow there are some one-time configu
!!! caution
Save the output (especially the `appId` and `tenant`) - you will need it for the next steps
-1. Configure federated identity credentials for GitHub Actions OIDC
+3. Configure federated identity credentials for GitHub Actions OIDC
Configure the service principal to trust GitHub Actions OIDC tokens from your repository:
@@ -72,7 +72,7 @@ Before you can run the `deploy_tre.yml` workflow there are some one-time configu
See [Configure a federated identity credential on an app](https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust?pivots=identity-wif-apps-methods-azcli) for more details.
-1. Configure repository secrets for OIDC authentication
+4. Configure repository secrets for OIDC authentication
Configure the following **secrets** (not variables) in your repository or environment:
@@ -147,6 +147,7 @@ Configure variables used in the deployment workflow:
|
Variable name
| Description |
| ----------- | ----------- |
| `LOCATION` | The Azure location (region) for all resources. E.g. `westeurope` |
+| `ACR_BASE_IMAGE_PREFIX` | Optional. ACR login server prefix used by image build workflows to pull mirrored base images instead of directly pulling from Docker Hub. Example value: `myacr.azurecr.io`. |
| `TERRAFORM_STATE_CONTAINER_NAME` | Optional. The name of the blob container to hold the Terraform state. Default value is `tfstate`. |
| `CORE_ADDRESS_SPACE` | Optional. The address space for the Azure TRE core virtual network. Default value is `10.0.0.0/22`. |
| `TRE_ADDRESS_SPACE` | Optional. The address space for the whole TRE environment virtual network where workspaces networks will be created (can include the core network as well). Default value is `10.0.0.0/16` |
@@ -159,6 +160,20 @@ Configure variables used in the deployment workflow:
| `APP_GATEWAY_SKU` | Optional. The SKU of the Application Gateway. Default value is `Standard_v2`. Allowed values [`Standard_v2`, `WAF_v2`] |
| `ENABLE_CMK_ENCRYPTION` | Optional. Default is `false`, if set to `true` customer-managed key encryption will be enabled for all supported resources. |
+### Optional: Mirror Docker Hub base images into ACR for CI
+
+To reduce Docker Hub rate-limit exposure in CI, Azure TRE includes a dedicated workflow to mirror selected upstream base images into your ACR and use those mirrors during image builds.
+
+1. Set repository/environment variable `ACR_BASE_IMAGE_PREFIX` to your ACR login server (for example, `myacr.azurecr.io`).
+2. Ensure the following secrets are configured for the mirror workflow:
+- `ACR_NAME`
+- `AZURE_CLIENT_ID`
+- `AZURE_TENANT_ID`
+- `AZURE_SUBSCRIPTION_ID`
+3. Run `/.github/workflows/sync_acr_base_images.yml` manually once to seed mirrored images, then allow the scheduled run to keep them up to date.
+
+The Docker image build workflow (`/.github/workflows/build_docker_images.yml`) automatically uses mirrored base images when `ACR_BASE_IMAGE_PREFIX` is set, and falls back to upstream image sources when it is not set.
+
### Deploy the TRE using the workflow
With all the repository secrets set, you can trigger a workflow run by pushing to develop/main of your fork, or by dispatching the workflow manually.
diff --git a/resource_processor/_version.py b/resource_processor/_version.py
index 7c37594d81..e318db3960 100644
--- a/resource_processor/_version.py
+++ b/resource_processor/_version.py
@@ -1 +1 @@
-__version__ = "0.13.5"
+__version__ = "0.13.6"
diff --git a/resource_processor/vmss_porter/Dockerfile b/resource_processor/vmss_porter/Dockerfile
index a49d19aade..1a17e09820 100644
--- a/resource_processor/vmss_porter/Dockerfile
+++ b/resource_processor/vmss_porter/Dockerfile
@@ -1,5 +1,7 @@
# syntax=docker/dockerfile:1
-FROM python:3.12-slim-bookworm
+ARG PYTHON_TAG=3.12-slim-bookworm
+ARG PYTHON_BASE_IMAGE=python
+FROM ${PYTHON_BASE_IMAGE}:${PYTHON_TAG}
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
diff --git a/templates/shared_services/admin-vm/Dockerfile.tmpl b/templates/shared_services/admin-vm/Dockerfile.tmpl
index a8c0026343..c21796d5a6 100644
--- a/templates/shared_services/admin-vm/Dockerfile.tmpl
+++ b/templates/shared_services/admin-vm/Dockerfile.tmpl
@@ -1,5 +1,7 @@
# syntax=docker/dockerfile-upstream:1.4.0
-FROM --platform=linux/amd64 debian:bookworm-slim
+ARG DEBIAN_TAG=bookworm-slim
+ARG DEBIAN_BASE_IMAGE=debian
+FROM --platform=linux/amd64 ${DEBIAN_BASE_IMAGE}:${DEBIAN_TAG}
# PORTER_INIT
diff --git a/templates/shared_services/admin-vm/porter.yaml b/templates/shared_services/admin-vm/porter.yaml
index 9cbdf3eaa0..63353d5bc5 100644
--- a/templates/shared_services/admin-vm/porter.yaml
+++ b/templates/shared_services/admin-vm/porter.yaml
@@ -1,7 +1,7 @@
---
schemaVersion: 1.0.0
name: tre-shared-service-admin-vm
-version: 0.5.4
+version: 0.5.5
description: "An admin vm shared service"
dockerfile: Dockerfile.tmpl
registry: azuretre
diff --git a/templates/shared_services/airlock_notifier/Dockerfile.tmpl b/templates/shared_services/airlock_notifier/Dockerfile.tmpl
index 816a6fb388..70d881496f 100644
--- a/templates/shared_services/airlock_notifier/Dockerfile.tmpl
+++ b/templates/shared_services/airlock_notifier/Dockerfile.tmpl
@@ -1,5 +1,7 @@
# syntax=docker/dockerfile-upstream:1.4.0
-FROM --platform=linux/amd64 debian:bookworm-slim
+ARG DEBIAN_TAG=bookworm-slim
+ARG DEBIAN_BASE_IMAGE=debian
+FROM --platform=linux/amd64 ${DEBIAN_BASE_IMAGE}:${DEBIAN_TAG}
# PORTER_INIT
diff --git a/templates/shared_services/airlock_notifier/porter.yaml b/templates/shared_services/airlock_notifier/porter.yaml
index 700821b6cd..9a60dba981 100644
--- a/templates/shared_services/airlock_notifier/porter.yaml
+++ b/templates/shared_services/airlock_notifier/porter.yaml
@@ -1,7 +1,7 @@
---
schemaVersion: 1.0.0
name: tre-shared-service-airlock-notifier
-version: 1.0.10
+version: 1.0.11
description: "A shared service notifying on Airlock Operations"
registry: azuretre
dockerfile: Dockerfile.tmpl
diff --git a/templates/shared_services/certs/Dockerfile.tmpl b/templates/shared_services/certs/Dockerfile.tmpl
index f61b684795..1f392d71a0 100644
--- a/templates/shared_services/certs/Dockerfile.tmpl
+++ b/templates/shared_services/certs/Dockerfile.tmpl
@@ -1,5 +1,7 @@
# syntax=docker/dockerfile-upstream:1.4.0
-FROM --platform=linux/amd64 python:3.13-slim-bookworm
+ARG PYTHON_TAG=3.13-slim-bookworm
+ARG PYTHON_BASE_IMAGE=python
+FROM --platform=linux/amd64 ${PYTHON_BASE_IMAGE}:${PYTHON_TAG}
# PORTER_INIT
diff --git a/templates/shared_services/certs/porter.yaml b/templates/shared_services/certs/porter.yaml
index 7d01fd7630..bb61f19c01 100755
--- a/templates/shared_services/certs/porter.yaml
+++ b/templates/shared_services/certs/porter.yaml
@@ -1,7 +1,7 @@
---
schemaVersion: 1.0.0
name: tre-shared-service-certs
-version: 0.7.10
+version: 0.7.11
description: "An Azure TRE shared service to generate certificates for a specified internal domain using Letsencrypt"
registry: azuretre
dockerfile: Dockerfile.tmpl
diff --git a/templates/shared_services/cyclecloud/Dockerfile.tmpl b/templates/shared_services/cyclecloud/Dockerfile.tmpl
index abc241c3a1..8cf4c593a6 100644
--- a/templates/shared_services/cyclecloud/Dockerfile.tmpl
+++ b/templates/shared_services/cyclecloud/Dockerfile.tmpl
@@ -1,5 +1,7 @@
# syntax=docker/dockerfile-upstream:1.4.0
-FROM --platform=linux/amd64 debian:bookworm-slim
+ARG DEBIAN_TAG=bookworm-slim
+ARG DEBIAN_BASE_IMAGE=debian
+FROM --platform=linux/amd64 ${DEBIAN_BASE_IMAGE}:${DEBIAN_TAG}
# PORTER_INIT
diff --git a/templates/shared_services/cyclecloud/porter.yaml b/templates/shared_services/cyclecloud/porter.yaml
index df454e012a..48e3f40ab9 100644
--- a/templates/shared_services/cyclecloud/porter.yaml
+++ b/templates/shared_services/cyclecloud/porter.yaml
@@ -1,7 +1,7 @@
---
schemaVersion: 1.0.0
name: tre-shared-service-cyclecloud
-version: 0.7.5
+version: 0.7.6
description: "An Azure TRE Shared Service Template for Azure Cyclecloud"
registry: azuretre
dockerfile: Dockerfile.tmpl
diff --git a/templates/shared_services/databricks-auth/Dockerfile.tmpl b/templates/shared_services/databricks-auth/Dockerfile.tmpl
index abc241c3a1..8cf4c593a6 100644
--- a/templates/shared_services/databricks-auth/Dockerfile.tmpl
+++ b/templates/shared_services/databricks-auth/Dockerfile.tmpl
@@ -1,5 +1,7 @@
# syntax=docker/dockerfile-upstream:1.4.0
-FROM --platform=linux/amd64 debian:bookworm-slim
+ARG DEBIAN_TAG=bookworm-slim
+ARG DEBIAN_BASE_IMAGE=debian
+FROM --platform=linux/amd64 ${DEBIAN_BASE_IMAGE}:${DEBIAN_TAG}
# PORTER_INIT
diff --git a/templates/shared_services/databricks-auth/porter.yaml b/templates/shared_services/databricks-auth/porter.yaml
index f7d34be8cc..37c92151b6 100644
--- a/templates/shared_services/databricks-auth/porter.yaml
+++ b/templates/shared_services/databricks-auth/porter.yaml
@@ -1,7 +1,7 @@
---
schemaVersion: 1.0.0
name: tre-shared-service-databricks-private-auth
-version: 0.1.14
+version: 0.1.15
description: "An Azure TRE shared service for Azure Databricks authentication."
registry: azuretre
dockerfile: Dockerfile.tmpl
diff --git a/templates/shared_services/firewall/Dockerfile.tmpl b/templates/shared_services/firewall/Dockerfile.tmpl
index 4494f207a1..95c4e67f38 100644
--- a/templates/shared_services/firewall/Dockerfile.tmpl
+++ b/templates/shared_services/firewall/Dockerfile.tmpl
@@ -1,5 +1,7 @@
# syntax=docker/dockerfile-upstream:1.4.0
-FROM --platform=linux/amd64 debian:bookworm-slim
+ARG DEBIAN_TAG=bookworm-slim
+ARG DEBIAN_BASE_IMAGE=debian
+FROM --platform=linux/amd64 ${DEBIAN_BASE_IMAGE}:${DEBIAN_TAG}
# PORTER_INIT
diff --git a/templates/shared_services/firewall/porter.yaml b/templates/shared_services/firewall/porter.yaml
index e8399e844b..45174e8ae1 100644
--- a/templates/shared_services/firewall/porter.yaml
+++ b/templates/shared_services/firewall/porter.yaml
@@ -1,7 +1,7 @@
---
schemaVersion: 1.0.0
name: tre-shared-service-firewall
-version: 1.6.1
+version: 1.6.2
description: "An Azure TRE Firewall shared service"
dockerfile: Dockerfile.tmpl
registry: azuretre
diff --git a/templates/shared_services/gitea/Dockerfile.tmpl b/templates/shared_services/gitea/Dockerfile.tmpl
index abc241c3a1..8cf4c593a6 100644
--- a/templates/shared_services/gitea/Dockerfile.tmpl
+++ b/templates/shared_services/gitea/Dockerfile.tmpl
@@ -1,5 +1,7 @@
# syntax=docker/dockerfile-upstream:1.4.0
-FROM --platform=linux/amd64 debian:bookworm-slim
+ARG DEBIAN_TAG=bookworm-slim
+ARG DEBIAN_BASE_IMAGE=debian
+FROM --platform=linux/amd64 ${DEBIAN_BASE_IMAGE}:${DEBIAN_TAG}
# PORTER_INIT
diff --git a/templates/shared_services/gitea/docker/Dockerfile b/templates/shared_services/gitea/docker/Dockerfile
index 16b4fd3bff..07c498c4c0 100644
--- a/templates/shared_services/gitea/docker/Dockerfile
+++ b/templates/shared_services/gitea/docker/Dockerfile
@@ -1,7 +1,8 @@
ARG GITEA_TAG=1.15
+ARG GITEA_BASE_IMAGE_REPO=gitea/gitea
ARG CERTIFICATE_URL=https://www.digicert.com/CACerts/BaltimoreCyberTrustRoot.crt.pem
-FROM gitea/gitea:${GITEA_TAG}
+FROM ${GITEA_BASE_IMAGE_REPO}:${GITEA_TAG}
# need to pass args to stage
ARG CERTIFICATE_URL
RUN wget -nv -O /usr/local/share/ca-certificates/mysql.crt.pem ${CERTIFICATE_URL} && update-ca-certificates
diff --git a/templates/shared_services/gitea/docker/version.txt b/templates/shared_services/gitea/docker/version.txt
index df0ed33211..8a3be2e00c 100644
--- a/templates/shared_services/gitea/docker/version.txt
+++ b/templates/shared_services/gitea/docker/version.txt
@@ -1 +1 @@
-__version__ = "0.3.12"
+__version__ = "0.3.13"
diff --git a/templates/shared_services/gitea/porter.yaml b/templates/shared_services/gitea/porter.yaml
index f9a76a7565..700be2a7f6 100644
--- a/templates/shared_services/gitea/porter.yaml
+++ b/templates/shared_services/gitea/porter.yaml
@@ -1,7 +1,7 @@
---
schemaVersion: 1.0.0
name: tre-shared-service-gitea
-version: 1.2.2
+version: 1.2.3
description: "A Gitea shared service"
dockerfile: Dockerfile.tmpl
registry: azuretre
diff --git a/templates/shared_services/sonatype-nexus-vm/Dockerfile.tmpl b/templates/shared_services/sonatype-nexus-vm/Dockerfile.tmpl
index 4494f207a1..95c4e67f38 100644
--- a/templates/shared_services/sonatype-nexus-vm/Dockerfile.tmpl
+++ b/templates/shared_services/sonatype-nexus-vm/Dockerfile.tmpl
@@ -1,5 +1,7 @@
# syntax=docker/dockerfile-upstream:1.4.0
-FROM --platform=linux/amd64 debian:bookworm-slim
+ARG DEBIAN_TAG=bookworm-slim
+ARG DEBIAN_BASE_IMAGE=debian
+FROM --platform=linux/amd64 ${DEBIAN_BASE_IMAGE}:${DEBIAN_TAG}
# PORTER_INIT
diff --git a/templates/shared_services/sonatype-nexus-vm/porter.yaml b/templates/shared_services/sonatype-nexus-vm/porter.yaml
index b9e11dd85f..d43add938a 100644
--- a/templates/shared_services/sonatype-nexus-vm/porter.yaml
+++ b/templates/shared_services/sonatype-nexus-vm/porter.yaml
@@ -1,7 +1,7 @@
---
schemaVersion: 1.0.0
name: tre-shared-service-sonatype-nexus
-version: 3.7.9
+version: 3.7.10
description: "A Sonatype Nexus shared service"
dockerfile: Dockerfile.tmpl
registry: azuretre
diff --git a/templates/workspace_services/azureml/Dockerfile.tmpl b/templates/workspace_services/azureml/Dockerfile.tmpl
index 44ffe027c8..14df9b4af3 100644
--- a/templates/workspace_services/azureml/Dockerfile.tmpl
+++ b/templates/workspace_services/azureml/Dockerfile.tmpl
@@ -1,5 +1,7 @@
# syntax=docker/dockerfile-upstream:1.4.0
-FROM --platform=linux/amd64 debian:bookworm-slim
+ARG DEBIAN_TAG=bookworm-slim
+ARG DEBIAN_BASE_IMAGE=debian
+FROM --platform=linux/amd64 ${DEBIAN_BASE_IMAGE}:${DEBIAN_TAG}
# PORTER_INIT
diff --git a/templates/workspace_services/azureml/porter.yaml b/templates/workspace_services/azureml/porter.yaml
index d6d4593ad4..723713cf35 100644
--- a/templates/workspace_services/azureml/porter.yaml
+++ b/templates/workspace_services/azureml/porter.yaml
@@ -1,7 +1,7 @@
---
schemaVersion: 1.0.0
name: tre-service-azureml
-version: 1.1.4
+version: 1.1.5
description: "An Azure TRE service for Azure Machine Learning"
registry: azuretre
dockerfile: Dockerfile.tmpl
diff --git a/templates/workspace_services/azureml/user_resources/aml_compute/Dockerfile.tmpl b/templates/workspace_services/azureml/user_resources/aml_compute/Dockerfile.tmpl
index 4494f207a1..95c4e67f38 100644
--- a/templates/workspace_services/azureml/user_resources/aml_compute/Dockerfile.tmpl
+++ b/templates/workspace_services/azureml/user_resources/aml_compute/Dockerfile.tmpl
@@ -1,5 +1,7 @@
# syntax=docker/dockerfile-upstream:1.4.0
-FROM --platform=linux/amd64 debian:bookworm-slim
+ARG DEBIAN_TAG=bookworm-slim
+ARG DEBIAN_BASE_IMAGE=debian
+FROM --platform=linux/amd64 ${DEBIAN_BASE_IMAGE}:${DEBIAN_TAG}
# PORTER_INIT
diff --git a/templates/workspace_services/azureml/user_resources/aml_compute/porter.yaml b/templates/workspace_services/azureml/user_resources/aml_compute/porter.yaml
index 34d3ee7844..b3c529c996 100644
--- a/templates/workspace_services/azureml/user_resources/aml_compute/porter.yaml
+++ b/templates/workspace_services/azureml/user_resources/aml_compute/porter.yaml
@@ -1,7 +1,7 @@
---
schemaVersion: 1.0.0
name: tre-user-resource-aml-compute-instance
-version: 0.5.12
+version: 0.5.13
description: "Azure Machine Learning Compute Instance"
registry: azuretre
dockerfile: Dockerfile.tmpl
diff --git a/templates/workspace_services/azuresql/Dockerfile.tmpl b/templates/workspace_services/azuresql/Dockerfile.tmpl
index abc241c3a1..8cf4c593a6 100644
--- a/templates/workspace_services/azuresql/Dockerfile.tmpl
+++ b/templates/workspace_services/azuresql/Dockerfile.tmpl
@@ -1,5 +1,7 @@
# syntax=docker/dockerfile-upstream:1.4.0
-FROM --platform=linux/amd64 debian:bookworm-slim
+ARG DEBIAN_TAG=bookworm-slim
+ARG DEBIAN_BASE_IMAGE=debian
+FROM --platform=linux/amd64 ${DEBIAN_BASE_IMAGE}:${DEBIAN_TAG}
# PORTER_INIT
diff --git a/templates/workspace_services/azuresql/porter.yaml b/templates/workspace_services/azuresql/porter.yaml
index 7e72065bba..48e969cb9b 100644
--- a/templates/workspace_services/azuresql/porter.yaml
+++ b/templates/workspace_services/azuresql/porter.yaml
@@ -1,7 +1,7 @@
---
schemaVersion: 1.0.0
name: tre-workspace-service-azuresql
-version: 1.0.17
+version: 1.0.18
description: "An Azure SQL workspace service"
registry: azuretre
dockerfile: Dockerfile.tmpl
diff --git a/templates/workspace_services/databricks/Dockerfile.tmpl b/templates/workspace_services/databricks/Dockerfile.tmpl
index c85d6bd038..8cb83087fa 100644
--- a/templates/workspace_services/databricks/Dockerfile.tmpl
+++ b/templates/workspace_services/databricks/Dockerfile.tmpl
@@ -1,5 +1,7 @@
# syntax=docker/dockerfile-upstream:1.4.0
-FROM --platform=linux/amd64 debian:bookworm-slim
+ARG DEBIAN_TAG=bookworm-slim
+ARG DEBIAN_BASE_IMAGE=debian
+FROM --platform=linux/amd64 ${DEBIAN_BASE_IMAGE}:${DEBIAN_TAG}
# PORTER_INIT
diff --git a/templates/workspace_services/databricks/porter.yaml b/templates/workspace_services/databricks/porter.yaml
index abfdd18a83..4e913a4e09 100644
--- a/templates/workspace_services/databricks/porter.yaml
+++ b/templates/workspace_services/databricks/porter.yaml
@@ -1,7 +1,7 @@
---
schemaVersion: 1.0.0
name: tre-service-databricks
-version: 1.0.16
+version: 1.0.17
description: "An Azure TRE service for Azure Databricks."
registry: azuretre
dockerfile: Dockerfile.tmpl
diff --git a/templates/workspace_services/gitea/Dockerfile.tmpl b/templates/workspace_services/gitea/Dockerfile.tmpl
index abc241c3a1..8cf4c593a6 100644
--- a/templates/workspace_services/gitea/Dockerfile.tmpl
+++ b/templates/workspace_services/gitea/Dockerfile.tmpl
@@ -1,5 +1,7 @@
# syntax=docker/dockerfile-upstream:1.4.0
-FROM --platform=linux/amd64 debian:bookworm-slim
+ARG DEBIAN_TAG=bookworm-slim
+ARG DEBIAN_BASE_IMAGE=debian
+FROM --platform=linux/amd64 ${DEBIAN_BASE_IMAGE}:${DEBIAN_TAG}
# PORTER_INIT
diff --git a/templates/workspace_services/gitea/docker/Dockerfile b/templates/workspace_services/gitea/docker/Dockerfile
index c82267de82..85eb04864f 100644
--- a/templates/workspace_services/gitea/docker/Dockerfile
+++ b/templates/workspace_services/gitea/docker/Dockerfile
@@ -1,7 +1,8 @@
ARG GITEA_TAG=1.17.3
+ARG GITEA_BASE_IMAGE_REPO=gitea/gitea
ARG CERTIFICATE_URL=https://www.digicert.com/CACerts/BaltimoreCyberTrustRoot.crt.pem
-FROM gitea/gitea:${GITEA_TAG}
+FROM ${GITEA_BASE_IMAGE_REPO}:${GITEA_TAG}
# need to pass args to stage
ARG CERTIFICATE_URL
RUN wget -q -O /usr/local/share/ca-certificates/mysql.crt.pem ${CERTIFICATE_URL} && update-ca-certificates
diff --git a/templates/workspace_services/gitea/porter.yaml b/templates/workspace_services/gitea/porter.yaml
index f9044eb34e..39621ab355 100644
--- a/templates/workspace_services/gitea/porter.yaml
+++ b/templates/workspace_services/gitea/porter.yaml
@@ -1,7 +1,7 @@
---
schemaVersion: 1.0.0
name: tre-workspace-service-gitea
-version: 1.3.3
+version: 1.3.4
description: "A Gitea workspace service"
dockerfile: Dockerfile.tmpl
registry: azuretre
diff --git a/templates/workspace_services/gitea/version.txt b/templates/workspace_services/gitea/version.txt
index 43c4ab0058..22049ab2c4 100644
--- a/templates/workspace_services/gitea/version.txt
+++ b/templates/workspace_services/gitea/version.txt
@@ -1 +1 @@
-__version__ = "0.6.1"
+__version__ = "0.6.2"
diff --git a/templates/workspace_services/guacamole/Dockerfile.tmpl b/templates/workspace_services/guacamole/Dockerfile.tmpl
index abc241c3a1..8cf4c593a6 100644
--- a/templates/workspace_services/guacamole/Dockerfile.tmpl
+++ b/templates/workspace_services/guacamole/Dockerfile.tmpl
@@ -1,5 +1,7 @@
# syntax=docker/dockerfile-upstream:1.4.0
-FROM --platform=linux/amd64 debian:bookworm-slim
+ARG DEBIAN_TAG=bookworm-slim
+ARG DEBIAN_BASE_IMAGE=debian
+FROM --platform=linux/amd64 ${DEBIAN_BASE_IMAGE}:${DEBIAN_TAG}
# PORTER_INIT
diff --git a/templates/workspace_services/guacamole/e2e-tests/playwright/Dockerfile b/templates/workspace_services/guacamole/e2e-tests/playwright/Dockerfile
index 202b4336af..4b15b177bf 100644
--- a/templates/workspace_services/guacamole/e2e-tests/playwright/Dockerfile
+++ b/templates/workspace_services/guacamole/e2e-tests/playwright/Dockerfile
@@ -1,4 +1,6 @@
-FROM mcr.microsoft.com/playwright:v1.56.1-noble
+ARG PLAYWRIGHT_TAG=v1.56.1-noble
+ARG PLAYWRIGHT_BASE_IMAGE=mcr.microsoft.com/playwright
+FROM ${PLAYWRIGHT_BASE_IMAGE}:${PLAYWRIGHT_TAG}
WORKDIR /app
diff --git a/templates/workspace_services/guacamole/guacamole-server/docker/Dockerfile b/templates/workspace_services/guacamole/guacamole-server/docker/Dockerfile
index 912777575a..1115bd92e8 100644
--- a/templates/workspace_services/guacamole/guacamole-server/docker/Dockerfile
+++ b/templates/workspace_services/guacamole/guacamole-server/docker/Dockerfile
@@ -1,4 +1,8 @@
-FROM maven:3.9-eclipse-temurin-17-alpine AS client_build
+ARG MAVEN_TAG=3.9-eclipse-temurin-17-alpine
+ARG MAVEN_BASE_IMAGE=maven
+ARG GUACD_TAG=1.6.0
+ARG GUACD_BASE_IMAGE=guacamole/guacd
+FROM ${MAVEN_BASE_IMAGE}:${MAVEN_TAG} AS client_build
COPY ./guacamole-auth-azure/pom.xml /pom.xml
# cache dependencies in a separate layer
@@ -11,7 +15,7 @@ RUN bash /tmp/maven_package_and_exit_succesfully.sh
FROM scratch AS test-results
COPY --from=client_build /target/surefire-reports/* /
-FROM guacamole/guacd:1.6.0
+FROM ${GUACD_BASE_IMAGE}:${GUACD_TAG}
ARG GUACAMOLE_AZURE_VERSION=0.5.0
diff --git a/templates/workspace_services/guacamole/guacamole-server/docker/version.txt b/templates/workspace_services/guacamole/guacamole-server/docker/version.txt
index 61fb31cae0..1f4c4d43b2 100644
--- a/templates/workspace_services/guacamole/guacamole-server/docker/version.txt
+++ b/templates/workspace_services/guacamole/guacamole-server/docker/version.txt
@@ -1 +1 @@
-__version__ = "0.10.0"
+__version__ = "0.10.1"
diff --git a/templates/workspace_services/guacamole/porter.yaml b/templates/workspace_services/guacamole/porter.yaml
index b2ce9174b4..8f172fe986 100644
--- a/templates/workspace_services/guacamole/porter.yaml
+++ b/templates/workspace_services/guacamole/porter.yaml
@@ -1,7 +1,7 @@
---
schemaVersion: 1.0.0
name: tre-service-guacamole
-version: 0.14.2
+version: 0.14.3
description: "An Azure TRE service for Guacamole"
dockerfile: Dockerfile.tmpl
registry: azuretre
diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/Dockerfile.tmpl b/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/Dockerfile.tmpl
index 4494f207a1..95c4e67f38 100644
--- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/Dockerfile.tmpl
+++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/Dockerfile.tmpl
@@ -1,5 +1,7 @@
# syntax=docker/dockerfile-upstream:1.4.0
-FROM --platform=linux/amd64 debian:bookworm-slim
+ARG DEBIAN_TAG=bookworm-slim
+ARG DEBIAN_BASE_IMAGE=debian
+FROM --platform=linux/amd64 ${DEBIAN_BASE_IMAGE}:${DEBIAN_TAG}
# PORTER_INIT
diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/porter.yaml b/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/porter.yaml
index 83b05768ce..975cf77416 100644
--- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/porter.yaml
+++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/porter.yaml
@@ -1,7 +1,7 @@
---
schemaVersion: 1.0.0
name: tre-service-guacamole-export-reviewvm
-version: 0.3.7
+version: 0.3.8
description: "An Azure TRE User Resource Template for reviewing Airlock export requests"
dockerfile: Dockerfile.tmpl
registry: azuretre
diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/Dockerfile.tmpl b/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/Dockerfile.tmpl
index 4494f207a1..95c4e67f38 100644
--- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/Dockerfile.tmpl
+++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/Dockerfile.tmpl
@@ -1,5 +1,7 @@
# syntax=docker/dockerfile-upstream:1.4.0
-FROM --platform=linux/amd64 debian:bookworm-slim
+ARG DEBIAN_TAG=bookworm-slim
+ARG DEBIAN_BASE_IMAGE=debian
+FROM --platform=linux/amd64 ${DEBIAN_BASE_IMAGE}:${DEBIAN_TAG}
# PORTER_INIT
diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/porter.yaml b/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/porter.yaml
index a2fbed781e..fe445ece10 100644
--- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/porter.yaml
+++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/porter.yaml
@@ -1,7 +1,7 @@
---
schemaVersion: 1.0.0
name: tre-service-guacamole-import-reviewvm
-version: 0.4.6
+version: 0.4.7
description: "An Azure TRE User Resource Template for reviewing Airlock import requests"
dockerfile: Dockerfile.tmpl
registry: azuretre
diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/Dockerfile.tmpl b/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/Dockerfile.tmpl
index 4494f207a1..95c4e67f38 100644
--- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/Dockerfile.tmpl
+++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/Dockerfile.tmpl
@@ -1,5 +1,7 @@
# syntax=docker/dockerfile-upstream:1.4.0
-FROM --platform=linux/amd64 debian:bookworm-slim
+ARG DEBIAN_TAG=bookworm-slim
+ARG DEBIAN_BASE_IMAGE=debian
+FROM --platform=linux/amd64 ${DEBIAN_BASE_IMAGE}:${DEBIAN_TAG}
# PORTER_INIT
diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/porter.yaml b/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/porter.yaml
index 186da0d571..f95113432c 100644
--- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/porter.yaml
+++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/porter.yaml
@@ -1,7 +1,7 @@
---
schemaVersion: 1.0.0
name: tre-service-guacamole-linuxvm
-version: 1.4.3
+version: 1.4.4
description: "An Azure TRE User Resource Template for Guacamole (Linux)"
dockerfile: Dockerfile.tmpl
registry: azuretre
diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/Dockerfile.tmpl b/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/Dockerfile.tmpl
index 4494f207a1..95c4e67f38 100644
--- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/Dockerfile.tmpl
+++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/Dockerfile.tmpl
@@ -1,5 +1,7 @@
# syntax=docker/dockerfile-upstream:1.4.0
-FROM --platform=linux/amd64 debian:bookworm-slim
+ARG DEBIAN_TAG=bookworm-slim
+ARG DEBIAN_BASE_IMAGE=debian
+FROM --platform=linux/amd64 ${DEBIAN_BASE_IMAGE}:${DEBIAN_TAG}
# PORTER_INIT
diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/porter.yaml b/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/porter.yaml
index f7e7bd963d..ea5de7a2e4 100644
--- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/porter.yaml
+++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/porter.yaml
@@ -1,7 +1,7 @@
---
schemaVersion: 1.0.0
name: tre-service-guacamole-windowsvm
-version: 1.4.6
+version: 1.4.7
description: "An Azure TRE User Resource Template for Guacamole (Windows 11 or Windows Server 2025)"
dockerfile: Dockerfile.tmpl
registry: azuretre
diff --git a/templates/workspace_services/health-services/Dockerfile.tmpl b/templates/workspace_services/health-services/Dockerfile.tmpl
index 44ffe027c8..14df9b4af3 100644
--- a/templates/workspace_services/health-services/Dockerfile.tmpl
+++ b/templates/workspace_services/health-services/Dockerfile.tmpl
@@ -1,5 +1,7 @@
# syntax=docker/dockerfile-upstream:1.4.0
-FROM --platform=linux/amd64 debian:bookworm-slim
+ARG DEBIAN_TAG=bookworm-slim
+ARG DEBIAN_BASE_IMAGE=debian
+FROM --platform=linux/amd64 ${DEBIAN_BASE_IMAGE}:${DEBIAN_TAG}
# PORTER_INIT
diff --git a/templates/workspace_services/health-services/porter.yaml b/templates/workspace_services/health-services/porter.yaml
index d7dd92c469..c59b50f62d 100644
--- a/templates/workspace_services/health-services/porter.yaml
+++ b/templates/workspace_services/health-services/porter.yaml
@@ -1,7 +1,7 @@
---
schemaVersion: 1.0.0
name: tre-workspace-service-health
-version: 0.3.5
+version: 0.3.6
description: "An Azure Data Health Services workspace service"
registry: azuretre
dockerfile: Dockerfile.tmpl
diff --git a/templates/workspace_services/mysql/Dockerfile.tmpl b/templates/workspace_services/mysql/Dockerfile.tmpl
index abc241c3a1..8cf4c593a6 100644
--- a/templates/workspace_services/mysql/Dockerfile.tmpl
+++ b/templates/workspace_services/mysql/Dockerfile.tmpl
@@ -1,5 +1,7 @@
# syntax=docker/dockerfile-upstream:1.4.0
-FROM --platform=linux/amd64 debian:bookworm-slim
+ARG DEBIAN_TAG=bookworm-slim
+ARG DEBIAN_BASE_IMAGE=debian
+FROM --platform=linux/amd64 ${DEBIAN_BASE_IMAGE}:${DEBIAN_TAG}
# PORTER_INIT
diff --git a/templates/workspace_services/mysql/porter.yaml b/templates/workspace_services/mysql/porter.yaml
index 80ce54d0a8..fb18c7bf97 100644
--- a/templates/workspace_services/mysql/porter.yaml
+++ b/templates/workspace_services/mysql/porter.yaml
@@ -1,7 +1,7 @@
---
schemaVersion: 1.0.0
name: tre-workspace-service-mysql
-version: 1.0.12
+version: 1.0.13
description: "A MySQL workspace service"
registry: azuretre
dockerfile: Dockerfile.tmpl
diff --git a/templates/workspace_services/ohdsi/Dockerfile.tmpl b/templates/workspace_services/ohdsi/Dockerfile.tmpl
index 12a32994a5..e89442d0d3 100644
--- a/templates/workspace_services/ohdsi/Dockerfile.tmpl
+++ b/templates/workspace_services/ohdsi/Dockerfile.tmpl
@@ -1,5 +1,7 @@
# syntax=docker/dockerfile-upstream:1.4.0
-FROM --platform=linux/amd64 debian:bullseye-slim
+ARG DEBIAN_TAG=bullseye-slim
+ARG DEBIAN_BASE_IMAGE=debian
+FROM --platform=linux/amd64 ${DEBIAN_BASE_IMAGE}:${DEBIAN_TAG}
# PORTER_INIT
diff --git a/templates/workspace_services/ohdsi/porter.yaml b/templates/workspace_services/ohdsi/porter.yaml
index ca5e7ef556..7c9aa6caae 100644
--- a/templates/workspace_services/ohdsi/porter.yaml
+++ b/templates/workspace_services/ohdsi/porter.yaml
@@ -1,7 +1,7 @@
---
schemaVersion: 1.0.0
name: tre-workspace-service-ohdsi
-version: 0.3.7
+version: 0.3.8
description: "An OHDSI workspace service"
registry: azuretre
dockerfile: Dockerfile.tmpl
diff --git a/templates/workspace_services/openai/Dockerfile.tmpl b/templates/workspace_services/openai/Dockerfile.tmpl
index abc241c3a1..8cf4c593a6 100644
--- a/templates/workspace_services/openai/Dockerfile.tmpl
+++ b/templates/workspace_services/openai/Dockerfile.tmpl
@@ -1,5 +1,7 @@
# syntax=docker/dockerfile-upstream:1.4.0
-FROM --platform=linux/amd64 debian:bookworm-slim
+ARG DEBIAN_TAG=bookworm-slim
+ARG DEBIAN_BASE_IMAGE=debian
+FROM --platform=linux/amd64 ${DEBIAN_BASE_IMAGE}:${DEBIAN_TAG}
# PORTER_INIT
diff --git a/templates/workspace_services/openai/porter.yaml b/templates/workspace_services/openai/porter.yaml
index 4ad4efc6b9..f5d2a8383d 100644
--- a/templates/workspace_services/openai/porter.yaml
+++ b/templates/workspace_services/openai/porter.yaml
@@ -1,7 +1,7 @@
---
schemaVersion: 1.0.0
name: tre-workspace-service-openai
-version: 1.0.8
+version: 1.0.9
description: "An OpenAI workspace service"
registry: azuretre
dockerfile: Dockerfile.tmpl
diff --git a/templates/workspaces/airlock-import-review/Dockerfile.tmpl b/templates/workspaces/airlock-import-review/Dockerfile.tmpl
index 4a9293704a..f5aeccc6af 100644
--- a/templates/workspaces/airlock-import-review/Dockerfile.tmpl
+++ b/templates/workspaces/airlock-import-review/Dockerfile.tmpl
@@ -1,5 +1,7 @@
# syntax=docker/dockerfile-upstream:1.4.0
-FROM --platform=linux/amd64 debian:bookworm-slim
+ARG DEBIAN_TAG=bookworm-slim
+ARG DEBIAN_BASE_IMAGE=debian
+FROM --platform=linux/amd64 ${DEBIAN_BASE_IMAGE}:${DEBIAN_TAG}
# PORTER_INIT
diff --git a/templates/workspaces/airlock-import-review/porter.yaml b/templates/workspaces/airlock-import-review/porter.yaml
index 464a41d4f7..3e56028346 100644
--- a/templates/workspaces/airlock-import-review/porter.yaml
+++ b/templates/workspaces/airlock-import-review/porter.yaml
@@ -1,7 +1,7 @@
---
schemaVersion: 1.0.0
name: tre-workspace-airlock-import-review
-version: 0.15.0
+version: 0.15.1
description: "A workspace to do Airlock Data Import Reviews for Azure TRE"
dockerfile: Dockerfile.tmpl
registry: azuretre
diff --git a/templates/workspaces/base/Dockerfile.tmpl b/templates/workspaces/base/Dockerfile.tmpl
index 44ffe027c8..14df9b4af3 100644
--- a/templates/workspaces/base/Dockerfile.tmpl
+++ b/templates/workspaces/base/Dockerfile.tmpl
@@ -1,5 +1,7 @@
# syntax=docker/dockerfile-upstream:1.4.0
-FROM --platform=linux/amd64 debian:bookworm-slim
+ARG DEBIAN_TAG=bookworm-slim
+ARG DEBIAN_BASE_IMAGE=debian
+FROM --platform=linux/amd64 ${DEBIAN_BASE_IMAGE}:${DEBIAN_TAG}
# PORTER_INIT
diff --git a/templates/workspaces/base/porter.yaml b/templates/workspaces/base/porter.yaml
index 22cb6cdfc3..456e99b854 100644
--- a/templates/workspaces/base/porter.yaml
+++ b/templates/workspaces/base/porter.yaml
@@ -1,7 +1,7 @@
---
schemaVersion: 1.0.0
name: tre-workspace-base
-version: 2.8.4
+version: 2.8.5
description: "A base Azure TRE workspace"
dockerfile: Dockerfile.tmpl
registry: azuretre
diff --git a/templates/workspaces/unrestricted/Dockerfile.tmpl b/templates/workspaces/unrestricted/Dockerfile.tmpl
index 1b12132325..0a11c4f2c4 100644
--- a/templates/workspaces/unrestricted/Dockerfile.tmpl
+++ b/templates/workspaces/unrestricted/Dockerfile.tmpl
@@ -1,5 +1,7 @@
# syntax=docker/dockerfile-upstream:1.4.0
-FROM --platform=linux/amd64 debian:bookworm-slim
+ARG DEBIAN_TAG=bookworm-slim
+ARG DEBIAN_BASE_IMAGE=debian
+FROM --platform=linux/amd64 ${DEBIAN_BASE_IMAGE}:${DEBIAN_TAG}
# PORTER_INIT
diff --git a/templates/workspaces/unrestricted/porter.yaml b/templates/workspaces/unrestricted/porter.yaml
index bd4373b587..ede4b755dc 100644
--- a/templates/workspaces/unrestricted/porter.yaml
+++ b/templates/workspaces/unrestricted/porter.yaml
@@ -1,7 +1,7 @@
---
schemaVersion: 1.0.0
name: tre-workspace-unrestricted
-version: 0.13.7
+version: 0.13.8
description: "A base Azure TRE workspace"
dockerfile: Dockerfile.tmpl
registry: azuretre