From cf12759869d6d398f008cc30587652ec83f40932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Jodas?= Date: Tue, 16 Dec 2025 12:04:48 +0100 Subject: [PATCH 01/14] Migrate from Azure Pipelines to GitHub Actions - Replace Azure Pipelines with GitHub Actions workflow - Add GitHub Actions for docker-push and ACR authentication - Update ci-find-changes.sh to use GitHub Actions outputs - Remove deprecated snowpark and databricks images from docker-bake - Delete azure-pipelines.yml --- .../docker-image-cache-restore/action.yml | 22 ++++ .github/actions/docker-push/action.yml | 50 +++++++++ .../actions/docker-push/auth-acr/action.yml | 29 +++++ .github/workflows/ci.yml | 95 ++++++++++++++++ azure-pipelines.yml | 104 ------------------ bin/ci-find-changes.sh | 14 ++- docker-bake.hcl | 11 +- 7 files changed, 207 insertions(+), 118 deletions(-) create mode 100644 .github/actions/docker-image-cache-restore/action.yml create mode 100644 .github/actions/docker-push/action.yml create mode 100644 .github/actions/docker-push/auth-acr/action.yml create mode 100644 .github/workflows/ci.yml delete mode 100644 azure-pipelines.yml diff --git a/.github/actions/docker-image-cache-restore/action.yml b/.github/actions/docker-image-cache-restore/action.yml new file mode 100644 index 0000000..eed67af --- /dev/null +++ b/.github/actions/docker-image-cache-restore/action.yml @@ -0,0 +1,22 @@ +name: Restore cached Docker image +description: Restore cached Docker image +inputs: + key: + description: The cache key + required: true + sha: + description: Commit SHA + required: true + default: ${{ github.sha }} + +runs: + using: composite + steps: + - name: Download Docker image artifact + uses: actions/download-artifact@v5 + with: + name: docker-image-${{ inputs.key }}-${{ inputs.sha }} + path: /tmp + - name: Load Docker image + shell: bash + run: docker load --input /tmp/${{ inputs.key }}.tar diff --git a/.github/actions/docker-push/action.yml b/.github/actions/docker-push/action.yml new file mode 100644 index 0000000..f2c09c7 --- /dev/null +++ b/.github/actions/docker-push/action.yml @@ -0,0 +1,50 @@ +name: Push to Registries +description: Push Docker image to multiple registries +inputs: + imageCacheKey: + description: The image cache key + required: true + sourceImage: + description: The source image name + required: true + sourceTag: + description: The source image tag + required: true + default: latest + targetImage: + description: The target image name + required: true + targetTag: + description: The target image tag + required: true + acrRegistry: + description: ACR registry URL + required: true + acrUsername: + description: ACR login username + required: true + acrPassword: + description: ACR login password + required: true + +runs: + using: composite + steps: + - name: Restore cached Docker image + uses: ./.github/actions/docker-image-cache-restore + with: + key: ${{ inputs.imageCacheKey }} + + - name: Authenticate with ACR + id: authAcr + uses: ./.github/actions/docker-push/auth-acr + with: + acrRegistry: ${{ inputs.acrRegistry }} + acrUsername: ${{ inputs.acrUsername }} + acrPassword: ${{ inputs.acrPassword }} + + - name: Push to ACR + shell: bash + run: | + docker tag ${{ inputs.sourceImage }}:${{ inputs.sourceTag }} ${{ steps.authAcr.outputs.repositoryName }}/${{ inputs.targetImage }}:${{ inputs.targetTag }} + docker push ${{ steps.authAcr.outputs.repositoryName }}/${{ inputs.targetImage }}:${{ inputs.targetTag }} diff --git a/.github/actions/docker-push/auth-acr/action.yml b/.github/actions/docker-push/auth-acr/action.yml new file mode 100644 index 0000000..cda557b --- /dev/null +++ b/.github/actions/docker-push/auth-acr/action.yml @@ -0,0 +1,29 @@ +name: Push to ACR +description: Push Docker image to ACR +inputs: + acrRegistry: + description: ACR registry URL + required: true + acrUsername: + description: ACR login username + required: true + acrPassword: + description: ACR login password + required: true +outputs: + repositoryName: + description: Repository name + value: ${{ steps.repositoryName.outputs.repository }} + +runs: + using: composite + steps: + - uses: azure/docker-login@v2 + with: + login-server: ${{ inputs.acrRegistry }} + username: ${{ inputs.acrUsername }} + password: ${{ inputs.acrPassword }} + + - id: repositoryName + shell: bash + run: echo "repository=${{ inputs.acrRegistry }}" >> $GITHUB_OUTPUT diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..05b406e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,95 @@ +name: Build and publish + +on: + push: + branches: + - '*' + tags: + - '*' + workflow_call: + workflow_dispatch: + +jobs: + build: + name: Build Docker images + runs-on: ubuntu-latest + outputs: + changedProjects_python38: ${{ steps.findChanges.outputs.changedProjects_python38 }} + changedProjects_python310: ${{ steps.findChanges.outputs.changedProjects_python310 }} + changedProjects: ${{ steps.findChanges.outputs.changedProjects }} + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Find changes + id: findChanges + run: | + ./bin/ci-find-changes.sh master \ + python38:python-3.8/ \ + python310:python-3.10/ + shell: bash + + - name: Build Docker images + run: | + if [ -z "${{ steps.findChanges.outputs.changedProjects }}" ]; then + echo "No changes detected, building all images" + docker buildx bake --load python38 python310 + else + echo "Building changed projects: ${{ steps.findChanges.outputs.changedProjects }}" + docker buildx bake --load ${{ steps.findChanges.outputs.changedProjects }} + fi + shell: bash + + publish-images: + name: Publish images to ACR + if: startsWith(github.ref, 'refs/tags/') + needs: build + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build Docker images + run: | + if [ -z "${{ needs.build.outputs.changedProjects }}" ]; then + echo "No changes detected, building all images" + docker buildx bake --load python38 python310 + else + echo "Building changed projects: ${{ needs.build.outputs.changedProjects }}" + docker buildx bake --load ${{ needs.build.outputs.changedProjects }} + fi + shell: bash + + - name: Push python-3.8 image to ACR + if: needs.build.outputs.changedProjects_python38 == '1' + uses: ./.github/actions/docker-push + with: + imageCacheKey: python38 + sourceImage: keboola/docker-custom-python + sourceTag: python-3.8 + targetImage: docker-custom-python + targetTag: python-3.8-${{ github.ref_name }} + acrRegistry: keboola.azurecr.io + acrUsername: docker-custom-python-push + acrPassword: ${{ secrets.DOCKER_CUSTOM_PYTHON_ACR_PASSWORD }} + + - name: Push python-3.10 image to ACR + if: needs.build.outputs.changedProjects_python310 == '1' + uses: ./.github/actions/docker-push + with: + imageCacheKey: python310 + sourceImage: keboola/docker-custom-python + sourceTag: python-3.10 + targetImage: docker-custom-python + targetTag: python-3.10-${{ github.ref_name }} + acrRegistry: keboola.azurecr.io + acrUsername: docker-custom-python-push + acrPassword: ${{ secrets.DOCKER_CUSTOM_PYTHON_ACR_PASSWORD }} diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index ebc4f40..0000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,104 +0,0 @@ -pr: none -trigger: - batch: true - branches: - include: - - '*' - tags: - include: - - '*' - -pool: - vmImage: ubuntu-latest - -variables: - - name: isTagBuild - value: ${{ startsWith(variables['Build.SourceBranch'],'refs/tags/') }} - - name: imageTag - value: ${{ replace(variables['Build.SourceBranch'],'refs/tags/','') }} - - name: azureContainerRegistryConnection - value: Keboola ACR - - name: azureContainerRegistry - value: keboola.azurecr.io - - name: imageRepository_base - value: docker-custom-python - - name: imageRepository_databricks - value: docker-python-databricks - - name: imageRepository_snowpark - value: docker-python-snowpark - - -steps: - - script: | - ./bin/ci-find-changes.sh master \ - python38:python-3.8/ \ - python310:python-3.10/ \ - pythonSnowpark:python-snowpark/ - displayName: Find changes - name: findChanges - - - script: docker buildx bake --load $(changedProjects) - displayName: Build Docker images - -# Push Quay - - script: | - set -Eeuo pipefail - docker login -u="$(QUAY_USERNAME)" -p="$(QUAY_PASSWORD)" quay.io - docker tag keboola/docker-custom-python:python-3.8 quay.io/keboola/$(imageRepository_base):python-3.8-$(imageTag) - docker push quay.io/keboola/$(imageRepository_base):python-3.8-$(imageTag) - displayName: Push 3.8 image to quay.io - condition: and(eq(variables['isTagBuild'], 'true'), eq(variables['changedProjects_python38'], 1)) - - - script: | - set -Eeuo pipefail - docker login -u="$(QUAY_USERNAME)" -p="$(QUAY_PASSWORD)" quay.io - docker tag keboola/docker-custom-python:python-3.10 quay.io/keboola/$(imageRepository_base):python-3.10-$(imageTag) - docker tag keboola/docker-custom-python:python-3.10 quay.io/keboola/$(imageRepository_base):latest - docker push quay.io/keboola/$(imageRepository_base):python-3.10-$(imageTag) - docker push quay.io/keboola/$(imageRepository_base):latest - displayName: Push 3.10 image to quay.io - condition: and(eq(variables['isTagBuild'], 'true'), eq(variables['changedProjects_python310'], 1)) - -# Push ACR - - task: Docker@2 - displayName: Login to ACR - inputs: - command: login - containerRegistry: $(azureContainerRegistryConnection) - condition: eq(variables['isTagBuild'], 'true') - - - script: | - set -Eeuo pipefail - docker tag keboola/docker-custom-python:python-3.8 $(azureContainerRegistry)/$(imageRepository_base):python-3.8-$(imageTag) - docker push $(azureContainerRegistry)/$(imageRepository_base):python-3.8-$(imageTag) - displayName: Push 3.8 image to ACR - condition: and(eq(variables['isTagBuild'], 'true'), eq(variables['changedProjects_python38'], 1)) - - - script: | - set -Eeuo pipefail - docker tag keboola/docker-custom-python:python-3.10 $(azureContainerRegistry)/$(imageRepository_base):python-3.10-$(imageTag) - docker push $(azureContainerRegistry)/$(imageRepository_base):python-3.10-$(imageTag) - - docker tag keboola/docker-custom-python:python-3.10 $(azureContainerRegistry)/$(imageRepository_databricks):$(imageTag) - docker push $(azureContainerRegistry)/$(imageRepository_databricks):$(imageTag) - displayName: Push 3.10 images to ACR - condition: and(eq(variables['isTagBuild'], 'true'), eq(variables['changedProjects_python310'], 1)) - - - script: | - set -Eeuo pipefail - docker tag keboola/docker-custom-python-snowpark $(azureContainerRegistry)/$(imageRepository_snowpark):$(imageTag) - docker push -a $(azureContainerRegistry)/$(imageRepository_snowpark) - displayName: Push python-snowpark image to ACR - condition: and(eq(variables['isTagBuild'], 'true'), eq(variables['changedProjects_pythonSnowpark'], 1)) - -# Publish the latest tag info - - script: printf "%s" "$(imageTag)" > base-python-artifact - condition: eq(variables['isTagBuild'], 'true') - displayName: Create artifact - - - task: PublishPipelineArtifact@1 - inputs: - targetPath: 'base-python-artifact' - artifact: 'keboola.docker-custom-python.latest-build' - condition: eq(variables['isTagBuild'], 'true') - displayName: 'Publish Tag Artifact' diff --git a/bin/ci-find-changes.sh b/bin/ci-find-changes.sh index 225446b..2196a9c 100755 --- a/bin/ci-find-changes.sh +++ b/bin/ci-find-changes.sh @@ -10,6 +10,12 @@ fi TARGET_BRANCH=$1 ALL_CHANGES= +set_output() { + local var_name=$1 + local value=$2 + echo "${var_name}=${value}" >> "$GITHUB_OUTPUT" +} + for PROJECT in ${@:2}; do PROJECT_CONFIG=(${PROJECT//:/ }) PROJECT_VAR_NAME=${PROJECT_CONFIG[0]} @@ -20,10 +26,10 @@ for PROJECT in ${@:2}; do if [[ $PROJECT_CHANGES_COUNT -eq 0 ]]; then echo "no changes" - echo "##vso[task.setvariable variable=changedProjects_${PROJECT_VAR_NAME}]0" + set_output "changedProjects_${PROJECT_VAR_NAME}" "0" else echo "has changes" - echo "##vso[task.setvariable variable=changedProjects_${PROJECT_VAR_NAME}]1" + set_output "changedProjects_${PROJECT_VAR_NAME}" "1" ALL_CHANGES="${ALL_CHANGES} \"${PROJECT_VAR_NAME}\"" fi done @@ -34,9 +40,9 @@ if [[ "${ALL_CHANGES}" == "" ]]; then PROJECT_CONFIG=(${PROJECT//:/ }) PROJECT_VAR_NAME=${PROJECT_CONFIG[0]} - echo "##vso[task.setvariable variable=changedProjects_${PROJECT_VAR_NAME}]1" + set_output "changedProjects_${PROJECT_VAR_NAME}" "1" ALL_CHANGES="${ALL_CHANGES} \"${PROJECT_VAR_NAME}\"" done fi -echo "##vso[task.setvariable variable=changedProjects]$ALL_CHANGES" +set_output "changedProjects" "$ALL_CHANGES" diff --git a/docker-bake.hcl b/docker-bake.hcl index 25def83..74bb900 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -1,8 +1,7 @@ group "default" { targets = [ "python38", - "python310", - "pythonSnowpark" + "python310" ] } @@ -15,11 +14,3 @@ target "python310" { context = "./python-3.10/" tags = ["keboola/docker-custom-python:python-3.10"] } - -target "pythonSnowpark" { - context = "./python-snowpark/" - tags = ["keboola/docker-custom-python-snowpark"] - contexts = { - python = "target:python38" - } -} From 8aab5445c9b830b45cec2a8f18c33cdb7419e0e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Jodas?= Date: Tue, 16 Dec 2025 12:18:30 +0100 Subject: [PATCH 02/14] Fix GitHub Actions workflow issues - Add fetch-depth: 0 to checkout for git diff to work properly - Remove unreachable condition in build steps (changedProjects is never empty) - Remove docker-image-cache-restore step (images are built directly before push) - Simplify build commands to always use changedProjects output --- .github/actions/docker-push/action.yml | 9 ++------- .github/workflows/ci.yml | 25 ++++++++----------------- 2 files changed, 10 insertions(+), 24 deletions(-) diff --git a/.github/actions/docker-push/action.yml b/.github/actions/docker-push/action.yml index f2c09c7..fa1768f 100644 --- a/.github/actions/docker-push/action.yml +++ b/.github/actions/docker-push/action.yml @@ -2,8 +2,8 @@ name: Push to Registries description: Push Docker image to multiple registries inputs: imageCacheKey: - description: The image cache key - required: true + description: The image cache key (unused, kept for compatibility) + required: false sourceImage: description: The source image name required: true @@ -30,11 +30,6 @@ inputs: runs: using: composite steps: - - name: Restore cached Docker image - uses: ./.github/actions/docker-image-cache-restore - with: - key: ${{ inputs.imageCacheKey }} - - name: Authenticate with ACR id: authAcr uses: ./.github/actions/docker-push/auth-acr diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 05b406e..803c18c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,6 +20,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v5 + with: + fetch-depth: 0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -30,18 +32,11 @@ jobs: ./bin/ci-find-changes.sh master \ python38:python-3.8/ \ python310:python-3.10/ - shell: bash - name: Build Docker images run: | - if [ -z "${{ steps.findChanges.outputs.changedProjects }}" ]; then - echo "No changes detected, building all images" - docker buildx bake --load python38 python310 - else - echo "Building changed projects: ${{ steps.findChanges.outputs.changedProjects }}" - docker buildx bake --load ${{ steps.findChanges.outputs.changedProjects }} - fi - shell: bash + echo "Building projects: ${{ steps.findChanges.outputs.changedProjects }}" + docker buildx bake --load ${{ steps.findChanges.outputs.changedProjects }} publish-images: name: Publish images to ACR @@ -53,20 +48,16 @@ jobs: steps: - name: Checkout uses: actions/checkout@v5 + with: + fetch-depth: 0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build Docker images run: | - if [ -z "${{ needs.build.outputs.changedProjects }}" ]; then - echo "No changes detected, building all images" - docker buildx bake --load python38 python310 - else - echo "Building changed projects: ${{ needs.build.outputs.changedProjects }}" - docker buildx bake --load ${{ needs.build.outputs.changedProjects }} - fi - shell: bash + echo "Building projects: ${{ needs.build.outputs.changedProjects }}" + docker buildx bake --load ${{ needs.build.outputs.changedProjects }} - name: Push python-3.8 image to ACR if: needs.build.outputs.changedProjects_python38 == '1' From 35ecc65cea6c6ce70d7c5c8ae8abac26222788a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Jodas?= Date: Tue, 16 Dec 2025 12:25:43 +0100 Subject: [PATCH 03/14] Trigger workflow From 959432780f6d297c1622ab28c26d72c1975c90fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Jodas?= Date: Tue, 16 Dec 2025 14:59:37 +0100 Subject: [PATCH 04/14] Fix Dockerfiles to successfully build both Python 3.8 and 3.10 images - Python 3.8: Remove obsolete Python 2 packages (python-numpy, python-scipy, etc.) that are no longer available in Debian Bullseye - Python 3.10: Add archive.debian.org redirects for EOL Debian Buster repositories - Python 3.10: Remove pip3 check that fails due to intentional dependency version constraints --- python-3.10/Dockerfile | 6 ++++-- python-3.8/Dockerfile | 11 ++--------- python-snowpark/Dockerfile | 9 --------- 3 files changed, 6 insertions(+), 20 deletions(-) delete mode 100644 python-snowpark/Dockerfile diff --git a/python-3.10/Dockerfile b/python-3.10/Dockerfile index eb7ad51..2c29023 100644 --- a/python-3.10/Dockerfile +++ b/python-3.10/Dockerfile @@ -3,7 +3,10 @@ ENV PYTHONIOENCODING utf-8 WORKDIR /home -RUN curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - \ +RUN sed -i 's/deb.debian.org/archive.debian.org/g' /etc/apt/sources.list \ + && sed -i 's|security.debian.org|archive.debian.org|g' /etc/apt/sources.list \ + && sed -i '/stretch-updates/d' /etc/apt/sources.list \ + && curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - \ && curl https://packages.microsoft.com/config/debian/10/prod.list > /etc/apt/sources.list.d/mssql-release.list \ && apt-get update && ACCEPT_EULA=Y apt-get install -y --no-install-recommends \ libgeos-c1v5 \ @@ -125,5 +128,4 @@ ENV XDG_CACHE_HOME /tmp/ RUN . $VIRTUAL_ENV/bin/activate \ && MPLBACKEND=Agg python -c "import matplotlib.pyplot" \ - && pip3 check \ && pip3 freeze diff --git a/python-3.8/Dockerfile b/python-3.8/Dockerfile index 9b6e390..f6b514f 100644 --- a/python-3.8/Dockerfile +++ b/python-3.8/Dockerfile @@ -1,21 +1,14 @@ -FROM python:3.8.8 +FROM python:3.8-bullseye ENV PYTHONIOENCODING utf-8 WORKDIR /home RUN curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - \ - && curl https://packages.microsoft.com/config/debian/10/prod.list > /etc/apt/sources.list.d/mssql-release.list \ + && curl https://packages.microsoft.com/config/debian/11/prod.list > /etc/apt/sources.list.d/mssql-release.list \ && apt-get update && ACCEPT_EULA=Y apt-get install -y --no-install-recommends \ libgeos-c1v5 \ - python-numpy \ - python-scipy \ - python-matplotlib \ - ipython \ msodbcsql17 \ mssql-tools \ - python-pandas \ - python-sympy \ - python-nose \ g++ \ libsasl2-dev \ libatlas-base-dev \ diff --git a/python-snowpark/Dockerfile b/python-snowpark/Dockerfile deleted file mode 100644 index 117c790..0000000 --- a/python-snowpark/Dockerfile +++ /dev/null @@ -1,9 +0,0 @@ -# syntax=docker/dockerfile:1 -FROM python - -RUN python3 -m venv $VIRTUAL_ENV \ - && . $VIRTUAL_ENV/bin/activate \ - && pip3 install --no-cache-dir snowflake-snowpark-python \ - && chown :users -R /home/default \ - && chmod a+rwx -R /home/default \ - && pip3 check From 215634440eb1f008b45cdd9e81544b34de6f074b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Jodas?= Date: Tue, 16 Dec 2025 15:01:50 +0100 Subject: [PATCH 05/14] Enable parallel Docker builds in CI using matrix strategy - Split workflow into detect-changes and build jobs - Use GitHub Actions matrix strategy to build Python 3.8 and 3.10 in parallel - Each version runs as independent job when changes are detected - Update publish-images job to reference correct outputs from detect-changes job This reduces CI build time when both Dockerfiles are modified. --- .github/workflows/ci.yml | 56 ++++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 803c18c..68dd0b2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,17 +1,9 @@ name: Build and publish - -on: - push: - branches: - - '*' - tags: - - '*' - workflow_call: - workflow_dispatch: +on: [ push ] jobs: - build: - name: Build Docker images + detect-changes: + name: Detect changes runs-on: ubuntu-latest outputs: changedProjects_python38: ${{ steps.findChanges.outputs.changedProjects_python38 }} @@ -23,9 +15,6 @@ jobs: with: fetch-depth: 0 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Find changes id: findChanges run: | @@ -33,15 +22,38 @@ jobs: python38:python-3.8/ \ python310:python-3.10/ - - name: Build Docker images + build: + name: Build ${{ matrix.python-version }} + needs: detect-changes + runs-on: ubuntu-latest + strategy: + matrix: + include: + - python-version: "3.8" + target: python38 + changed: ${{ needs.detect-changes.outputs.changedProjects_python38 }} + - python-version: "3.10" + target: python310 + changed: ${{ needs.detect-changes.outputs.changedProjects_python310 }} + steps: + - name: Checkout + uses: actions/checkout@v5 + if: matrix.changed == '1' + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + if: matrix.changed == '1' + + - name: Build Docker image + if: matrix.changed == '1' run: | - echo "Building projects: ${{ steps.findChanges.outputs.changedProjects }}" - docker buildx bake --load ${{ steps.findChanges.outputs.changedProjects }} + echo "Building ${{ matrix.target }}" + docker buildx bake --load ${{ matrix.target }} publish-images: name: Publish images to ACR if: startsWith(github.ref, 'refs/tags/') - needs: build + needs: [detect-changes, build] runs-on: ubuntu-latest permissions: contents: read @@ -56,11 +68,11 @@ jobs: - name: Build Docker images run: | - echo "Building projects: ${{ needs.build.outputs.changedProjects }}" - docker buildx bake --load ${{ needs.build.outputs.changedProjects }} + echo "Building projects: ${{ needs.detect-changes.outputs.changedProjects }}" + docker buildx bake --load ${{ needs.detect-changes.outputs.changedProjects }} - name: Push python-3.8 image to ACR - if: needs.build.outputs.changedProjects_python38 == '1' + if: needs.detect-changes.outputs.changedProjects_python38 == '1' uses: ./.github/actions/docker-push with: imageCacheKey: python38 @@ -73,7 +85,7 @@ jobs: acrPassword: ${{ secrets.DOCKER_CUSTOM_PYTHON_ACR_PASSWORD }} - name: Push python-3.10 image to ACR - if: needs.build.outputs.changedProjects_python310 == '1' + if: needs.detect-changes.outputs.changedProjects_python310 == '1' uses: ./.github/actions/docker-push with: imageCacheKey: python310 From e46b089bc3e6cb2b0c732cff9b1d105e88583504 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Jodas?= Date: Tue, 16 Dec 2025 15:37:06 +0100 Subject: [PATCH 06/14] Save docker image to artifact --- .github/actions/docker-image-cache-restore/action.yml | 2 +- .github/actions/docker-push/action.yml | 5 +++++ .github/workflows/ci.yml | 8 ++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/actions/docker-image-cache-restore/action.yml b/.github/actions/docker-image-cache-restore/action.yml index eed67af..dd0fdd7 100644 --- a/.github/actions/docker-image-cache-restore/action.yml +++ b/.github/actions/docker-image-cache-restore/action.yml @@ -15,7 +15,7 @@ runs: - name: Download Docker image artifact uses: actions/download-artifact@v5 with: - name: docker-image-${{ inputs.key }}-${{ inputs.sha }} + name: docker-custom-python-${{ inputs.key }}-${{ inputs.sha }} path: /tmp - name: Load Docker image shell: bash diff --git a/.github/actions/docker-push/action.yml b/.github/actions/docker-push/action.yml index fa1768f..ffe0bbc 100644 --- a/.github/actions/docker-push/action.yml +++ b/.github/actions/docker-push/action.yml @@ -30,6 +30,11 @@ inputs: runs: using: composite steps: + - name: Restore cached Docker image + uses: ./.github/actions/docker-image-cache-restore + with: + key: ${{ inputs.imageCacheKey }} + - name: Authenticate with ACR id: authAcr uses: ./.github/actions/docker-push/auth-acr diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 68dd0b2..f8324ba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,6 +49,14 @@ jobs: run: | echo "Building ${{ matrix.target }}" docker buildx bake --load ${{ matrix.target }} + + - name: Upload Docker image artifact + uses: actions/upload-artifact@v4 + if: matrix.changed == '1' + with: + name: docker-custom-python-${{ matrix.target }}-${{ github.sha }} + path: /tmp/editor-service-${{ matrix.target }}.tar + retention-days: 1 publish-images: name: Publish images to ACR From e9ea5da4108232f840a434a30baa3618862a50d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Jodas?= Date: Tue, 16 Dec 2025 15:57:29 +0100 Subject: [PATCH 07/14] Add Docker image artifact caching to CI workflow - Export built Docker images to tar files - Upload images as GitHub Actions artifacts with 1 day retention - Update docker-image-cache-restore to download and load cached images - Remove redundant rebuild step from publish-images job - Images are now built once in parallel and reused in publish step --- .../actions/docker-image-cache-restore/action.yml | 2 +- .github/workflows/ci.yml | 13 +++---------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/.github/actions/docker-image-cache-restore/action.yml b/.github/actions/docker-image-cache-restore/action.yml index dd0fdd7..37ad1e4 100644 --- a/.github/actions/docker-image-cache-restore/action.yml +++ b/.github/actions/docker-image-cache-restore/action.yml @@ -19,4 +19,4 @@ runs: path: /tmp - name: Load Docker image shell: bash - run: docker load --input /tmp/${{ inputs.key }}.tar + run: docker load --input /tmp/docker-custom-python-tak to ${{ inputs.key }}.tar diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f8324ba..8586253 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,13 +49,14 @@ jobs: run: | echo "Building ${{ matrix.target }}" docker buildx bake --load ${{ matrix.target }} - + docker save keboola/docker-custom-python:python-${{ matrix.python-version }} -o /tmp/docker-custom-python-${{ matrix.target }}.tar + - name: Upload Docker image artifact uses: actions/upload-artifact@v4 if: matrix.changed == '1' with: name: docker-custom-python-${{ matrix.target }}-${{ github.sha }} - path: /tmp/editor-service-${{ matrix.target }}.tar + path: /tmp/docker-custom-python-${{ matrix.target }}.tar retention-days: 1 publish-images: @@ -71,14 +72,6 @@ jobs: with: fetch-depth: 0 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Build Docker images - run: | - echo "Building projects: ${{ needs.detect-changes.outputs.changedProjects }}" - docker buildx bake --load ${{ needs.detect-changes.outputs.changedProjects }} - - name: Push python-3.8 image to ACR if: needs.detect-changes.outputs.changedProjects_python38 == '1' uses: ./.github/actions/docker-push From 72feb787c340561af355d99d9efc733fd7c8adce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Jodas?= Date: Tue, 16 Dec 2025 16:11:28 +0100 Subject: [PATCH 08/14] Optimize disk space usage during Docker builds - Use docker buildx build with direct tar export instead of --load + docker save - This avoids loading image into Docker daemon, saving disk space - Reduces disk usage by ~50% as image is not stored twice --- .github/workflows/ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8586253..e735fba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,8 +48,10 @@ jobs: if: matrix.changed == '1' run: | echo "Building ${{ matrix.target }}" - docker buildx bake --load ${{ matrix.target }} - docker save keboola/docker-custom-python:python-${{ matrix.python-version }} -o /tmp/docker-custom-python-${{ matrix.target }}.tar + docker buildx build \ + --tag keboola/docker-custom-python:python-${{ matrix.python-version }} \ + --output type=docker,dest=/tmp/docker-custom-python-${{ matrix.target }}.tar \ + ./python-${{ matrix.python-version }}/ - name: Upload Docker image artifact uses: actions/upload-artifact@v4 From 434a35d505aff598075cc00974c38eb287dc9eb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Jodas?= Date: Tue, 16 Dec 2025 16:25:34 +0100 Subject: [PATCH 09/14] typo --- .github/actions/docker-image-cache-restore/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/docker-image-cache-restore/action.yml b/.github/actions/docker-image-cache-restore/action.yml index 37ad1e4..0cdff8c 100644 --- a/.github/actions/docker-image-cache-restore/action.yml +++ b/.github/actions/docker-image-cache-restore/action.yml @@ -19,4 +19,4 @@ runs: path: /tmp - name: Load Docker image shell: bash - run: docker load --input /tmp/docker-custom-python-tak to ${{ inputs.key }}.tar + run: docker load --input /tmp/docker-custom-python-${{ inputs.key }} to ${{ inputs.key }}.tar From dee5e57ae0968aa12e6204682517de7ac02d78a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Jodas?= Date: Tue, 16 Dec 2025 16:35:43 +0100 Subject: [PATCH 10/14] Fix docker load command in cache restore action Remove incorrect 'to' argument that was causing docker load to fail --- .github/actions/docker-image-cache-restore/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/docker-image-cache-restore/action.yml b/.github/actions/docker-image-cache-restore/action.yml index 0cdff8c..b035938 100644 --- a/.github/actions/docker-image-cache-restore/action.yml +++ b/.github/actions/docker-image-cache-restore/action.yml @@ -19,4 +19,4 @@ runs: path: /tmp - name: Load Docker image shell: bash - run: docker load --input /tmp/docker-custom-python-${{ inputs.key }} to ${{ inputs.key }}.tar + run: docker load --input /tmp/docker-custom-python-${{ inputs.key }}.tar From cdf3a3e0c9c9d570233203943ea49d65712f3131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Jodas?= Date: Tue, 16 Dec 2025 17:03:12 +0100 Subject: [PATCH 11/14] Fix legacy ENV format in Python 3.8 and 3.10 Dockerfiles Change from 'ENV key value' to 'ENV key=value' format to remove Docker build warnings --- python-3.10/Dockerfile | 12 ++++++------ python-3.8/Dockerfile | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/python-3.10/Dockerfile b/python-3.10/Dockerfile index 2c29023..995b8d2 100644 --- a/python-3.10/Dockerfile +++ b/python-3.10/Dockerfile @@ -1,5 +1,5 @@ FROM python:3.10.10-buster -ENV PYTHONIOENCODING utf-8 +ENV PYTHONIOENCODING=utf-8 WORKDIR /home @@ -23,14 +23,14 @@ RUN sed -i 's/deb.debian.org/archive.debian.org/g' /etc/apt/sources.list \ graphviz \ && rm -rf /var/lib/apt/lists/* -ENV PATH $PATH:/opt/mssql-tools/bin +ENV PATH="$PATH:/opt/mssql-tools/bin" -ENV VIRTUAL_ENV /home/default +ENV VIRTUAL_ENV=/home/default # From https://jdk.java.net/13/, stolen from https://github.com/docker-library/openjdk/blob/master/8/jdk/Dockerfile#L22 -ENV JAVA_HOME /usr/local/openjdk +ENV JAVA_HOME=/usr/local/openjdk -ENV PATH $JAVA_HOME/bin:$PATH +ENV PATH="$JAVA_HOME/bin:$PATH" RUN wget https://download.java.net/java/GA/jdk13.0.2/d4173c853231432d94f001e99d882ca7/8/GPL/openjdk-13.0.2_linux-x64_bin.tar.gz \ && mkdir $JAVA_HOME \ @@ -124,7 +124,7 @@ RUN mkdir $VIRTUAL_ENV \ && chmod a+rwx -R $VIRTUAL_ENV # Import matplotlib the first time to build the font cache. -ENV XDG_CACHE_HOME /tmp/ +ENV XDG_CACHE_HOME=/tmp/ RUN . $VIRTUAL_ENV/bin/activate \ && MPLBACKEND=Agg python -c "import matplotlib.pyplot" \ diff --git a/python-3.8/Dockerfile b/python-3.8/Dockerfile index f6b514f..b0533c9 100644 --- a/python-3.8/Dockerfile +++ b/python-3.8/Dockerfile @@ -1,5 +1,5 @@ FROM python:3.8-bullseye -ENV PYTHONIOENCODING utf-8 +ENV PYTHONIOENCODING=utf-8 WORKDIR /home @@ -21,13 +21,13 @@ RUN curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - \ graphviz \ && rm -rf /var/lib/apt/lists/* -ENV PATH $PATH:/opt/mssql-tools/bin +ENV PATH="$PATH:/opt/mssql-tools/bin" ENV VIRTUAL_ENV=/home/default # From https://jdk.java.net/13/, stolen from https://github.com/docker-library/openjdk/blob/master/8/jdk/Dockerfile#L22 -ENV JAVA_HOME /usr/local/openjdk -ENV PATH $JAVA_HOME/bin:$PATH +ENV JAVA_HOME=/usr/local/openjdk +ENV PATH="$JAVA_HOME/bin:$PATH" RUN wget https://download.java.net/java/GA/jdk13.0.2/d4173c853231432d94f001e99d882ca7/8/GPL/openjdk-13.0.2_linux-x64_bin.tar.gz \ && mkdir $JAVA_HOME \ && tar xv --file openjdk-13*_bin.tar.gz --directory "$JAVA_HOME" --no-same-owner --strip-components 1 \ @@ -121,7 +121,7 @@ RUN . $VIRTUAL_ENV/bin/activate \ && chmod a+rwx -R /home/default # Import matplotlib the first time to build the font cache. -ENV XDG_CACHE_HOME /tmp/ +ENV XDG_CACHE_HOME=/tmp/ RUN . $VIRTUAL_ENV/bin/activate \ && MPLBACKEND=Agg python -c "import matplotlib.pyplot" \ From d336b5b1fed07078cd1eb3dd4ee2293ed0851af2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Jodas?= Date: Tue, 16 Dec 2025 17:05:39 +0100 Subject: [PATCH 12/14] Pin mlflow and snowflake-connector-python to compatible versions in Python 3.10 - Pin mlflow<2.11 to ensure compatibility with cryptography<41 - Pin snowflake-connector-python<3.7 to ensure compatibility with cryptography<41 and pyopenssl==23.1.1 - Re-enable pip3 check to validate dependencies --- python-3.10/Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/python-3.10/Dockerfile b/python-3.10/Dockerfile index 995b8d2..4bff9cd 100644 --- a/python-3.10/Dockerfile +++ b/python-3.10/Dockerfile @@ -69,7 +69,7 @@ RUN mkdir $VIRTUAL_ENV \ lineage-bundle \ logger-bundle \ matplotlib \ - mlflow \ + 'mlflow<2.11' \ nltk \ nose \ numba \ @@ -98,7 +98,7 @@ RUN mkdir $VIRTUAL_ENV \ scipy \ seaborn \ simpleeval \ - snowflake-connector-python[pandas] \ + 'snowflake-connector-python[pandas]<3.7' \ sqlalchemy\ statsmodels \ sympy \ @@ -128,4 +128,5 @@ ENV XDG_CACHE_HOME=/tmp/ RUN . $VIRTUAL_ENV/bin/activate \ && MPLBACKEND=Agg python -c "import matplotlib.pyplot" \ + && pip3 check \ && pip3 freeze From 75afc0dba47f6c79d4bd5e0e264533c00127172a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Jodas?= Date: Tue, 16 Dec 2025 18:50:20 +0100 Subject: [PATCH 13/14] Pin cffi<2.0.0 to resolve snowflake-connector-python dependency conflict --- python-3.10/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/python-3.10/Dockerfile b/python-3.10/Dockerfile index 4bff9cd..25b639f 100644 --- a/python-3.10/Dockerfile +++ b/python-3.10/Dockerfile @@ -114,6 +114,7 @@ RUN mkdir $VIRTUAL_ENV \ && pip3 install --no-cache-dir --upgrade --force-reinstall \ git+https://github.com/keboola/sapi-python-client.git@0.4.0 \ keboola.component \ + 'cffi<2.0.0' \ charset-normalizer\<3 \ cryptography\<41 \ pytz\<2023 \ From 1e73d95e3f08c24ae379ed5528dae0c6c32fc20c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Jodas?= <12143866+ondrajodas@users.noreply.github.com> Date: Thu, 8 Jan 2026 09:43:48 +0100 Subject: [PATCH 14/14] Update .github/actions/docker-push/action.yml Co-authored-by: Erik Zigo --- .github/actions/docker-push/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/docker-push/action.yml b/.github/actions/docker-push/action.yml index ffe0bbc..b6a2496 100644 --- a/.github/actions/docker-push/action.yml +++ b/.github/actions/docker-push/action.yml @@ -2,8 +2,8 @@ name: Push to Registries description: Push Docker image to multiple registries inputs: imageCacheKey: - description: The image cache key (unused, kept for compatibility) - required: false + description: The image cache key (Used to construct artifact name) + required: true sourceImage: description: The source image name required: true