Authored-By: Codex (GPT-6) <noreply@openai.com>
6.0 KiB
action/image-build
Composite Gitea Action that builds a container image with buildx and optionally
runs a smoke test. Does not push — pair with action/image-push to publish.
Splitting build from push lets a PR workflow run image-build without push or
deploy side effects while main runs the full build → push → deploy chain. A
PR build that pulls a private base image still needs a registry token limited to
the read:package capability; public-base builds need no token. The action logs
in as ci-bot, so the token must be issued to that account.
Private Go module builds
tools/private-modules/ owns the stdlib-only offline generator and verifier for
native Go file-proxy bundles. Consumers export exact committed source bytes so
their first check needs no Git, network, module dependency, or credential.
Changes belong here; consumers must not edit their generated copies.
From this checkout, export a reviewed full commit ID into a service checkout:
python3 tools/export-private-modules.py /path/to/service --revision FULL_COMMIT_ID
python3 tools/export-private-modules.py /path/to/service --revision FULL_COMMIT_ID --check
The export includes source revision/path/digest metadata and a SHA256 lock.
Service Make and Docker builds run sha256sum -c tools/private-modules.sha256
before GO111MODULE=off GOTOOLCHAIN=local GOFLAGS= go run tools/private-modules.go.
Reviewers can reproduce --check from that pinned source commit. The local hash
check detects accidental drift; Git review establishes the trusted source pin.
Populate selected private versions through the configured authenticated Go
client once, then run the generated command with -write to copy unchanged
.info, .mod, and .zip cache artifacts into third_party/go-proxy. The
generator itself enforces offline resolution and a local toolchain. It preserves
the complete original archives, including any licenses and notices. Normal Go
downloads still validate their content against the consumer's go.sum.
The generated proxy directory has an exact build-only nested go.mod marker.
Native Go module packaging excludes that directory from provider releases, so
downstream consumers never recursively bundle another provider's build inputs.
The checker requires the marker, and a native local-Git archive test proves the
exclusion without permitting Git network protocols.
Consumer resolution uses GONOPROXY=none, GONOSUMDB=code.fritzlab.net,
GOFLAGS=-mod=readonly, GOTOOLCHAIN=local, and
GOPROXY=file:///absolute/service/third_party/go-proxy,https://proxy.golang.org.
Run the checker before and after downloads; its exact go.mod/go.sum fingerprint
rejects stale dependency closures. Public modules stay on the public proxy.
Registry publication remains a separate authenticated operation.
Usage
- uses: actions/checkout@v4
- uses: https://code.fritzlab.net/action/image-build@v1
with:
image: code.fritzlab.net/fritzlab/chrony
smoke-test: docker run --rm --entrypoint /usr/sbin/chronyd $IMAGE -v
The image is built and tagged as <image>:<github.run_number> in the runner's
local Docker daemon. Subsequent steps (e.g. action/image-push) can reference
the same tag.
Private bases in PRs
Pass token only when the PR head is trusted, and limit ci-bot package access
to the private base images that the build requires. Never expose an
organization-wide package reader to a contributor-controlled Dockerfile: it can
pull and disclose any package that the account can read.
with:
token: ${{ secrets.PACKAGE_READ_TOKEN }} # caller-chosen secret name
The token must be issued to ci-bot with read:package capability. Tokens from
other accounts fail because the action's registry username is fixed. Omit the
input for public bases.
Inputs
| Name | Required | Default | Description |
|---|---|---|---|
image |
yes | — | Full image name without tag (e.g. code.fritzlab.net/fritzlab/chrony). |
context |
no | . |
Docker build context. |
dockerfile |
no | Dockerfile (in context) |
Path to the Dockerfile, relative to the context (or absolute under $GITHUB_WORKSPACE). Use for monorepos where the build context is the repo root but the Dockerfile lives in a subdir, e.g. dockerfile: api/Dockerfile. |
build-args |
no | — | Multiline KEY=VALUE build args. Visible in docker history — never put secrets here. |
secrets |
no | — | Multiline id=VALUE BuildKit secrets (--secret). For tokens the build needs (e.g. a ci-bot token to go mod download a private module) that must not leak into layers. Reference with RUN --mount=type=secret,id=<id>. |
smoke-test |
no | — | Shell command run after build. $IMAGE is set to <image>:<run_number>. Non-zero exit fails the action. |
token |
no | — | ci-bot access token with read:package capability. Required to pull a private base image; omit for public bases. |
Outputs
| Name | Description |
|---|---|
tag |
Numeric tag assigned (= github.run_number). |
Private module contract validation
The private-module contract workflow runs its Go race tests in the digest-pinned
standard Go compiler image in tests/private-modules.Dockerfile. The Fritzlab
runner doesn't provide GCC. Only tools/private-modules enters that build context;
the test runs with networking and module downloads disabled. The public compiler
image must already be cached or obtainable through the existing image pull path.
This check doesn't publish an image or change runner configuration.
python3 -m unittest discover -s tests
docker build --network none --file tests/private-modules.Dockerfile tools/private-modules
Smoke test patterns
Override entrypoint for a binary that expects no args:
smoke-test: docker run --rm --entrypoint /usr/sbin/chronyd $IMAGE -v
Run a help command that returns non-zero:
smoke-test: docker run --rm $IMAGE --help || true
Multiple checks chained:
smoke-test: |
docker run --rm $IMAGE --version
docker run --rm --entrypoint /bin/sh $IMAGE -c 'test -x /usr/local/bin/myapp'