# 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: ```sh 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`. 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 ```yaml - 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 `:` 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. ```yaml 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=`. | | `smoke-test` | no | — | Shell command run after build. `$IMAGE` is set to `:`. 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`). | ## Smoke test patterns Override entrypoint for a binary that expects no args: ```yaml smoke-test: docker run --rm --entrypoint /usr/sbin/chronyd $IMAGE -v ``` Run a help command that returns non-zero: ```yaml smoke-test: docker run --rm $IMAGE --help || true ``` Multiple checks chained: ```yaml smoke-test: | docker run --rm $IMAGE --version docker run --rm --entrypoint /bin/sh $IMAGE -c 'test -x /usr/local/bin/myapp' ```