Shared CI/CD workflows and actions for the thingzio organization.
One place to fix a pipeline problem, one place to review a supply-chain decision, and one identity for every repository to verify against.
- Reusable workflows are called at the job level and do a whole job end to end. These are the product.
- Composite actions are called at the step level, for building something the workflows do not cover.
Pin to a commit SHA, or track the floating @v1 tag. See the
latest release for the
current version.
One exception, and it is not a style preference: pin the release and build
workflows by SHA. Their SLSA Build Level 3 claim rests on the build
definition being immutable, and a floating tag is a definition someone can
move. @v1 is fine for a linter; it quietly forfeits the level for a
builder.
| Workflow | What it does | Docs |
|---|---|---|
build-ko.yaml |
Builds a Go repository into a multi-platform, signed, SBOM-attested container image with ko. No Dockerfile; arm64 is cross-compiled rather than emulated. | docs |
release-go.yaml |
Releases a Go repository's binaries with goreleaser: a signed checksum file, SLSA Build Level 3 provenance, and SBOMs when the caller's config asks for them. Signing is isolated from the caller's goreleaser hooks, which is what keeps the level. | docs |
build-docker.yaml |
Builds a Dockerfile into a multi-platform, signed, SBOM-attested container image on native per-architecture runners. | docs |
codeql-go.yaml |
Runs CodeQL over a Go repository and uploads the results to the caller's security tab. Builds explicitly, detects vendoring, and skips private repositories so it cannot start GHAS billing by surprise. | docs |
terraform-scan.yaml |
Scans Terraform for misconfigurations with Trivy, failing on a severity threshold. Replaces tfsec, which is end of life and whose ruleset is frozen. | docs |
lint-go.yaml |
Runs gofmt, go vet and golangci-lint over a Go repository, at the org-pinned linter version. | docs |
deploy-cloud-run.yaml |
Deploys digest-pinned images to Cloud Run services and jobs, pulling through an Artifact Registry remote repository, with optional scheduler pause/resume. | docs |
Use build-ko whenever the answer is "a Go binary in a minimal base image";
use build-docker when the image needs system packages, a non-Go runtime, or
multiple build stages.
build-ko, build-docker and release-go all reach SLSA v1.0 Build Level
3 — see SLSA. codeql-go, terraform-scan, lint-go and
deploy-cloud-run sign nothing and publish no artifact, so the level does not
apply to them; the no-caller-supplied-code rule below still does.
Composite actions are SLSA Build Level 2, not 3. They run inside a job you control, so the isolation the Level 3 claim rests on does not apply. Use a reusable workflow when the level matters.
| Action | What it does | Docs |
|---|---|---|
load-versions |
Reads pinned tool versions from .versions.yaml |
docs |
setup-go |
Configures Go from the repository's go.mod, caching modules only when a lock file exists |
docs |
setup-build-tools |
Installs ko, crane, syft and cosign, checksum-verified | docs |
registry-login |
Authenticates to GHCR or any OCI registry | docs |
image-tags |
Derives release tags and the candidate tag from the event | docs |
resolve-digests |
Resolves index and per-platform digests, fails closed | docs |
sbom-attest |
Generates CycloneDX SBOMs and attests them with cosign | docs |
promote-tags |
Moves release tags onto an attested digest | docs |
verify-image |
Verifies provenance and SBOM attestations | docs |
Call a reusable workflow at the job level and grant it the permissions it needs. Each workflow's document lists its inputs, outputs and required permissions; start with adoption if you are converting an existing pipeline.
jobs:
image:
permissions:
contents: read
packages: write
id-token: write
attestations: write
uses: thingzio/actions/.github/workflows/build-ko.yaml@<commit-sha> # v1.0.0
with:
image: thingzio/my-app
main: ./cmd/my-appPin to a commit SHA with a trailing version comment. That is what this repository does with every action it uses, what OpenSSF Scorecard's pinned-dependencies check expects, and what Renovate and Dependabot understand.
The floating @v1 tag is supported for teams that prefer the lower-effort
option. It moves on every compatible release, so you inherit changes without
review — a deliberate trade-off, not an oversight.
Because the Sigstore certificate identity contains the workflow filename, renaming a workflow file is a breaking change even when its inputs are unchanged.
Worth reading once; it is also the bar a new workflow has to meet.
- No caller-supplied code. No input becomes shell, and
runs-oncomes from a closed allowlist. This is what the Build Level 3 claim rests on. - Nothing unpinned. Every
uses:is a commit SHA; every downloaded binary is checksum-verified and deleted on mismatch. Nocurl | bash. - Read-only by default.
permissions: contents: readat the top of every file, elevated per job only where needed. - Fork-safe.
pull_requestonly, neverpull_request_target. Fork builds degrade to no push, no signature, and say so in the job summary. - Fail closed. Inputs are validated at the boundary, and anything that cannot be verified stops the run rather than being warned about.
- Publish last. Anything that makes an artifact reachable by name happens only after its evidence exists, so a failed run leaves nothing behind.
- Versions live in one file.
.versions.yamlis the single source of truth; nothing anywhere hardcodes a version. - Proven, not asserted.
selftest.yamlexercises every workflow against real fixtures on every pull request and verifies the result, including the commands published for consumers. The one exception isdeploy-cloud-run, which would need a disposable GCP project; its decision logic is unit tested with gcloud stubbed, and Cloud Run's own revision model means a service that fails to start never takes traffic.
make verify # everything CI runs: shell tests, actionlint, yamllint, shellcheck
make test # the shell test suite
make lint # linters only
make versions # print every pinned versionTools install into .bin/ at the pinned versions, so a laptop and a runner run
the same binaries.
.versions.yaml single source of truth for every tool version
.github/workflows/ reusable workflows, each with a sibling .md
plus this repo's own CI, selftest, release
.github/actions/ composite actions, each with a README
scripts/ validation and tag logic, unit tested
scripts/actions/ the shell behind each composite action
test/ zero-dependency shell test harness
testdata/ fixtures the selftest builds for real
docs/ cross-cutting: verification, SLSA, adoption, org policy
| Verification | How to check what these workflows publish |
| SLSA | What level, why it holds, and what would break it |
| Adoption | Moving an existing repository onto these workflows |
| Org allowlist | Actions policy a consuming org needs |
| Contributing | Setup, the trust-boundary rules, how tests work |
| Releasing | For maintainers |
| Security policy | Reporting, scope, and posture |
| Maintainers | Who to ask |
| Rulesets | Branch and tag protection as code |
Apache 2.0. See NOTICE.