name: Harbor Build + Push + Deploy (Compose) "on": workflow_call: inputs: # Build inputs harbor_registry: description: "Harbor registry hostname (example: harbor.hcmc.online)" required: false type: string default: harbor.hcmc.online harbor_project: description: "Harbor project name (default: ci)" required: false type: string default: ci image_repo: description: "Optional override for repo name under project (default: GitHub repository name)" required: false type: string default: "" context: description: "Docker build context directory" required: false type: string default: . dockerfile: description: "Dockerfile path (relative to repository root)" required: false type: string default: Dockerfile platforms: description: "Build platforms for buildx (default: linux/amd64)" required: false type: string default: linux/amd64 tag: description: "Optional tag to push (default: sha-)" required: false type: string default: "" # Deploy inputs deploy_runner: description: "Single runner label for deploy (example: self-hosted)" required: true type: string deploy_runner_json: description: "Optional JSON array of runner labels (example: [\"self-hosted\",\"prod\"]). If set, overrides deploy_runner." required: false type: string default: "" compose_workdir: description: "Directory containing compose files on the target runner" required: false type: string default: . compose_args: description: "Arguments after `docker compose`" required: false type: string default: up -d --pull always --remove-orphans secrets: HARBOR_PUSH_USERNAME: description: "Harbor robot/user with push rights to harbor_project" required: true HARBOR_PUSH_PASSWORD: description: "Token/password for HARBOR_PUSH_USERNAME" required: true HARBOR_PULL_USERNAME: description: "Harbor robot/user with pull rights to the deployed repositories" required: true HARBOR_PULL_PASSWORD: description: "Token/password for HARBOR_PULL_USERNAME" required: true outputs: image_repo: description: "Image repository (no tag/digest), e.g. harbor.hcmc.online/ci/myapp" value: ${{ jobs.build.outputs.image_repo }} image_digest: description: "Content digest, e.g. sha256:..." value: ${{ jobs.build.outputs.image_digest }} image_ref: description: "Immutable image ref, e.g. harbor.hcmc.online/ci/myapp@sha256:..." value: ${{ jobs.build.outputs.image_ref }} image_tag: description: "Tag pushed alongside digest (for humans), e.g. sha-abc123" value: ${{ jobs.build.outputs.image_tag }} jobs: build: runs-on: ubuntu-latest outputs: image_repo: ${{ steps.meta.outputs.image_repo }} image_digest: ${{ steps.build.outputs.digest }} image_ref: ${{ steps.meta.outputs.image_repo }}@${{ steps.build.outputs.digest }} image_tag: ${{ steps.meta.outputs.image_tag }} steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Buildx uses: docker/setup-buildx-action@v3 - name: Compute Image Repo + Tag id: meta shell: bash run: | set -euo pipefail repo_name="${{ inputs.image_repo }}" if [[ -z "${repo_name}" ]]; then repo_name="${{ github.event.repository.name }}" fi tag="${{ inputs.tag }}" if [[ -z "${tag}" ]]; then short_sha="$(echo "${{ github.sha }}" | cut -c1-12)" tag="sha-${short_sha}" fi image_repo="${{ inputs.harbor_registry }}/${{ inputs.harbor_project }}/${repo_name}" echo "image_repo=${image_repo}" >> "${GITHUB_OUTPUT}" echo "image_tag=${tag}" >> "${GITHUB_OUTPUT}" - name: Docker Login (Harbor Push) uses: docker/login-action@v3 with: registry: ${{ inputs.harbor_registry }} username: ${{ secrets.HARBOR_PUSH_USERNAME }} password: ${{ secrets.HARBOR_PUSH_PASSWORD }} - name: Build + Push (Harbor) id: build uses: docker/build-push-action@v6 with: context: ${{ inputs.context }} file: ${{ inputs.dockerfile }} platforms: ${{ inputs.platforms }} push: true tags: | ${{ steps.meta.outputs.image_repo }}:${{ steps.meta.outputs.image_tag }} deploy: needs: build runs-on: ${{ inputs.deploy_runner_json != '' && fromJSON(inputs.deploy_runner_json) || inputs.deploy_runner }} steps: - name: Checkout uses: actions/checkout@v4 - name: Docker Login (Harbor Pull) uses: docker/login-action@v3 with: registry: ${{ inputs.harbor_registry }} username: ${{ secrets.HARBOR_PULL_USERNAME }} password: ${{ secrets.HARBOR_PULL_PASSWORD }} - name: Show deploy image if: runner.os != 'Windows' shell: bash run: | echo "Deploying: ${{ needs.build.outputs.image_repo }}@${{ needs.build.outputs.image_digest }}" - name: Show deploy image (Windows) if: runner.os == 'Windows' shell: pwsh run: | Write-Output "Deploying: ${{ needs.build.outputs.image_repo }}@${{ needs.build.outputs.image_digest }}" - name: Compose Up (Linux/macOS) if: runner.os != 'Windows' shell: bash working-directory: ${{ inputs.compose_workdir }} env: DOCKER_IMAGE: ${{ needs.build.outputs.image_repo }}@${{ needs.build.outputs.image_digest }} run: | set -euo pipefail docker compose ${{ inputs.compose_args }} - name: Compose Up (Windows) if: runner.os == 'Windows' shell: pwsh working-directory: ${{ inputs.compose_workdir }} env: DOCKER_IMAGE: ${{ needs.build.outputs.image_repo }}@${{ needs.build.outputs.image_digest }} run: | docker compose ${{ inputs.compose_args }}