The runners share one host Docker daemon across replicas; the legacy builder corrupts layers under that concurrency. buildx builds in an isolated buildkitd container. buildx is installed as a static binary, keeping the no-marketplace-actions property. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
139 lines
6.2 KiB
YAML
139 lines
6.2 KiB
YAML
name: Build & publish Docker image
|
|
description: >-
|
|
Build a Docker image with BuildKit (buildx) and push it to this Forgejo
|
|
instance's container registry. Reimplements docker/login-action +
|
|
docker/metadata-action + docker/setup-buildx-action + docker/build-push-action
|
|
in plain shell using the docker CLI and a statically-installed buildx plugin —
|
|
no marketplace actions are fetched or run.
|
|
|
|
BuildKit (not the legacy builder) is required: the runners share one host
|
|
Docker daemon across replicas, and the legacy builder corrupts layers under
|
|
that concurrency ("failed to get layer ... layer does not exist"). buildx
|
|
builds in an isolated buildkitd container, which is robust against it.
|
|
|
|
inputs:
|
|
context:
|
|
description: Docker build context.
|
|
default: "."
|
|
dockerfile:
|
|
description: Path to the Dockerfile (relative to the workspace). Empty = <context>/Dockerfile.
|
|
default: ""
|
|
image-suffix:
|
|
description: Appended to the derived image name <host>/<owner>/<repo>, e.g. "-server".
|
|
default: ""
|
|
build-args:
|
|
description: Newline-separated KEY=VALUE pairs passed as --build-arg.
|
|
default: ""
|
|
push:
|
|
description: Whether to log in and push ("true"/"false"). Set false for pull-request builds.
|
|
default: "true"
|
|
token:
|
|
description: Registry password/token (e.g. secrets.FORGEJO_PACKAGE_TOKEN).
|
|
required: true
|
|
registry-host:
|
|
description: Override the registry host. Defaults to github.server_url (vars.CONTAINER_REGISTRY_HOST is honoured if set in the env).
|
|
default: ""
|
|
|
|
runs:
|
|
using: composite
|
|
steps:
|
|
- name: Build and publish
|
|
shell: bash
|
|
env:
|
|
INPUT_CONTEXT: ${{ inputs.context }}
|
|
INPUT_DOCKERFILE: ${{ inputs.dockerfile }}
|
|
INPUT_IMAGE_SUFFIX: ${{ inputs.image-suffix }}
|
|
INPUT_BUILD_ARGS: ${{ inputs.build-args }}
|
|
INPUT_PUSH: ${{ inputs.push }}
|
|
INPUT_TOKEN: ${{ inputs.token }}
|
|
INPUT_REGISTRY_HOST: ${{ inputs.registry-host }}
|
|
REPO: ${{ github.repository }}
|
|
OWNER: ${{ github.repository_owner }}
|
|
SERVER_URL: ${{ github.server_url }}
|
|
SHA: ${{ github.sha }}
|
|
REF: ${{ github.ref }}
|
|
REF_TYPE: ${{ github.ref_type }}
|
|
REF_NAME: ${{ github.ref_name }}
|
|
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
# --- Ensure the docker CLI exists (idempotent; the node:* runner image may lack it) ---
|
|
if ! command -v docker >/dev/null 2>&1; then
|
|
ARCH="$(uname -m)"
|
|
curl -fsSL --retry 3 --retry-connrefused \
|
|
"https://download.docker.com/linux/static/stable/${ARCH}/docker-27.5.1.tgz" \
|
|
| tar xz --strip-components=1 -C /usr/local/bin docker/docker
|
|
fi
|
|
docker --version
|
|
|
|
# --- Ensure the buildx plugin exists (static binary, not a marketplace action) ---
|
|
if ! docker buildx version >/dev/null 2>&1; then
|
|
case "$(uname -m)" in
|
|
x86_64) BX_ARCH=amd64 ;;
|
|
aarch64|arm64) BX_ARCH=arm64 ;;
|
|
*) BX_ARCH="$(uname -m)" ;;
|
|
esac
|
|
mkdir -p /usr/local/lib/docker/cli-plugins
|
|
curl -fsSL --retry 3 --retry-connrefused \
|
|
"https://github.com/docker/buildx/releases/download/v0.19.3/buildx-v0.19.3.linux-${BX_ARCH}" \
|
|
-o /usr/local/lib/docker/cli-plugins/docker-buildx
|
|
chmod +x /usr/local/lib/docker/cli-plugins/docker-buildx
|
|
fi
|
|
docker buildx version
|
|
|
|
# A host-networked buildkit builder so it can reach the registry on 127.0.0.1
|
|
# (buildkit treats loopback registries as insecure/HTTP automatically). A fixed
|
|
# name lets concurrent jobs share one builder and its cache; the inspect||create
|
|
# guard tolerates the create race between simultaneous jobs.
|
|
docker buildx inspect ci >/dev/null 2>&1 \
|
|
|| docker buildx create --name ci --driver docker-container --driver-opt network=host >/dev/null 2>&1 \
|
|
|| true
|
|
docker buildx use ci
|
|
|
|
# --- Resolve the registry host (canonical form of the block copy-pasted across repos) ---
|
|
host="${INPUT_REGISTRY_HOST:-${CONTAINER_REGISTRY_HOST:-$SERVER_URL}}"
|
|
host="${host#https://}"; host="${host#http://}"; host="${host%/}"
|
|
# The job's docker daemon reaches the registry via the host-published port, not the
|
|
# internal service name; this is the same remap the old inline scripts performed.
|
|
[ "$host" = "forgejo:3000" ] && host="127.0.0.1:13000"
|
|
repo="$(printf '%s' "$REPO" | tr '[:upper:]' '[:lower:]')"
|
|
image="${host}/${repo}${INPUT_IMAGE_SUFFIX}"
|
|
echo "Target image: ${image}"
|
|
|
|
# --- Compute tags from the git ref (replaces docker/metadata-action) ---
|
|
tags=( -t "${image}:sha-$(printf '%s' "$SHA" | cut -c1-12)" )
|
|
if [ "$REF" = "refs/heads/${DEFAULT_BRANCH}" ]; then
|
|
tags+=( -t "${image}:latest" )
|
|
fi
|
|
if [ "$REF_TYPE" = "tag" ]; then
|
|
tags+=( -t "${image}:${REF_NAME}" )
|
|
if printf '%s' "$REF_NAME" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
|
|
v="${REF_NAME#v}"
|
|
tags+=( -t "${image}:${v}" -t "${image}:${v%.*}" -t "${image}:${v%%.*}" )
|
|
fi
|
|
fi
|
|
|
|
# --- Assemble extra build flags ---
|
|
extra=()
|
|
[ -n "${INPUT_DOCKERFILE}" ] && extra+=( -f "${INPUT_DOCKERFILE}" )
|
|
while IFS= read -r a; do
|
|
[ -n "$a" ] && extra+=( --build-arg "$a" )
|
|
done <<< "${INPUT_BUILD_ARGS}"
|
|
|
|
# --- Log in before building so buildx can push directly (skipped on PRs) ---
|
|
if [ "${INPUT_PUSH}" = "true" ]; then
|
|
printf '%s' "${INPUT_TOKEN}" | docker login "$host" -u "$OWNER" --password-stdin
|
|
output=( --push )
|
|
else
|
|
# Pull-request builds validate the Dockerfile without producing/pushing an image.
|
|
output=()
|
|
fi
|
|
|
|
# --- Build (and push when requested) with BuildKit ---
|
|
docker buildx build "${extra[@]}" "${output[@]}" \
|
|
--label "org.opencontainers.image.source=${SERVER_URL}/${REPO}" \
|
|
--label "org.opencontainers.image.revision=${SHA}" \
|
|
--label "org.opencontainers.image.created=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
|
|
"${tags[@]}" \
|
|
"${INPUT_CONTEXT}"
|