Build & Upload Docker Image workflow (mietzen/porkbun-ddns)
The Build & Upload Docker Image workflow from mietzen/porkbun-ddns, explained and optimized by Latchkey.
CI health: C - fair
Point runs-on at Latchkey and get run de-duplication, job timeouts, SHA-pinned actions, self-healing for flaky steps, and up to 58% lower cost, applied automatically.
What it does
This is the Build & Upload Docker Image workflow from the mietzen/porkbun-ddns repository, a real project running GitHub Actions. It is shown here with attribution under its MIT license.
Below, Latchkey shows a faster, safer version produced by its optimization engine.
The workflow
name: Build & Upload Docker Image
on:
pull_request:
release:
types: [published]
jobs:
Setup:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.set-matrix.outputs.version }}
build_matrix: ${{ steps.set-matrix.outputs.build_matrix }}
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Setup Matrix-Jobs
id: set-matrix
env:
VERSION: ${{ github.ref_name }}
GITHUB_EVENT_NAME: ${{ github.event_name }}
run: |
echo "Run triggered by: ${GITHUB_EVENT_NAME}"
if grep -c -E '^v[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}$' <<< ${VERSION}; then
VERSION=$(sed 's/^.\{1\}//g' <<< ${VERSION})
else
if [[ ${GITHUB_EVENT_NAME} != 'pull_request' ]]; then
echo "This branch shouldn't be build: ${VERSION}"
exit 1
else
VERSION='0.0.0'
fi
fi
echo "version=$(echo ${VERSION})" >> $GITHUB_OUTPUT
echo "build_matrix=$(cat .github/platforms.json)" >> $GITHUB_OUTPUT
Build:
runs-on: ubuntu-latest
needs: Setup
strategy:
fail-fast: false
matrix:
${{ insert }}: ${{ fromJson(needs.Setup.outputs.build_matrix) }}
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Install Perquisites
run: |
curl -L https://omnitruck.cinc.sh/install.sh | sudo bash -s -- -P cinc-auditor -v 5
- name: Build
env:
DOCKER_USER: ${{ vars.DOCKER_HUB_USERNAME || 'user' }}
BUILD_NR: ${{ github.run_number }}
PLATFORM: ${{ matrix.platforms }}
VERSION: ${{ needs.Setup.outputs.version }}
run: |
ARCH=$(echo "${PLATFORM}" | awk -F "/" '{print $2$3}')
echo ""
echo "============================================================="
echo "Building: ${DOCKER_USER}/porkbun-ddns:${VERSION}-${ARCH}-${BUILD_NR}"
echo "============================================================="
echo ""
docker buildx build \
--tag "${DOCKER_USER}/porkbun-ddns:${VERSION}-${ARCH}-${BUILD_NR}" \
--platform ${PLATFORM} \
--build-arg PORKBUN_DDNS_VERSION=${VERSION} \
--load \
-f ./Docker/Dockerfile .
- name: Test
env:
DOCKER_USER: ${{ vars.DOCKER_HUB_USERNAME || 'user' }}
BUILD_NR: ${{ github.run_number }}
PLATFORM: ${{ matrix.platforms }}
VERSION: ${{ needs.Setup.outputs.version }}
working-directory: './Docker'
run: |
set -a
ARCH=$(echo "${PLATFORM}" | awk -F "/" '{print $2$3}')
echo ""
echo "============================================================="
echo "Testing: ${DOCKER_USER}/porkbun-ddns:${VERSION}-${ARCH}-${BUILD_NR}"
echo "============================================================="
echo ""
exec ./test/test.sh
echo ""
- name: Login to Docker Hub
if: github.event_name == 'release'
uses: docker/login-action@v4
with:
username: ${{ vars.DOCKER_HUB_USERNAME || 'user' }}
password: ${{ secrets.DOCKER_HUB_DEPLOY_KEY }}
- name: Push Images
if: github.event_name == 'release'
env:
DOCKER_USER: ${{ vars.DOCKER_HUB_USERNAME || 'user' }}
BUILD_NR: ${{ github.run_number }}
PLATFORM: ${{ matrix.platforms }}
VERSION: ${{ needs.Setup.outputs.version }}
run: |
ARCH=$(echo "${PLATFORM}" | awk -F "/" '{print $2$3}')
echo ""
echo "============================================================="
echo "Pushing: ${DOCKER_USER}/porkbun-ddns:${VERSION}-${ARCH}-${BUILD_NR}"
echo "============================================================="
echo ""
docker tag "${DOCKER_USER}/porkbun-ddns:${VERSION}-${ARCH}-${BUILD_NR}" "${DOCKER_USER}/porkbun-ddns:${VERSION}-${ARCH}"
docker tag "${DOCKER_USER}/porkbun-ddns:${VERSION}-${ARCH}-${BUILD_NR}" "${DOCKER_USER}/porkbun-ddns:${ARCH}"
docker push "${DOCKER_USER}/porkbun-ddns:${VERSION}-${ARCH}-${BUILD_NR}"
docker push "${DOCKER_USER}/porkbun-ddns:${VERSION}-${ARCH}"
docker push "${DOCKER_USER}/porkbun-ddns:${ARCH}"
Shared-Manifest:
if: github.event_name == 'release'
runs-on: ubuntu-latest
needs:
- Build
- Setup
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Login to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ vars.DOCKER_HUB_USERNAME || 'user' }}
password: ${{ secrets.DOCKER_HUB_DEPLOY_KEY }}
- name: Create and push shared manifest
env:
DOCKER_USER: ${{ vars.DOCKER_HUB_USERNAME || 'user' }}
BUILD_NR: ${{ github.run_number }}
VERSION: ${{ needs.Setup.outputs.version }}
run: |
echo ""
echo "============================================================="
echo "Pushing shared manifest: ${DOCKER_USER}/porkbun-ddns:${VERSION}"
echo "============================================================="
echo ""
echo "#!/bin/bash" > push-shared-tags.sh
echo -n "docker manifest create ${DOCKER_USER}/porkbun-ddns:${VERSION}" >> push-shared-tags.sh
while read -r PLATFORM; do
ARCH=$(echo "${PLATFORM}" | awk -F "/" '{print $2$3}')
echo -n " ${DOCKER_USER}/porkbun-ddns:${VERSION}-${ARCH}-${BUILD_NR}" >> push-shared-tags.sh
done <<< "$(cat .github/platforms.json | jq -r '.platforms | join("\n")')"
echo "" >> push-shared-tags.sh
echo -n "docker manifest create ${DOCKER_USER}/porkbun-ddns:latest" >> push-shared-tags.sh
while read -r PLATFORM; do
ARCH=$(echo "${PLATFORM}" | awk -F "/" '{print $2$3}')
echo -n " ${DOCKER_USER}/porkbun-ddns:${VERSION}-${ARCH}-${BUILD_NR}" >> push-shared-tags.sh
done <<< "$(cat .github/platforms.json | jq -r '.platforms | join("\n")')"
echo "" >> push-shared-tags.sh
echo "docker manifest push ${DOCKER_USER}/porkbun-ddns:${VERSION}" >> push-shared-tags.sh
echo "docker manifest push ${DOCKER_USER}/porkbun-ddns:latest" >> push-shared-tags.sh
cat push-shared-tags.sh
chmod +x ./push-shared-tags.sh
./push-shared-tags.sh
Check-Build:
if: ${{ always() }}
runs-on: ubuntu-latest
needs:
- Build
- Shared-Manifest
steps:
- run: |
result="${{ needs.Build.result }}"
if [[ $result == "success" || $result == "skipped" ]]; then
exit 0
else
exit 1
fi
The same workflow, on Latchkey
Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.
name: Build & Upload Docker Image on: pull_request: release: types: [published] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: Setup: timeout-minutes: 30 runs-on: latchkey-small outputs: version: ${{ steps.set-matrix.outputs.version }} build_matrix: ${{ steps.set-matrix.outputs.build_matrix }} steps: - name: Checkout uses: actions/checkout@v7 with: fetch-depth: 0 - name: Setup Matrix-Jobs id: set-matrix env: VERSION: ${{ github.ref_name }} GITHUB_EVENT_NAME: ${{ github.event_name }} run: | echo "Run triggered by: ${GITHUB_EVENT_NAME}" if grep -c -E '^v[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}$' <<< ${VERSION}; then VERSION=$(sed 's/^.\{1\}//g' <<< ${VERSION}) else if [[ ${GITHUB_EVENT_NAME} != 'pull_request' ]]; then echo "This branch shouldn't be build: ${VERSION}" exit 1 else VERSION='0.0.0' fi fi echo "version=$(echo ${VERSION})" >> $GITHUB_OUTPUT echo "build_matrix=$(cat .github/platforms.json)" >> $GITHUB_OUTPUT Build: timeout-minutes: 30 runs-on: latchkey-small needs: Setup strategy: fail-fast: false matrix: ${{ insert }}: ${{ fromJson(needs.Setup.outputs.build_matrix) }} steps: - name: Checkout uses: actions/checkout@v7 - name: Set up QEMU uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - name: Install Perquisites run: | curl -L https://omnitruck.cinc.sh/install.sh | sudo bash -s -- -P cinc-auditor -v 5 - name: Build env: DOCKER_USER: ${{ vars.DOCKER_HUB_USERNAME || 'user' }} BUILD_NR: ${{ github.run_number }} PLATFORM: ${{ matrix.platforms }} VERSION: ${{ needs.Setup.outputs.version }} run: | ARCH=$(echo "${PLATFORM}" | awk -F "/" '{print $2$3}') echo "" echo "=============================================================" echo "Building: ${DOCKER_USER}/porkbun-ddns:${VERSION}-${ARCH}-${BUILD_NR}" echo "=============================================================" echo "" docker buildx build \ --tag "${DOCKER_USER}/porkbun-ddns:${VERSION}-${ARCH}-${BUILD_NR}" \ --platform ${PLATFORM} \ --build-arg PORKBUN_DDNS_VERSION=${VERSION} \ --load \ -f ./Docker/Dockerfile . - name: Test env: DOCKER_USER: ${{ vars.DOCKER_HUB_USERNAME || 'user' }} BUILD_NR: ${{ github.run_number }} PLATFORM: ${{ matrix.platforms }} VERSION: ${{ needs.Setup.outputs.version }} working-directory: './Docker' run: | set -a ARCH=$(echo "${PLATFORM}" | awk -F "/" '{print $2$3}') echo "" echo "=============================================================" echo "Testing: ${DOCKER_USER}/porkbun-ddns:${VERSION}-${ARCH}-${BUILD_NR}" echo "=============================================================" echo "" exec ./test/test.sh echo "" - name: Login to Docker Hub if: github.event_name == 'release' uses: docker/login-action@v4 with: username: ${{ vars.DOCKER_HUB_USERNAME || 'user' }} password: ${{ secrets.DOCKER_HUB_DEPLOY_KEY }} - name: Push Images if: github.event_name == 'release' env: DOCKER_USER: ${{ vars.DOCKER_HUB_USERNAME || 'user' }} BUILD_NR: ${{ github.run_number }} PLATFORM: ${{ matrix.platforms }} VERSION: ${{ needs.Setup.outputs.version }} run: | ARCH=$(echo "${PLATFORM}" | awk -F "/" '{print $2$3}') echo "" echo "=============================================================" echo "Pushing: ${DOCKER_USER}/porkbun-ddns:${VERSION}-${ARCH}-${BUILD_NR}" echo "=============================================================" echo "" docker tag "${DOCKER_USER}/porkbun-ddns:${VERSION}-${ARCH}-${BUILD_NR}" "${DOCKER_USER}/porkbun-ddns:${VERSION}-${ARCH}" docker tag "${DOCKER_USER}/porkbun-ddns:${VERSION}-${ARCH}-${BUILD_NR}" "${DOCKER_USER}/porkbun-ddns:${ARCH}" docker push "${DOCKER_USER}/porkbun-ddns:${VERSION}-${ARCH}-${BUILD_NR}" docker push "${DOCKER_USER}/porkbun-ddns:${VERSION}-${ARCH}" docker push "${DOCKER_USER}/porkbun-ddns:${ARCH}" Shared-Manifest: timeout-minutes: 30 if: github.event_name == 'release' runs-on: latchkey-small needs: - Build - Setup steps: - name: Checkout uses: actions/checkout@v7 - name: Login to Docker Hub uses: docker/login-action@v4 with: username: ${{ vars.DOCKER_HUB_USERNAME || 'user' }} password: ${{ secrets.DOCKER_HUB_DEPLOY_KEY }} - name: Create and push shared manifest env: DOCKER_USER: ${{ vars.DOCKER_HUB_USERNAME || 'user' }} BUILD_NR: ${{ github.run_number }} VERSION: ${{ needs.Setup.outputs.version }} run: | echo "" echo "=============================================================" echo "Pushing shared manifest: ${DOCKER_USER}/porkbun-ddns:${VERSION}" echo "=============================================================" echo "" echo "#!/bin/bash" > push-shared-tags.sh echo -n "docker manifest create ${DOCKER_USER}/porkbun-ddns:${VERSION}" >> push-shared-tags.sh while read -r PLATFORM; do ARCH=$(echo "${PLATFORM}" | awk -F "/" '{print $2$3}') echo -n " ${DOCKER_USER}/porkbun-ddns:${VERSION}-${ARCH}-${BUILD_NR}" >> push-shared-tags.sh done <<< "$(cat .github/platforms.json | jq -r '.platforms | join("\n")')" echo "" >> push-shared-tags.sh echo -n "docker manifest create ${DOCKER_USER}/porkbun-ddns:latest" >> push-shared-tags.sh while read -r PLATFORM; do ARCH=$(echo "${PLATFORM}" | awk -F "/" '{print $2$3}') echo -n " ${DOCKER_USER}/porkbun-ddns:${VERSION}-${ARCH}-${BUILD_NR}" >> push-shared-tags.sh done <<< "$(cat .github/platforms.json | jq -r '.platforms | join("\n")')" echo "" >> push-shared-tags.sh echo "docker manifest push ${DOCKER_USER}/porkbun-ddns:${VERSION}" >> push-shared-tags.sh echo "docker manifest push ${DOCKER_USER}/porkbun-ddns:latest" >> push-shared-tags.sh cat push-shared-tags.sh chmod +x ./push-shared-tags.sh ./push-shared-tags.sh Check-Build: timeout-minutes: 30 if: ${{ always() }} runs-on: latchkey-small needs: - Build - Shared-Manifest steps: - run: | result="${{ needs.Build.result }}" if [[ $result == "success" || $result == "skipped" ]]; then exit 0 else exit 1 fi
What changed
- Run on Latchkey managed runners with one line (
runs-on), which apply the fixes below automatically and self-heal transient failures. This example useslatchkey-small; pick the runner size that fits the job. - Cancel superseded runs when a branch or PR gets a newer push.
- Add a job timeout so a hung step cannot burn hours of runner time.
3 third-party actions are referenced by a movable tag. Pin them to the commit SHA (Latchkey resolves and applies this automatically) so a repointed tag cannot change what runs.
What Latchkey heals here
This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:
- Container pulls and builds
- Network fetches
This workflow runs 4 jobs per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.