Add kaniko-build: shared action to build and push container images
All checks were successful
Self-test kaniko-build / build-without-push (push) Successful in 7s
All checks were successful
Self-test kaniko-build / build-without-push (push) Successful in 7s
Lets any repository on git.scotty-world.com build a Docker/OCI image from its checked-out code and push it to the registry it chooses (scotty-git's own container registry, Artifact Registry, Docker Hub, GHCR, ...). Kaniko instead of docker build: the runner is a shared, long-lived VM, so giving workflows the Docker daemon would mean root on that machine and image layers left on its disk. Kaniko builds inside the step's own container: no root, no privileged mode, nothing left behind. Kaniko v1.23.2 is pinned by digest. The action stores no credentials; each repository passes its own secret. A self-test workflow builds a small image (without pushing) on every change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
21e961d4f0
6 changed files with 293 additions and 0 deletions
23
.forgejo/workflows/selftest.yml
Normal file
23
.forgejo/workflows/selftest.yml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
name: Self-test kaniko-build
|
||||
|
||||
# Builds a tiny image with the action in this repo, without pushing, so a broken change to
|
||||
# kaniko-build/ shows up here before any user's workflow picks it up.
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-without-push:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build the test image (no push)
|
||||
uses: ./kaniko-build
|
||||
with:
|
||||
context: kaniko-build/test
|
||||
dockerfile: kaniko-build/test/Dockerfile
|
||||
push: "false"
|
||||
build_args: |
|
||||
GREETING=selftest
|
||||
106
README.md
Normal file
106
README.md
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
# scotty-git actions
|
||||
|
||||
Shared Forgejo Actions that any repository on **git.scotty-world.com** can use.
|
||||
|
||||
| Action | What it does |
|
||||
|---|---|
|
||||
| [`kaniko-build`](#kaniko-build) | Build a container image from your repository and push it to a registry |
|
||||
|
||||
Actions on this server are referenced by their **full URL**. Short names such as `actions/checkout`
|
||||
still resolve to `data.forgejo.org`, as usual.
|
||||
|
||||
---
|
||||
|
||||
## kaniko-build
|
||||
|
||||
Builds a Docker/OCI image from the repository your job checked out, using
|
||||
[Kaniko](https://github.com/GoogleContainerTools/kaniko), and pushes it to the registry you choose.
|
||||
|
||||
**Why Kaniko and not `docker build`:** the scotty-git runner is a shared, long-lived machine. Giving
|
||||
workflows the Docker daemon would hand every workflow root access to that machine and leave image
|
||||
layers on its disk. Kaniko builds inside the step's own container instead: no root, no privileged
|
||||
mode, nothing left behind. The build happens the same way as on a GitHub-hosted runner; only the
|
||||
engine differs.
|
||||
|
||||
### Quick start: push to scotty-git's own container registry
|
||||
|
||||
No cloud account needed. Create a token at **Settings → Applications** with the
|
||||
`write:package` scope, and save it in your repository as the secret `REGISTRY_TOKEN`
|
||||
(**Settings → Actions → Secrets**). Then:
|
||||
|
||||
```yaml
|
||||
# .forgejo/workflows/image.yml
|
||||
name: Build image
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
image:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: https://git.scotty-world.com/scotty-git/actions/kaniko-build@v1
|
||||
with:
|
||||
destination: git.scotty-world.com/${{ github.repository }}:${{ github.sha }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||
```
|
||||
|
||||
The image appears under your repository's **Packages** tab.
|
||||
|
||||
### Google Artifact Registry
|
||||
|
||||
Use a service account that can push to **your** repository only (role `Artifact Registry Writer`
|
||||
granted on that one repository, nothing project-wide). Save its JSON key as a secret in **your**
|
||||
repository, never at organisation or instance level:
|
||||
|
||||
```yaml
|
||||
- uses: https://git.scotty-world.com/scotty-git/actions/kaniko-build@v1
|
||||
with:
|
||||
destination: asia-south1-docker.pkg.dev/my-project/my-repo/my-app:${{ github.sha }}
|
||||
gcp_key_json: ${{ secrets.GCP_AR_PUSH_KEY }}
|
||||
cache_repo: asia-south1-docker.pkg.dev/my-project/my-repo/my-app-cache
|
||||
```
|
||||
|
||||
### Docker Hub, GitHub Container Registry, others
|
||||
|
||||
```yaml
|
||||
with:
|
||||
destination: docker.io/my-user/my-app:1.2.3
|
||||
username: my-user
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
```
|
||||
|
||||
### Inputs
|
||||
|
||||
| Input | Default | Description |
|
||||
|---|---|---|
|
||||
| `destination` | | Image reference(s), separated by commas or new lines. Required unless `push` is `false`. |
|
||||
| `context` | `.` | Build context, relative to the repository root. |
|
||||
| `dockerfile` | `Dockerfile` | Dockerfile path, relative to the repository root. |
|
||||
| `push` | `true` | `false` builds without pushing, e.g. to check that a PR still builds. |
|
||||
| `registry` | host of the first destination | Registry the credentials are for. |
|
||||
| `username` / `password` | | Registry login. Always pass the password from a secret. |
|
||||
| `gcp_key_json` | | Google service-account key JSON, instead of username/password. |
|
||||
| `cache_repo` | | Registry repository for the layer cache. Speeds up rebuilds; stored in the registry, not on the runner. |
|
||||
| `build_args` | | One `KEY=VALUE` per line. |
|
||||
| `extra_args` | | Extra Kaniko flags, space-separated (advanced). |
|
||||
|
||||
**Output:** `digest`, the sha256 digest of the pushed image.
|
||||
|
||||
### Good to know
|
||||
|
||||
- **Check out first.** The action builds whatever `actions/checkout` put in the workspace.
|
||||
- **Credentials stay yours.** This action stores no credentials. Each repository passes its own
|
||||
secret, and the login exists only inside the step's container for the length of the build.
|
||||
- **The runner is shared.** Image builds take a few minutes on a small machine and other jobs
|
||||
queue behind them. Use `cache_repo` to keep rebuilds short.
|
||||
- **The Kaniko version is pinned** (v1.23.2, by digest) in `kaniko-build/Dockerfile`, so a build
|
||||
tool update can never change your builds without a reviewed change here.
|
||||
- **Versions:** use `@v1` to get compatible fixes. Breaking changes get a new major tag.
|
||||
|
||||
### Changing this action
|
||||
|
||||
Open a pull request. The `Self-test kaniko-build` workflow builds a small test image with your
|
||||
change before it can affect anyone's workflow.
|
||||
9
kaniko-build/Dockerfile
Normal file
9
kaniko-build/Dockerfile
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Kaniko v1.23.2 (the last release of the original Google project), pinned by digest so the
|
||||
# build tool can never change underneath a workflow. The -debug variant includes a busybox
|
||||
# shell, which the entrypoint needs for the credential setup.
|
||||
FROM gcr.io/kaniko-project/executor@sha256:c3109d5926a997b100c4343944e06c6b30a6804b2f9abe0994d3de6ef92b028e
|
||||
|
||||
# Kept under /kaniko: Kaniko never overwrites that directory while it unpacks the base image.
|
||||
COPY entrypoint.sh /kaniko/scotty-git-entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/busybox/sh", "/kaniko/scotty-git-entrypoint.sh"]
|
||||
68
kaniko-build/action.yml
Normal file
68
kaniko-build/action.yml
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
name: Kaniko build and push
|
||||
description: >-
|
||||
Build a container image from the checked-out repository with Kaniko and push it to any
|
||||
registry. Needs no Docker daemon, no root on the runner and no privileged mode, and leaves
|
||||
nothing behind on the runner's disk.
|
||||
author: scotty-git
|
||||
|
||||
inputs:
|
||||
destination:
|
||||
description: >-
|
||||
Image reference(s) to push, separated by commas or new lines,
|
||||
e.g. "git.scotty-world.com/my-org/my-app:1.2.3". Required unless push is "false".
|
||||
required: false
|
||||
default: ""
|
||||
context:
|
||||
description: Build context, relative to the repository root.
|
||||
required: false
|
||||
default: "."
|
||||
dockerfile:
|
||||
description: Path to the Dockerfile, relative to the repository root.
|
||||
required: false
|
||||
default: "Dockerfile"
|
||||
push:
|
||||
description: '"false" builds the image but does not push it (useful for PR checks).'
|
||||
required: false
|
||||
default: "true"
|
||||
registry:
|
||||
description: >-
|
||||
Registry host the credentials below are for, e.g. "git.scotty-world.com",
|
||||
"asia-south1-docker.pkg.dev", "ghcr.io". Defaults to the host of the first destination.
|
||||
required: false
|
||||
default: ""
|
||||
username:
|
||||
description: Registry username. Use with password.
|
||||
required: false
|
||||
default: ""
|
||||
password:
|
||||
description: Registry password or token. Pass it from a secret, never as plain text.
|
||||
required: false
|
||||
default: ""
|
||||
gcp_key_json:
|
||||
description: >-
|
||||
Google service-account key JSON for Artifact Registry, instead of username/password.
|
||||
Pass it from a secret.
|
||||
required: false
|
||||
default: ""
|
||||
cache_repo:
|
||||
description: >-
|
||||
Registry repository for Kaniko's layer cache, e.g. "git.scotty-world.com/my-org/my-app-cache".
|
||||
Makes rebuilds fast without storing anything on the runner. Leave empty for no cache.
|
||||
required: false
|
||||
default: ""
|
||||
build_args:
|
||||
description: Build arguments, one KEY=VALUE per line.
|
||||
required: false
|
||||
default: ""
|
||||
extra_args:
|
||||
description: Extra Kaniko executor flags, space-separated (advanced).
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
outputs:
|
||||
digest:
|
||||
description: The sha256 digest of the pushed image (empty when push is "false").
|
||||
|
||||
runs:
|
||||
using: docker
|
||||
image: Dockerfile
|
||||
82
kaniko-build/entrypoint.sh
Executable file
82
kaniko-build/entrypoint.sh
Executable file
|
|
@ -0,0 +1,82 @@
|
|||
#!/busybox/sh
|
||||
# scotty-git shared action: build and push a container image with Kaniko.
|
||||
# Inputs arrive as INPUT_<NAME> environment variables (see action.yml).
|
||||
# Never prints credentials. The registry login only exists inside this short-lived container.
|
||||
set -eu
|
||||
|
||||
fail() { printf '::error::%s\n' "$*"; exit 1; }
|
||||
|
||||
DESTINATION=${INPUT_DESTINATION:-}
|
||||
CONTEXT=${INPUT_CONTEXT:-.}
|
||||
DOCKERFILE=${INPUT_DOCKERFILE:-Dockerfile}
|
||||
PUSH=${INPUT_PUSH:-true}
|
||||
REGISTRY=${INPUT_REGISTRY:-}
|
||||
USERNAME=${INPUT_USERNAME:-}
|
||||
PASSWORD=${INPUT_PASSWORD:-}
|
||||
GCP_KEY_JSON=${INPUT_GCP_KEY_JSON:-}
|
||||
CACHE_REPO=${INPUT_CACHE_REPO:-}
|
||||
BUILD_ARGS=${INPUT_BUILD_ARGS:-}
|
||||
EXTRA_ARGS=${INPUT_EXTRA_ARGS:-}
|
||||
|
||||
# ---- Source: the repository the job already checked out ----
|
||||
WS=${GITHUB_WORKSPACE:-}
|
||||
if [ -z "$WS" ] || [ ! -d "$WS" ]; then fail "No checked-out workspace found. Add 'uses: actions/checkout@v4' before this step."; fi
|
||||
if [ ! -d "$WS/$CONTEXT" ]; then fail "Build context '$CONTEXT' does not exist in the repository."; fi
|
||||
if [ ! -f "$WS/$DOCKERFILE" ]; then fail "Dockerfile '$DOCKERFILE' not found in the repository."; fi
|
||||
|
||||
# ---- Destinations ----
|
||||
DESTS=$(printf '%s' "$DESTINATION" | tr ',\n' ' ')
|
||||
if [ "$PUSH" = "true" ] && [ -z "$(printf '%s' "$DESTS" | tr -d ' ')" ]; then
|
||||
fail "'destination' is required when push is true."
|
||||
fi
|
||||
|
||||
# ---- Registry login (written only inside this container) ----
|
||||
mkdir -p /kaniko/.docker
|
||||
if [ -z "$REGISTRY" ]; then
|
||||
for d in $DESTS; do REGISTRY=${d%%/*}; break; done
|
||||
if [ -z "$REGISTRY" ] && [ -n "$CACHE_REPO" ]; then REGISTRY=${CACHE_REPO%%/*}; fi
|
||||
fi
|
||||
AUTH=""
|
||||
if [ -n "$GCP_KEY_JSON" ]; then
|
||||
AUTH=$(printf '_json_key:%s' "$GCP_KEY_JSON" | base64 | tr -d '\n')
|
||||
elif [ -n "$USERNAME" ] && [ -n "$PASSWORD" ]; then
|
||||
AUTH=$(printf '%s:%s' "$USERNAME" "$PASSWORD" | base64 | tr -d '\n')
|
||||
elif [ -n "$USERNAME" ] || [ -n "$PASSWORD" ]; then
|
||||
fail "Set both 'username' and 'password', or neither."
|
||||
fi
|
||||
if [ -n "$AUTH" ]; then
|
||||
if [ -z "$REGISTRY" ]; then fail "Could not tell which registry the credentials are for; set 'registry'."; fi
|
||||
printf '{"auths":{"%s":{"auth":"%s"}}}\n' "$REGISTRY" "$AUTH" > /kaniko/.docker/config.json
|
||||
echo "Logged in to $REGISTRY for this build."
|
||||
fi
|
||||
|
||||
# ---- Executor flags ----
|
||||
set -- --context "dir://$WS/$CONTEXT" --dockerfile "$WS/$DOCKERFILE" --cleanup
|
||||
if [ "$PUSH" = "true" ]; then
|
||||
for d in $DESTS; do set -- "$@" --destination "$d"; done
|
||||
set -- "$@" --digest-file /kaniko/digest
|
||||
else
|
||||
set -- "$@" --no-push
|
||||
for d in $DESTS; do set -- "$@" --destination "$d"; done
|
||||
fi
|
||||
if [ -n "$CACHE_REPO" ]; then set -- "$@" --cache=true --cache-repo "$CACHE_REPO"; fi
|
||||
OLDIFS=$IFS
|
||||
IFS='
|
||||
'
|
||||
for a in $BUILD_ARGS; do
|
||||
if [ -n "$a" ]; then set -- "$@" --build-arg "$a"; fi
|
||||
done
|
||||
IFS=$OLDIFS
|
||||
# shellcheck disable=SC2086 # extra_args is deliberately split into separate flags
|
||||
if [ -n "$EXTRA_ARGS" ]; then set -- "$@" $EXTRA_ARGS; fi
|
||||
|
||||
echo "Building $DOCKERFILE (context: $CONTEXT)$( [ "$PUSH" = "true" ] && echo " -> $DESTS" || echo ", not pushing")"
|
||||
/kaniko/executor "$@"
|
||||
|
||||
# ---- Output ----
|
||||
if [ "$PUSH" = "true" ] && [ -f /kaniko/digest ]; then
|
||||
DIGEST=$(cat /kaniko/digest)
|
||||
echo "Pushed: $DIGEST"
|
||||
if [ -n "${GITHUB_OUTPUT:-}" ]; then echo "digest=$DIGEST" >> "$GITHUB_OUTPUT"; fi
|
||||
fi
|
||||
exit 0
|
||||
5
kaniko-build/test/Dockerfile
Normal file
5
kaniko-build/test/Dockerfile
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Tiny image used by the self-test workflow to prove kaniko-build works on the runner.
|
||||
FROM alpine:3.20
|
||||
ARG GREETING=hello
|
||||
RUN echo "$GREETING from kaniko-build" > /built-by-kaniko.txt
|
||||
CMD ["cat", "/built-by-kaniko.txt"]
|
||||
Loading…
Add table
Reference in a new issue