Skip to content

chore: migrate to new docker publish workflow #2107

@Rocket-Quack

Description

@Rocket-Quack

🔄 Migration Required: Update Docker Publish Workflow Reference

This repository is currently using the legacy Docker publish workflow, which has been replaced with a new standardized workflow.


📦 What changed

A new workflow is now used for publishing Docker images:

The previous workflow is still functional for now but is considered legacy and will be removed once all dependent repositories have been migrated.


⚠️ Required change

Please update all references in this repository from the old workflow to the new workflow.


🧪 Backward compatibility

  • The old workflow remains temporarily supported
  • No immediate breaking changes
  • Migration can be performed safely at any time

🚀 Action required

  • Identify references to the old workflow in this repository
  • Replace them with the new workflow
  • Verify Docker publishing still works as expected

🗓️ Deprecation note

The old workflow will be removed once all repositories have been migrated.

Migration progress is tracked here:
frappe/frappe_docker#1884


Example for new Workflow

# This workflow uses frappe_docker only as shared build logic.
# The app repo itself owns publishing.
#
# Replace these placeholders before using it:
# - <app_name>
# - <ghcr_image_name>
# - <dockerhub_image>
#
# This reference is best suited for single-app repositories.

name: Build and Publish Docker Images

on:
  release:
    types: [released]
  workflow_dispatch:
    inputs:
      app_ref:
        description: Git tag or branch to build
        required: false
        type: string
      image_tag:
        description: Override the published image tag
        required: false
        type: string
      frappe_ref:
        description: Override the base Frappe image tag, for example <frappe_ref_example>
        required: false
        type: string
      push:
        description: Push the built images to registries
        required: true
        default: true
        type: boolean

permissions:
  contents: read
  packages: write

concurrency:
  group: docker-image-${{ github.event.release.tag_name || github.event.inputs.app_ref || github.ref_name }}
  cancel-in-progress: false

jobs:
  prepare:
    name: Resolve Build Metadata
    runs-on: ubuntu-latest
    outputs:
      app_ref: ${{ steps.meta.outputs.app_ref }}
      app_repo: ${{ steps.meta.outputs.app_repo }}
      frappe_ref: ${{ steps.meta.outputs.frappe_ref }}
      ghcr_image: ${{ steps.meta.outputs.ghcr_image }}
      dockerhub_image: ${{ steps.meta.outputs.dockerhub_image }}
      image_tags: ${{ steps.meta.outputs.image_tags }}
      push: ${{ steps.meta.outputs.push }}
    steps:
      - name: Resolve metadata
        id: meta
        shell: bash
        env:
          EVENT_NAME: ${{ github.event_name }}
          RELEASE_TAG: ${{ github.event.release.tag_name }}
          INPUT_APP_REF: ${{ github.event.inputs.app_ref }}
          INPUT_IMAGE_TAG: ${{ github.event.inputs.image_tag }}
          INPUT_FRAPPE_REF: ${{ github.event.inputs.frappe_ref }}
          INPUT_PUSH: ${{ github.event.inputs.push }}
          REPOSITORY: ${{ github.repository }}
          REPOSITORY_OWNER: ${{ github.repository_owner }}

          # This comes from the repository variable "DOCKERHUB_IMAGE".
          # If it is not set, Docker Hub publishing is skipped.
          DOCKERHUB_IMAGE: ${{ vars.DOCKERHUB_IMAGE }}
        run: |
          set -euo pipefail

          # Resolve the app repo ref to build.
          # Priority:
          # 1. manual workflow input
          # 2. release tag
          # 3. current ref name
          app_ref="${INPUT_APP_REF:-}"
          if [ -z "$app_ref" ]; then
            if [ "$EVENT_NAME" = "release" ] && [ -n "${RELEASE_TAG:-}" ]; then
              app_ref="$RELEASE_TAG"
            else
              app_ref="${GITHUB_REF_NAME}"
            fi
          fi

          # Release events always push.
          # Manual runs may choose push=false.
          push="${INPUT_PUSH:-true}"
          if [ "$EVENT_NAME" = "release" ]; then
            push="true"
          fi

          # Resolve the Frappe base image line.
          #
          # This logic assumes release tags can be mapped to the correct
          # Frappe base image line.
          #
          # Example:
          # <release_tag_example> -> <frappe_ref_example>
          #
          # If that assumption is not valid for a given app repo,
          # this logic must be adjusted.
          frappe_ref="${INPUT_FRAPPE_REF:-}"
          major=""
          if [ -z "$frappe_ref" ]; then
            if [[ "$app_ref" =~ ^v([0-9]+)([.].*)?$ ]]; then
              major="${BASH_REMATCH[1]}"
              frappe_ref="version-${major}"
            elif [[ "$app_ref" =~ ^version-([0-9]+)$ ]]; then
              major="${BASH_REMATCH[1]}"
              frappe_ref="$app_ref"
            elif [ "$app_ref" = "develop" ]; then
              frappe_ref="develop"
            else
              echo "::error::Unable to derive frappe_ref from app_ref '$app_ref'. Provide the workflow_dispatch input 'frappe_ref' or adjust the derivation logic."
              exit 1
            fi
          elif [[ "$frappe_ref" =~ ^version-([0-9]+)$ ]]; then
            major="${BASH_REMATCH[1]}"
          fi

          # Resolve published image tags.
          #
          # Example:
          # v16.15.0 -> v16.15.0, v16, version-16
          # develop -> develop, latest
          #
          # Adjust this logic if the app repository uses a different tagging scheme.
          declare -a tags
          if [ -n "${INPUT_IMAGE_TAG:-}" ]; then
            tags=("$INPUT_IMAGE_TAG")
          elif [[ "$app_ref" =~ ^v([0-9]+)([.].*)?$ ]]; then
            major="${BASH_REMATCH[1]}"
            tags=("$app_ref" "v${major}" "version-${major}")
          elif [ "$app_ref" = "develop" ]; then
            tags=("develop" "latest")
          else
            tags=("$app_ref")
          fi

          # Deduplicate tags and serialize them to JSON so the publish jobs
          # can consume them via a matrix.
          declare -A seen
          image_tags="["
          for tag in "${tags[@]}"; do
            if [ -z "$tag" ] || [ -n "${seen[$tag]+x}" ]; then
              continue
            fi
            seen[$tag]=1
            image_tags="${image_tags}\"${tag}\","
          done
          image_tags="${image_tags%,}]"

          if [ "$image_tags" = "]" ]; then
            echo "::error::No image tags were resolved."
            exit 1
          fi

          # Build the GHCR image name from the current repository owner.
          # Example:
          # github.repository_owner=frappe -> ghcr.io/frappe/<ghcr_image_name>
          owner_lower="$(echo "$REPOSITORY_OWNER" | tr '[:upper:]' '[:lower:]')"

          # Docker Hub image comes from the repository variable DOCKERHUB_IMAGE.
          dockerhub_image="$(echo "${DOCKERHUB_IMAGE:-}" | tr '[:upper:]' '[:lower:]')"

          {
            echo "app_ref=$app_ref"
            echo "app_repo=https://github.com/$REPOSITORY"
            echo "frappe_ref=$frappe_ref"
            echo "ghcr_image=ghcr.io/$owner_lower/<ghcr_image_name>"
            echo "dockerhub_image=$dockerhub_image"
            echo "image_tags=$image_tags"
            echo "push=$push"
          } >> "$GITHUB_OUTPUT"

  validate-dockerhub:
    name: Validate Docker Hub Configuration
    needs: prepare
    if: ${{ needs.prepare.outputs.push == 'true' && needs.prepare.outputs.dockerhub_image != '' }}
    runs-on: ubuntu-latest
    steps:
      - name: Check required secrets
        shell: bash
        env:
          DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
          DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
        run: |
          set -euo pipefail
          test -n "${DOCKERHUB_USERNAME:-}" || { echo "::error::Missing secret DOCKERHUB_USERNAME."; exit 1; }
          test -n "${DOCKERHUB_TOKEN:-}" || { echo "::error::Missing secret DOCKERHUB_TOKEN."; exit 1; }

  publish-ghcr:
    name: Publish GHCR
    needs: prepare
    strategy:
      fail-fast: false
      matrix:
        image_tag: ${{ fromJSON(needs.prepare.outputs.image_tags) }}

    # Key migration point:
    # use the reusable app image workflow from frappe_docker
    # instead of a custom build or a curl-based dispatch flow.
    uses: frappe/frappe_docker/.github/workflows/app-build-image.yml@main
    with:
      # This must match the app name that Bench installs.
      app_name: <app_name>

      # Build source: the current app repository and ref.
      app_repo: ${{ needs.prepare.outputs.app_repo }}
      app_ref: ${{ needs.prepare.outputs.app_ref }}

      # Base Frappe image line to build on.
      frappe_ref: ${{ needs.prepare.outputs.frappe_ref }}

      # Final image target in GHCR.
      image_name: ${{ needs.prepare.outputs.ghcr_image }}
      image_tag: ${{ matrix.image_tag }}

      # Manual runs may choose not to push.
      push: ${{ needs.prepare.outputs.push == 'true' }}

      registry: ghcr.io
      platforms: linux/amd64,linux/arm64

  publish-dockerhub:
    name: Publish Docker Hub
    needs:
      - prepare
      - validate-dockerhub
    if: ${{ needs.prepare.outputs.push == 'true' && needs.prepare.outputs.dockerhub_image != '' }}
    strategy:
      fail-fast: false
      matrix:
        image_tag: ${{ fromJSON(needs.prepare.outputs.image_tags) }}
    uses: frappe/frappe_docker/.github/workflows/app-build-image.yml@main
    with:
      app_name: <app_name>
      app_repo: ${{ needs.prepare.outputs.app_repo }}
      app_ref: ${{ needs.prepare.outputs.app_ref }}
      frappe_ref: ${{ needs.prepare.outputs.frappe_ref }}
      image_name: ${{ needs.prepare.outputs.dockerhub_image }}
      image_tag: ${{ matrix.image_tag }}
      push: true
      platforms: linux/amd64,linux/arm64
    secrets:
      REGISTRY_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
      REGISTRY_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }}

🙌 Thanks

This migration helps unify and simplify our CI/CD pipeline across repositories.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions