The dev-tools image¶
Every Go, Rust,
and Svelte component runs on
registry.gitlab.com/phpboyscout/images/dev-tools
by default. That wasn't always true — earlier versions of these components
installed their toolchain tools at job runtime, on nearly every pipeline
job.
The problem: paying the install cost on every job¶
Before dev-tools, a rust-security job would cargo install --locked
cargo-binstall then cargo binstall cargo-deny cargo-audit before it could
run either scanner. A go-security job would go install
golang.org/x/vuln/cmd/govulncheck@latest before it could scan. Multiply
that across every MR, every job, every consumer repo, and it's a network
fetch plus (for some tools) a compile, paid over and over for tools that
don't change between runs.
dev-tools bakes them instead: Go, Rust (via rustup), Node, and the CI
tools each language's components need (cargo-nextest, cargo-llvm-cov,
cargo-deny, cargo-audit, govulncheck) all live in one image, versioned
together. Switching the component image defaults to it and deleting the
runtime installs was, in the project's own words, "the single biggest
recurring-time win on the self-hosted runner" —
2026-06-22-components-use-dev-tools-v0.14.md.
What actually changed¶
| Component | input | what dropped |
|---|---|---|
go-security |
govulncheck_image |
go install golang.org/x/vuln/cmd/govulncheck@latest |
rust-test |
image |
cargo install cargo-binstall + cargo binstall cargo-nextest (+ cargo-llvm-cov for the coverage job) |
rust-security |
rust_image |
the binstall bootstrap for cargo-deny / cargo-audit |
The scripts now call the tool directly — govulncheck ./..., cargo
nextest run, cargo deny check — because it's already on PATH.
What deliberately stayed¶
Not everything moved onto dev-tools, and each exception has a reason:
rustup component add clippy|rustfmt|llvm-tools-previewstays in the scripts.dev-toolsbakes these for its own pinned Rust toolchain, but a consumer'srust-toolchain.tomlcan makerustupinstall a different toolchain at runtime — and that toolchain needs its own components.rustup component addis a fast, local no-op when the component is already present, and a correctness safeguard when it isn't. (The cargo binary tools — nextest, deny, audit — are toolchain-independent, so baking them works regardless of which Rust the project pins.)- Cross-OS jobs are untouched.
rust-test'stest-macos/test-windowsrun onsaas-macos/saas-windowsrunners and bootstrap Rust + tools there —dev-toolsis a Linux image, so those jobs keep their own install logic. - Security scanners with their own upstream images are untouched.
trivy,gitleaks,osv-scanner, andanalyze(semgrep) run from their own pinned images regardless of language track — they were never a runtime install to begin with, so there was nothing to bake. - Vestigial version inputs stay, ignored, for compatibility.
rust-security'scargo_deny_version/cargo_audit_versionandrust-test'sbinstall_versionno longer drive anything — the image pins those versions now — but removing the inputs outright would be a breaking change for a consumer who overrode them. They're documented as deprecated and slated for removal in a later major.
Why this belongs to Go/Rust/Svelte and not Tofu¶
The Terraform/OpenTofu and static-site components run on
infra-tools instead —
a separate, older image consolidating tofu, tflint, trivy, checkov,
terraform-docs, and (for Zensical) Python. infra-tools predates
dev-tools and follows the identical philosophy — bake the toolchain once,
version it independently of the components — just for a different set of
tools. dev-tools exists because the Go/Rust/Svelte tracks needed the same
treatment for their own toolchains.