diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml new file mode 100644 index 0000000..a712f8e --- /dev/null +++ b/.gitea/workflows/build.yaml @@ -0,0 +1,148 @@ +name: 🏗️ Build Container Image + +on: + workflow_call: + inputs: + # --- Required --- + image_name: + description: 'Image name without registry/org (e.g. ansible-act-runner)' + required: true + type: string + dockerfile_path: + description: 'Path to Dockerfile relative to repo root (e.g. docker/Dockerfile)' + required: true + type: string + context_path: + description: 'Build context directory relative to repo root (e.g. docker/)' + required: true + type: string + + # --- Optional overrides --- + registry: + description: 'Registry to push to (default: gitea.mod.home)' + required: false + type: string + default: 'gitea.mod.home' + image_org: + description: 'Registry org/namespace (default: calling repo owner)' + required: false + type: string + default: '' + extra_tag: + description: 'Additional tag besides latest and SHA (e.g. stable)' + required: false + type: string + default: '' + no_cache: + description: 'Disable build cache' + required: false + type: boolean + default: false + + secrets: + TOKEN: + required: true + # Optional — override default REGISTRY_USER/REGISTRY_PASSWORD + REGISTRY_USER_OVERRIDE: + required: false + REGISTRY_PASSWORD_OVERRIDE: + required: false + +jobs: + build: + # Runs directly on runner host (docker:host) + # Kaniko executor available via tools volume (initContainer in deployment) + runs-on: docker + + steps: + - name: 🔎 Checkout + run: | + git clone \ + --depth 1 \ + --branch "${{ gitea.ref_name }}" \ + "http://${{ secrets.TOKEN }}@gitea.mod.home/${{ gitea.repository_owner }}/${{ gitea.event.repository.name }}.git" \ + /workspace + + - name: 🏷️ Resolve Image Destination + id: meta + run: | + # Registry + REGISTRY="${{ inputs.registry }}" + + # Org: use override if provided, otherwise calling repo owner + ORG="${{ inputs.image_org }}" + if [ -z "${ORG}" ]; then + ORG="${{ gitea.repository_owner }}" + fi + + # Credentials: use override if provided, otherwise defaults + USER="${{ secrets.REGISTRY_USER_OVERRIDE }}" + if [ -z "${USER}" ]; then + USER="${{ secrets.REGISTRY_USER }}" + fi + PASS="${{ secrets.REGISTRY_PASSWORD_OVERRIDE }}" + if [ -z "${PASS}" ]; then + PASS="${{ secrets.REGISTRY_PASSWORD }}" + fi + + IMAGE="${REGISTRY}/${ORG}/${{ inputs.image_name }}" + SHORT_SHA="${{ gitea.sha }}" + SHORT_SHA="${SHORT_SHA:0:8}" + + echo "image=${IMAGE}" >> $GITHUB_OUTPUT + echo "tag_latest=${IMAGE}:latest" >> $GITHUB_OUTPUT + echo "tag_sha=${IMAGE}:${SHORT_SHA}" >> $GITHUB_OUTPUT + echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT + echo "registry=${REGISTRY}" >> $GITHUB_OUTPUT + echo "user=${USER}" >> $GITHUB_OUTPUT + echo "pass=${PASS}" >> $GITHUB_OUTPUT + + - name: 🔑 Create Kaniko Registry Config + run: | + mkdir -p /kaniko/.docker + AUTH=$(echo -n "${{ steps.meta.outputs.user }}:${{ steps.meta.outputs.pass }}" | base64) + cat > /kaniko/.docker/config.json << EOF + { + "auths": { + "${{ steps.meta.outputs.registry }}": { + "auth": "${AUTH}" + } + } + } + EOF + + - name: 🔨 Build + Push Image + run: | + # Build extra destinations + DESTINATIONS="--destination ${{ steps.meta.outputs.tag_latest }} \ + --destination ${{ steps.meta.outputs.tag_sha }}" + + if [ -n "${{ inputs.extra_tag }}" ]; then + DESTINATIONS="${DESTINATIONS} --destination ${{ steps.meta.outputs.image }}:${{ inputs.extra_tag }}" + fi + + NO_CACHE="" + if [ "${{ inputs.no_cache }}" = "true" ]; then + NO_CACHE="--no-push-cache --cache=false" + fi + + /kaniko/executor \ + --context=dir:///workspace/${{ inputs.context_path }} \ + --dockerfile=/workspace/${{ inputs.dockerfile_path }} \ + ${DESTINATIONS} \ + ${NO_CACHE} \ + --insecure \ + --skip-tls-verify \ + --compressed-caching=false + + - name: 📨 Telegram Notification + if: always() + run: | + STATUS="${{ job.status }}" + IMAGE="${{ steps.meta.outputs.tag_latest }}" + SHA="${{ steps.meta.outputs.short_sha }}" + TEXT="🏗️ Image Build: ${IMAGE}%0ASHA: ${SHA}%0AStatus: ${STATUS}" + curl -s -X POST \ + "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \ + -d "chat_id=${{ secrets.TELEGRAM_CHAT_ID }}" \ + -d "text=${TEXT}" diff --git a/README.md b/README.md index f97d627..78aaebe 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,175 @@ # image-builder +Reusable Gitea Actions workflow for building and pushing container images using +[Kaniko](https://github.com/GoogleContainerTools/kaniko). No Docker daemon +required — Kaniko runs directly on the runner host and handles both build and +push in a single step. + +--- + +## How It Works + +``` +Calling repo (e.g. ansible/ansible-runner) + → workflow_call → docker/image-builder + → Kaniko builds Dockerfile + → Pushes to gitea.mod.home//:latest + → Pushes to gitea.mod.home//: +``` + +Kaniko is available on the runner host via the `tools` EmptyDir volume, +populated by the `install-build-tools` initContainer in the act runner +deployment. + +--- + +## Usage + +### Minimal — push to Gitea Registry + +```yaml +# .gitea/workflows/build-myimage.yaml in your repo +name: 🏗️ Build My Image + +on: + push: + branches: [main] + paths: + - 'docker/Dockerfile' + workflow_dispatch: + +jobs: + build: + uses: docker/image-builder/.gitea/workflows/build.yaml@main + with: + image_name: my-image # → gitea.mod.home//my-image + dockerfile_path: docker/Dockerfile + context_path: docker/ + secrets: + TOKEN: ${{ secrets.TOKEN }} +``` + +### With extra tag + +```yaml +jobs: + build: + uses: docker/image-builder/.gitea/workflows/build.yaml@main + with: + image_name: my-image + dockerfile_path: docker/Dockerfile + context_path: docker/ + extra_tag: stable + secrets: + TOKEN: ${{ secrets.TOKEN }} +``` + +### Push to Docker Hub + +```yaml +jobs: + build: + uses: docker/image-builder/.gitea/workflows/build.yaml@main + with: + image_name: myuser/my-image + registry: registry-1.docker.io + dockerfile_path: docker/Dockerfile + context_path: docker/ + secrets: + TOKEN: ${{ secrets.TOKEN }} + REGISTRY_USER_OVERRIDE: ${{ secrets.DOCKERHUB_USER }} + REGISTRY_PASSWORD_OVERRIDE: ${{ secrets.DOCKERHUB_TOKEN }} +``` + +### Force rebuild without cache + +```yaml + with: + image_name: my-image + dockerfile_path: docker/Dockerfile + context_path: docker/ + no_cache: true +``` + +--- + +## Inputs + +| Input | Required | Default | Description | +|-------|----------|---------|-------------| +| `image_name` | ✅ | — | Image name without registry/org | +| `dockerfile_path` | ✅ | — | Path to Dockerfile relative to repo root | +| `context_path` | ✅ | — | Build context directory relative to repo root | +| `registry` | ❌ | `gitea.mod.home` | Target registry | +| `image_org` | ❌ | calling repo owner | Registry org/namespace | +| `extra_tag` | ❌ | `''` | Additional tag besides `latest` and SHA | +| `no_cache` | ❌ | `false` | Disable Kaniko build cache | + +## Secrets + +| Secret | Required | Description | +|--------|----------|-------------| +| `TOKEN` | ✅ | Gitea access token for checkout | +| `REGISTRY_USER_OVERRIDE` | ❌ | Override registry username | +| `REGISTRY_PASSWORD_OVERRIDE` | ❌ | Override registry password | + +Secrets `REGISTRY_USER` and `REGISTRY_PASSWORD` must be set at the +**`docker` org level** in Gitea. All other repos inherit them automatically. + +--- + +## Required Secrets (docker Org) + +Set these in Gitea → `docker` org → Settings → Secrets: + +| Secret | Description | +|--------|-------------| +| `REGISTRY_USER` | Gitea username for container registry login | +| `REGISTRY_PASSWORD` | Gitea access token with `package:write` scope | +| `TELEGRAM_BOT_TOKEN` | Telegram bot token for build notifications | +| `TELEGRAM_CHAT_ID` | Telegram chat ID for build notifications | + +--- + +## Runner Requirements + +The act runner deployment must have the `install-build-tools` initContainer +which copies the Kaniko executor binary into the `tools` EmptyDir volume: + +```yaml +initContainers: + - name: install-build-tools + image: gcr.io/kaniko-project/executor:latest + command: + - /bin/sh + - -c + - cp /kaniko/executor /tools/executor && chmod +x /tools/executor + volumeMounts: + - name: tools + mountPath: /tools +``` + +The runner must have the `docker` label registered: + +```yaml +# configmap.yaml +labels: + - "docker:host" +``` + +--- + +## Image Naming Convention + +| Calling repo | image_name | Result | +|-------------|------------|--------| +| `ansible/ansible-runner` | `ansible-act-runner` | `gitea.mod.home/ansible/ansible-act-runner:latest` | +| `ansible/ansible-runner` | `ubuntu-act-runner` | `gitea.mod.home/ansible/ubuntu-act-runner:latest` | +| any repo | `myimage` with `image_org: shared` | `gitea.mod.home/shared/myimage:latest` | + +--- + +## Roadmap + +- [ ] TLS for Gitea registry — remove `--insecure` flag once cert-manager CA is in place +- [ ] Multi-arch builds via Kaniko cross-compilation