From d023002a6262f6f868181c112b8952f1064968f6 Mon Sep 17 00:00:00 2001 From: Jose Henrique Date: Sat, 17 Oct 2026 11:56:43 -0300 Subject: [PATCH] adding docker-build job --- .gitea/workflows/test-build.yaml | 29 +++++ AGENTS.md | 12 ++ README.md | 43 ++++++- docker-build/action.yaml | 213 +++++++++++++++++++++++++++++++ 4 files changed, 296 insertions(+), 1 deletion(-) create mode 100644 docker-build/action.yaml diff --git a/.gitea/workflows/test-build.yaml b/.gitea/workflows/test-build.yaml index 6cf57ca..f37586a 100644 --- a/.gitea/workflows/test-build.yaml +++ b/.gitea/workflows/test-build.yaml @@ -12,6 +12,7 @@ on: - main paths: - build-and-push/** + - docker-build/** - deploy-restart/** - kubectl-apply/** - ssh-deploy/** @@ -110,6 +111,34 @@ jobs: echo "${MANIFEST}" | grep -q "linux/arm64" || { echo "missing linux/arm64"; exit 1; } echo "self-test PASSED: multi-arch image built and pushed" + test-build-only: + name: Build-only tiny multi-arch image (${{ matrix.runner }}) + if: ${{ gitea.event_name != 'workflow_run' || gitea.event.workflow_run.conclusion == 'success' }} + runs-on: ${{ matrix.runner }} + strategy: + matrix: + runner: [runner-slim-amd64, runner-full-amd64, runner-slim-arm64, runner-full-arm64] + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Generate a minimal Dockerfile + shell: bash + run: | + mkdir -p _selftest + cat > _selftest/Dockerfile <<'EOF' + FROM alpine:3.20 + RUN echo "pipeline-actions self-test build-only OK" + CMD ["true"] + EOF + + - name: Build (no push) via composite action + uses: https://git.ivanch.me/ivanch/pipeline-actions/docker-build@main + with: + build_context: _selftest + build_dockerfile: Dockerfile + ssh_key: ${{ secrets.SSH_KEY_DOCKERBUILD }} + test-k8s: name: Test Kubernetes actions (${{ matrix.runner }}) if: ${{ gitea.event_name != 'workflow_run' || gitea.event.workflow_run.conclusion == 'success' }} diff --git a/AGENTS.md b/AGENTS.md index b3c69e4..f3c2976 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,6 +24,18 @@ Logs into the container registry, sets up SSH key for `docker-build.haven`, crea - `docker_host` (default `docker-build.haven`) — remote builder hostname - `docker_user` (default `root`) — SSH user for remote builder +### `docker-build` (`docker-build/action.yaml`) + +Same remote SSH Buildx setup as `build-and-push`, but only **builds** the multi-arch image — no push. Use it to validate a Dockerfile (e.g. PRs / non-main branches). + +**Inputs:** +- `ssh_key` (required) — SSH private key for remote builder (use secrets) +- `registry_password` (default empty) — when set, logs into the registry first (only needed for private base images) +- `registry_host` (default `git.ivanch.me`) / `registry_username` (default `ivanch`) — used only with `registry_password` +- `image` (default empty) — optional image ref without tag, only used to tag the build result +- `image_tag` (default `latest`) — comma-separated tag(s), applied only when `image` is set +- `platforms`, `build_context`, `build_dockerfile`, `build_args`, `docker_host`, `docker_user` — same as `build-and-push` + ### `deploy-restart` (`deploy-restart/action.yaml`) Validates `KUBE_CONFIG`, installs kubectl, and performs `rollout restart` of a Kubernetes Deployment. diff --git a/README.md b/README.md index a4f8058..0ab151c 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,47 @@ jobs: --- +### `docker-build` + +Builds a multi-architecture Docker image on the same remote Buildx builder as `build-and-push`, but **does not push** it. Useful for validating a Dockerfile (e.g. on PRs / non-main branches). + +```yaml +jobs: + build: + name: Build Image (no push) + runs-on: runner-slim-amd64 + steps: + - uses: actions/checkout@v4 + - uses: https://git.ivanch.me/ivanch/pipeline-actions/docker-build@main + with: + ssh_key: ${{ secrets.SSH_KEY_DOCKERBUILD }} + # optional: + # registry_password: ${{ secrets.REGISTRY_PASSWORD }} # only for private base images + # image: git.ivanch.me/ivanch/my-app + # image_tag: latest + # platforms: linux/amd64,linux/arm64 + # build_context: . + # build_dockerfile: Dockerfile + # build_args: "" +``` + +| Input | Required | Default | Description | +|---|---|---|---| +| `ssh_key` | ✅ | — | SSH private key for the remote builder (use secrets) | +| `registry_password` | ❌ | _(empty)_ | When set, logs into the registry first (needed only for private base images) | +| `registry_host` | ❌ | `git.ivanch.me` | Container registry hostname | +| `registry_username` | ❌ | `ivanch` | Registry username | +| `image` | ❌ | _(empty)_ | Image reference without tag; only used to tag the build result | +| `image_tag` | ❌ | `latest` | Tag(s) applied when `image` is set (comma-separated) | +| `platforms` | ❌ | `linux/amd64,linux/arm64` | Target build platforms | +| `build_context` | ❌ | `.` | Docker build context path | +| `build_dockerfile` | ❌ | `Dockerfile` | Dockerfile path relative to context | +| `build_args` | ❌ | _(empty)_ | Build arguments, one `KEY=VALUE` per line | +| `docker_host` | ❌ | `docker-build.haven` | Remote builder hostname | +| `docker_user` | ❌ | `root` | SSH user for remote builder | + +--- + ### `deploy-restart` Validates a kubeconfig, installs kubectl, and performs a `rollout restart` of a Kubernetes Deployment. @@ -158,7 +199,7 @@ Consumer repositories need these secrets configured in Gitea: | Secret | Used by | Purpose | |---|---|---| | `REGISTRY_PASSWORD` | `build-and-push` | Container registry password/token | -| `SSH_KEY_DOCKERBUILD` | `build-and-push` | SSH private key for `docker-build.haven` | +| `SSH_KEY_DOCKERBUILD` | `build-and-push, docker-build` | SSH private key for `docker-build.haven` | | `KUBE_CONFIG` | `deploy-restart, kubectl-apply` | Full kubeconfig YAML for cluster access | | `DEPLOY_HOST` | `ssh-deploy` | Remote server hostname or IP | | `DEPLOY_USERNAME` | `ssh-deploy` | SSH username for the remote server | diff --git a/docker-build/action.yaml b/docker-build/action.yaml new file mode 100644 index 0000000..a8e4008 --- /dev/null +++ b/docker-build/action.yaml @@ -0,0 +1,213 @@ +name: Build Multi-Arch Image (no push) +description: Set up a remote Docker Buildx builder over SSH and build a multi-arch image without pushing it (e.g. to validate a Dockerfile in PRs/CI). + +inputs: + ssh_key: + description: SSH private key for the remote Docker builder host (pass via secrets) + required: true + image: + description: Optional full image reference (host/path/name) without tag, used only to tag the build result + required: false + default: "" + image_tag: + description: Tag(s) to apply when `image` is set. Comma-separated for multiple tags (e.g. "latest,abc1234"). + required: false + default: latest + registry_host: + description: Hostname of the container registry (only used when registry_password is set) + required: false + default: git.ivanch.me + registry_username: + description: Username for the container registry (only used when registry_password is set) + required: false + default: ivanch + registry_password: + description: Optional registry password/token. Only needed if the build pulls private base images (pass via secrets). + required: false + default: "" + platforms: + description: Comma-separated list of target platforms + required: false + default: linux/amd64,linux/arm64 + build_context: + description: Docker build context path + required: false + default: . + build_dockerfile: + description: Path to the Dockerfile (relative to context) + required: false + default: Dockerfile + build_args: + description: >- + Optional build arguments, one per line in KEY=VALUE form. Each line is + appended as a `--build-arg KEY=VALUE` to the build command. + required: false + default: "" + docker_host: + description: Hostname of the remote Docker Buildx builder + required: false + default: docker-build.haven + docker_user: + description: SSH user for the remote Docker builder + required: false + default: root + +runs: + using: composite + steps: + - name: Ensure Docker CLI and Buildx are installed + shell: bash + run: | + if ! command -v docker >/dev/null 2>&1; then + echo "Docker CLI not found, attempting to install it with Buildx..." + if command -v apk >/dev/null 2>&1; then + apk add --no-cache docker-cli docker-cli-buildx + elif command -v apt-get >/dev/null 2>&1; then + export DEBIAN_FRONTEND=noninteractive + apt-get update -y && apt-get install -y docker.io docker-buildx + elif command -v dnf >/dev/null 2>&1; then + dnf install -y docker-cli docker-buildx + elif command -v yum >/dev/null 2>&1; then + yum install -y docker docker-buildx + else + echo "::error::Docker CLI is missing and no supported package manager (apk, apt-get, dnf, yum) was found." + exit 1 + fi + fi + + if ! docker buildx version >/dev/null 2>&1; then + echo "::error::Docker Buildx is required but is not installed." + exit 1 + fi + + - name: Log in to Container Registry (optional) + if: ${{ inputs.registry_password != '' }} + shell: bash + env: + REGISTRY_HOST: ${{ inputs.registry_host }} + REGISTRY_USERNAME: ${{ inputs.registry_username }} + REGISTRY_PASSWORD: ${{ inputs.registry_password }} + run: | + echo "${REGISTRY_PASSWORD}" \ + | docker login "${REGISTRY_HOST}" \ + -u "${REGISTRY_USERNAME}" \ + --password-stdin + + - name: Set up SSH key for remote builder + shell: bash + env: + SSH_KEY: ${{ inputs.ssh_key }} + BUILDER_HOST: ${{ inputs.docker_host }} + BUILDER_USER: ${{ inputs.docker_user }} + run: | + # Defensive: strip any stray CR (CRLF checkout via core.autocrlf). + SSH_KEY=$(printf '%s' "${SSH_KEY}" | tr -d '\r') + BUILDER_HOST=$(printf '%s' "${BUILDER_HOST}" | tr -d '\r') + BUILDER_USER=$(printf '%s' "${BUILDER_USER}" | tr -d '\r') + + mkdir -p ~/.ssh + echo "${SSH_KEY}" > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + ssh-keyscan "${BUILDER_HOST}" >> ~/.ssh/known_hosts + + # Fail fast if the runner can't reach / auth to the remote daemon. + echo "== probing ssh://***@${BUILDER_HOST} ==" + if timeout 25 ssh \ + -o BatchMode=yes \ + -o ConnectTimeout=10 \ + -o ServerAliveInterval=5 \ + -o ServerAliveCountMax=2 \ + -o StrictHostKeyChecking=accept-new \ + -i ~/.ssh/id_ed25519 \ + "${BUILDER_USER}@${BUILDER_HOST}" \ + 'docker version --format "server={{.Server.Version}}"'; then + echo "SSH probe OK" + else + rc=$? + echo "SSH probe FAILED (exit ${rc})" + if [ "${rc}" = "124" ]; then + echo "-> timed out: TCP opens but SSH/daemon does not answer." + echo "-> Check: dockerd running on ${BUILDER_HOST}? firewall path? MTU?" + fi + exit "${rc}" + fi + + - name: Set up remote Docker context and buildx + shell: bash + env: + BUILDER_HOST: ${{ inputs.docker_host }} + BUILDER_USER: ${{ inputs.docker_user }} + PLATFORMS: ${{ inputs.platforms }} + run: | + # Same approach as build-and-push: plain docker context over SSH driving + # the remote daemon's built-in buildkit. Never set DOCKER_HOST. + unset DOCKER_HOST || true + BUILDER_HOST=$(printf '%s' "${BUILDER_HOST}" | tr -d '\r') + BUILDER_USER=$(printf '%s' "${BUILDER_USER}" | tr -d '\r') + + docker context rm -f remote-builder 2>/dev/null || true + docker context create remote-builder \ + --docker "host=ssh://${BUILDER_USER}@${BUILDER_HOST}" + + # Ensure qemu/binfmt is registered on the remote for cross-arch builds. + if echo "${PLATFORMS}" | grep -q 'arm'; then + echo "== ensuring qemu/binfmt on remote for cross-arch ==" + docker --context remote-builder run --privileged --rm \ + tonistiigi/binfmt --install all >/dev/null 2>&1 || \ + echo "WARN: binfmt install returned non-zero (may already be present)" + fi + + docker --context remote-builder buildx inspect >/dev/null + + - name: Generate tags list + id: gentags + shell: bash + env: + IMAGE: ${{ inputs.image }} + IMAGE_TAGS: ${{ inputs.image_tag }} + run: | + TAGS_ARGS="" + if [ -n "${IMAGE}" ]; then + IFS=',' read -ra ADDR <<< "${IMAGE_TAGS}" + for tag in "${ADDR[@]}"; do + trimmed_tag=$(echo "${tag}" | xargs) + if [ -n "${trimmed_tag}" ]; then + TAGS_ARGS="${TAGS_ARGS} -t ${IMAGE}:${trimmed_tag}" + fi + done + fi + echo "tag_args=${TAGS_ARGS}" >> "$GITHUB_OUTPUT" + + - name: Generate build args + id: genbuildargs + shell: bash + env: + BUILD_ARGS_INPUT: ${{ inputs.build_args }} + run: | + ARGS="" + while IFS= read -r line; do + trimmed=$(printf '%s' "${line}" | tr -d '\r' | xargs) + if [ -n "${trimmed}" ]; then + ARGS="${ARGS} --build-arg ${trimmed}" + fi + done <<< "${BUILD_ARGS_INPUT}" + echo "build_args=${ARGS}" >> "$GITHUB_OUTPUT" + + - name: Build Multi-Arch Image (no push) + shell: bash + env: + BUILD_CONTEXT: ${{ inputs.build_context }} + BUILD_DOCKERFILE: ${{ inputs.build_dockerfile }} + PLATFORMS: ${{ inputs.platforms }} + TAG_ARGS: ${{ steps.gentags.outputs.tag_args }} + BUILD_ARGS: ${{ steps.genbuildargs.outputs.build_args }} + run: | + unset DOCKER_HOST || true + # Build only: no --push / --load. The result stays in the remote + # builder's cache, so a failing Dockerfile still fails the step. + docker --context remote-builder buildx build \ + --platform "${PLATFORMS}" \ + --file "${BUILD_CONTEXT}/${BUILD_DOCKERFILE}" \ + ${BUILD_ARGS} \ + ${TAG_ARGS} \ + "${BUILD_CONTEXT}"