diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2a2583b..b804e65 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -77,15 +77,28 @@ jobs: - name: Docker metadata id: docker_meta uses: docker/metadata-action@v6 + env: + DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index with: images: ${{ steps.image.outputs.name }} tags: | type=raw,value=${{ steps.meta.outputs.version }} labels: | org.opencontainers.image.title=${{ steps.meta.outputs.name }} + org.opencontainers.image.description=${{ steps.meta.outputs.description }} + org.opencontainers.image.version=${{ steps.meta.outputs.version }} + org.opencontainers.image.source=https://github.com/${{ github.repository }} + org.opencontainers.image.revision=${{ github.sha }} + org.opencontainers.image.documentation=https://github.com/${{ github.repository }}/tree/${{ github.sha }}/images/${{ steps.tag.outputs.tool }} + org.opencontainers.image.licenses=${{ steps.meta.outputs.license }} + annotations: | + org.opencontainers.image.title=${{ steps.meta.outputs.name }} + org.opencontainers.image.description=${{ steps.meta.outputs.description }} org.opencontainers.image.version=${{ steps.meta.outputs.version }} - org.opencontainers.image.source=https://github.com/${{ steps.meta.outputs.upstream }} + org.opencontainers.image.source=https://github.com/${{ github.repository }} org.opencontainers.image.revision=${{ github.sha }} + org.opencontainers.image.documentation=https://github.com/${{ github.repository }}/tree/${{ github.sha }}/images/${{ steps.tag.outputs.tool }} + org.opencontainers.image.licenses=${{ steps.meta.outputs.license }} - name: Build and push id: build @@ -97,6 +110,7 @@ jobs: push: true tags: ${{ steps.docker_meta.outputs.tags }} labels: ${{ steps.docker_meta.outputs.labels }} + annotations: ${{ steps.docker_meta.outputs.annotations }} build-args: | VERSION=${{ steps.meta.outputs.version }} REVISION=${{ github.sha }} diff --git a/README.md b/README.md index e055578..c3543fe 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ -# Tiny Images +# Tiny CLI Images — Minimal Docker and OCI Images -Minimal OCI images for useful command-line tools. +Minimal, secure, multi-architecture Docker and OCI container images for useful +command-line tools. Images are published to GitHub Container Registry (GHCR) +for `linux/amd64` and `linux/arm64`. Release images run as non-root, include +SBOM and provenance attestations, and are signed with Sigstore. ## Principles @@ -15,14 +18,17 @@ Minimal OCI images for useful command-line tools. - Immutable version tags - No unnecessary shell or package manager -## Images +## Container image catalog -| Image | amd64 | arm64 | Runtime | -|---|---|---|---| -| age | ✓ | ✓ | scratch | -| xh | ✓ | ✓ | scratch | +| Tool | Container image | Purpose | Platforms | Runtime | +|---|---|---|---|---| +| [age](images/age/README.md) | `ghcr.io/unitmatrix/age` | File encryption | amd64, arm64 | scratch | +| [xh](images/xh/README.md) | `ghcr.io/unitmatrix/xh` | HTTP client | amd64, arm64 | scratch | -## age +## age encryption container image + +The age image packages the [age encryption tool](https://github.com/FiloSottile/age) +in a minimal, non-root container without a shell or package manager. @@ -35,7 +41,10 @@ docker run --rm ghcr.io/unitmatrix/age:1.3.1 --version See [the age image documentation](images/age/README.md) for usage and release details. -## xh +## xh HTTP client container image + +The xh image packages the [xh HTTP client](https://github.com/ducaale/xh) in a +minimal, non-root container with CA certificates for HTTPS requests. diff --git a/docs/PROJECT.md b/docs/PROJECT.md index 59b1aee..0460c92 100644 --- a/docs/PROJECT.md +++ b/docs/PROJECT.md @@ -108,8 +108,10 @@ entrypoint. The final image uses `scratch` and runs as UID/GID `65532:65532`. ## Metadata and updates `scripts/meta.py` reads `images//image.toml` and exposes the name, -version, upstream repository, platform targets, and checksums to GitHub -Actions. +description, version, upstream repository, license, platform targets, and +checksums to GitHub Actions. Release builds publish this metadata as OCI labels +and as manifest and image-index annotations so multi-architecture GHCR package +pages display the image description and link back to this repository. `scripts/update.py ` queries the GitHub Releases API, rejects missing, draft, or prerelease releases, requires both expected platform diff --git a/images/age/Dockerfile b/images/age/Dockerfile index a83e9ed..77fecb4 100644 --- a/images/age/Dockerfile +++ b/images/age/Dockerfile @@ -56,10 +56,11 @@ ARG REVISION COPY --from=fetch /rootfs / LABEL org.opencontainers.image.title="age" -LABEL org.opencontainers.image.description="Minimal OCI image for age" +LABEL org.opencontainers.image.description="Minimal, multi-architecture OCI container image for age file encryption" LABEL org.opencontainers.image.version="${VERSION}" LABEL org.opencontainers.image.revision="${REVISION}" -LABEL org.opencontainers.image.source="https://github.com/FiloSottile/age" +LABEL org.opencontainers.image.source="https://github.com/unitmatrix/tiny-cli-images" +LABEL org.opencontainers.image.documentation="https://github.com/unitmatrix/tiny-cli-images/tree/main/images/age" LABEL org.opencontainers.image.licenses="BSD-3-Clause" USER 65532:65532 diff --git a/images/age/README.md b/images/age/README.md index ab38a1b..316f35e 100644 --- a/images/age/README.md +++ b/images/age/README.md @@ -1,7 +1,9 @@ -# age OCI image +# age Docker and OCI container image -This image packages the upstream [FiloSottile/age](https://github.com/FiloSottile/age) -static Linux binaries in a minimal `scratch` runtime. +Minimal, non-root, multi-architecture container image for +[age](https://github.com/FiloSottile/age), a simple, modern, and secure file +encryption tool. The image packages the upstream static Linux binaries in a +`scratch` runtime and is published as `ghcr.io/unitmatrix/age`. ## Usage diff --git a/images/age/image.toml b/images/age/image.toml index 4b4813e..a0b17c7 100644 --- a/images/age/image.toml +++ b/images/age/image.toml @@ -1,5 +1,5 @@ name = "age" -description = "Simple, modern, and secure file encryption" +description = "Minimal, multi-architecture OCI container image for age file encryption" version = "1.3.1" upstream = "FiloSottile/age" diff --git a/images/xh/Dockerfile b/images/xh/Dockerfile index 42d2733..844181d 100644 --- a/images/xh/Dockerfile +++ b/images/xh/Dockerfile @@ -57,10 +57,11 @@ ARG REVISION COPY --from=fetch /rootfs / LABEL org.opencontainers.image.title="xh" -LABEL org.opencontainers.image.description="Minimal OCI image for xh" +LABEL org.opencontainers.image.description="Minimal, multi-architecture OCI container image for xh, the friendly HTTP client" LABEL org.opencontainers.image.version="${VERSION}" LABEL org.opencontainers.image.revision="${REVISION}" -LABEL org.opencontainers.image.source="https://github.com/ducaale/xh" +LABEL org.opencontainers.image.source="https://github.com/unitmatrix/tiny-cli-images" +LABEL org.opencontainers.image.documentation="https://github.com/unitmatrix/tiny-cli-images/tree/main/images/xh" LABEL org.opencontainers.image.licenses="MIT" USER 65532:65532 diff --git a/images/xh/README.md b/images/xh/README.md index d519254..e2cdddd 100644 --- a/images/xh/README.md +++ b/images/xh/README.md @@ -1,7 +1,9 @@ -# xh OCI image +# xh Docker and OCI container image -This image packages the upstream [ducaale/xh](https://github.com/ducaale/xh) -Linux musl binary in a minimal `scratch` runtime. +Minimal, non-root, multi-architecture container image for +[xh](https://github.com/ducaale/xh), a friendly and fast HTTP client. The image +packages the upstream Linux musl binary in a `scratch` runtime with CA +certificates and is published as `ghcr.io/unitmatrix/xh`. ## Usage diff --git a/images/xh/image.toml b/images/xh/image.toml index 9b2de5f..c50699e 100644 --- a/images/xh/image.toml +++ b/images/xh/image.toml @@ -1,5 +1,5 @@ name = "xh" -description = "Friendly and fast tool for sending HTTP requests" +description = "Minimal, multi-architecture OCI container image for xh, the friendly HTTP client" version = "0.26.2" upstream = "ducaale/xh" diff --git a/scripts/meta.py b/scripts/meta.py index 3e4d255..38b2ea0 100755 --- a/scripts/meta.py +++ b/scripts/meta.py @@ -41,6 +41,10 @@ def load_config(image: str) -> dict[str, Any]: def metadata(config: dict[str, Any]) -> dict[str, str]: + description = config.get("description") + if not isinstance(description, str) or not description or "\n" in description: + fail("manifest description must be a non-empty single-line string") + version = config.get("version") if not isinstance(version, str): fail("manifest version must be a string") @@ -56,10 +60,16 @@ def metadata(config: dict[str, Any]) -> dict[str, str]: if config.get("architectures") != list(ARCHITECTURES): fail("manifest must declare amd64 and arm64 in that order") + license_name = config.get("license") + if not isinstance(license_name, str) or not license_name or "\n" in license_name: + fail("manifest license must be a non-empty single-line string") + values = { "name": config["name"], + "description": description, "version": version, "upstream": upstream, + "license": license_name, } platforms = config.get("platform")