<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Alek&#x27;s Blog - sbom</title>
    <subtitle>My Blog to share my knowledge</subtitle>
    <link rel="self" type="application/atom+xml" href="https://blog.none.at/tags/sbom/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://blog.none.at"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-07-06T00:00:00+00:00</updated>
    <id>https://blog.none.at/tags/sbom/atom.xml</id>
    <entry xml:lang="en">
        <title>k8s-scale-app-rs: Scale or Restart a Kubernetes Deployment from a CronJob</title>
        <published>2026-07-06T00:00:00+00:00</published>
        <updated>2026-07-06T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              aleks
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.none.at/blog/2026/2026-07-06-k8s-scale-app-rs/"/>
        <id>https://blog.none.at/blog/2026/2026-07-06-k8s-scale-app-rs/</id>
        
        <content type="html" xml:base="https://blog.none.at/blog/2026/2026-07-06-k8s-scale-app-rs/">&lt;h2 id=&quot;tl-dr&quot;&gt;TL;DR&lt;a class=&quot;zola-anchor&quot; href=&quot;#tl-dr&quot; aria-label=&quot;Anchor link for: tl-dr&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;git001&#x2F;k8s-scale-app-rs&quot;&gt;k8s-scale-app-rs&lt;&#x2F;a&gt; is a Rust CLI that does exactly one of two things and then exits:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;scale&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — sets a Deployment’s replica count to a fixed value via the &lt;code&gt;deployments&#x2F;scale&lt;&#x2F;code&gt; subresource.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;restart&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — patches the pod template’s &lt;code&gt;restartedAt&lt;&#x2F;code&gt; annotation, which is the same mechanism &lt;code&gt;kubectl rollout restart&lt;&#x2F;code&gt; uses.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Designed to run as a &lt;code&gt;CronJob&lt;&#x2F;code&gt;. Version 1.0.0 is published; container images and the Helm chart are on &lt;code&gt;ghcr.io&lt;&#x2F;code&gt;, signed with cosign (keyless via GitHub OIDC), and shipped with an SPDX SBOM plus SLSA build provenance.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-i-built-it&quot;&gt;Why I built it&lt;a class=&quot;zola-anchor&quot; href=&quot;#why-i-built-it&quot; aria-label=&quot;Anchor link for: why-i-built-it&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The classic way to “run &lt;code&gt;kubectl scale&lt;&#x2F;code&gt; on a schedule in a cluster” is to put a &lt;code&gt;kubectl&lt;&#x2F;code&gt; binary into a Job image. That works, but it drags in a few things I did not want:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The full &lt;code&gt;kubectl&lt;&#x2F;code&gt; binary (about 50 MB stripped) for one API call.&lt;&#x2F;li&gt;
&lt;li&gt;A kubeconfig &#x2F; ServiceAccount plumbing that is broader than the one operation actually needs.&lt;&#x2F;li&gt;
&lt;li&gt;Enough YAML around the pod spec to hide the actual behavior behind a template.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;A purpose-built tool is a natural fit for Rust here: a single self-contained binary — no OpenSSL, no runtime dependency beyond glibc and the CA trust store — and a ServiceAccount that only needs &lt;code&gt;deployments&#x2F;scale: patch&lt;&#x2F;code&gt;. That is what k8s-scale-app-rs is.&lt;&#x2F;p&gt;
&lt;p&gt;KEDA and Argo Workflows solve a related but different problem — reacting to events or orchestrating multi-step pipelines — not “flip a replica count or a restart annotation on a fixed schedule.” KEDA in particular has its own well-known scale-from-zero gap: a Prometheus-metric trigger can scale 1→N but not 0→1, because a Deployment at zero replicas emits no metric to react to (see &lt;a href=&quot;https:&#x2F;&#x2F;blog.none.at&#x2F;blog&#x2F;2026&#x2F;2026-06-02-llm-inference-on-ovh-observability&#x2F;#keda-autoscaling&quot;&gt;the LLM inference series&lt;&#x2F;a&gt; for the concrete failure mode). None of that machinery applies here — a CronJob calling a purpose-built binary on a fixed schedule has nothing to react to.&lt;&#x2F;p&gt;
&lt;p&gt;The same binary also covers restart. &lt;code&gt;kubectl rollout restart deployment&#x2F;foo&lt;&#x2F;code&gt; under the hood is one patch on &lt;code&gt;spec.template.metadata.annotations[&quot;kubectl.kubernetes.io&#x2F;restartedAt&quot;]&lt;&#x2F;code&gt;. Two subcommands, same binary, same image.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-it-does&quot;&gt;What it does&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-it-does&quot; aria-label=&quot;Anchor link for: what-it-does&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The CLI is two subcommands sharing common arguments:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;k8s-scale-app-rs scale   --deployment &amp;lt;NAME&amp;gt; --replicas &amp;lt;N&amp;gt; [--dry-run] [--extra-ca-bundle &amp;lt;PATH&amp;gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;k8s-scale-app-rs restart --deployment &amp;lt;NAME&amp;gt;                [--dry-run] [--extra-ca-bundle &amp;lt;PATH&amp;gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Configuration is via environment variables, overridable by CLI flags:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Flag&lt;&#x2F;th&gt;&lt;th&gt;ENV&lt;&#x2F;th&gt;&lt;th&gt;Subcommands&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--deployment&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;K8S_SCALE_DEPLOYMENT&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;both&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--namespace&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;K8S_SCALE_NAMESPACE&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;both&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--replicas&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;K8S_SCALE_REPLICAS&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;scale&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--dry-run&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;K8S_SCALE_DRY_RUN&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;both&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--extra-ca-bundle&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;K8S_SCALE_EXTRA_CA_BUNDLE&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;both&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;The &lt;code&gt;--namespace&lt;&#x2F;code&gt; flag exists, but the CronJob does not hardcode it. Instead, the pod reads its own namespace from the Downward API and passes it through as an environment variable. That keeps the ServiceAccount’s Role safely namespace-scoped: the tool can only ever act on Deployments in the namespace where the CronJob itself runs. Not a general-purpose “scale anything anywhere” tool.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;design-highlights&quot;&gt;Design highlights&lt;a class=&quot;zola-anchor&quot; href=&quot;#design-highlights&quot; aria-label=&quot;Anchor link for: design-highlights&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;minimal-rbac-via-the-scale-subresource&quot;&gt;Minimal RBAC via the &lt;code&gt;scale&lt;&#x2F;code&gt; subresource&lt;a class=&quot;zola-anchor&quot; href=&quot;#minimal-rbac-via-the-scale-subresource&quot; aria-label=&quot;Anchor link for: minimal-rbac-via-the-scale-subresource&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Setting replicas does not need &lt;code&gt;patch&lt;&#x2F;code&gt; on the full Deployment. Kubernetes exposes &lt;code&gt;deployments&#x2F;scale&lt;&#x2F;code&gt; as a dedicated subresource:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;r&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;ules&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt; a&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;piGroups&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;apps&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;    r&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;esources&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;deployments&#x2F;scale&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;    v&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;erbs&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;get&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;patch&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;update&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That is all the scale mode needs. Restart mode does need &lt;code&gt;patch&lt;&#x2F;code&gt; on &lt;code&gt;deployments&lt;&#x2F;code&gt; itself (to update the pod template annotation), so the shipped Role covers both:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt; a&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;piGroups&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;apps&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;    r&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;esources&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;deployments&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;    v&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;erbs&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;get&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;patch&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;patch&lt;&#x2F;code&gt; on the full Deployment is broader than &lt;code&gt;patch&lt;&#x2F;code&gt; on &lt;code&gt;deployments&#x2F;scale&lt;&#x2F;code&gt; — RBAC review at deploy time should factor that in when the CronJob is configured for restart mode.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;extra-ca-bundle-merged-into-the-trust-store&quot;&gt;Extra-CA bundle merged into the trust store&lt;a class=&quot;zola-anchor&quot; href=&quot;#extra-ca-bundle-merged-into-the-trust-store&quot; aria-label=&quot;Anchor link for: extra-ca-bundle-merged-into-the-trust-store&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;The kube client uses the in-cluster CA from the ServiceAccount token secret automatically. For clusters whose API server certificate is signed by a corporate CA that is not in the SA-mounted &lt;code&gt;ca.crt&lt;&#x2F;code&gt;, &lt;code&gt;--extra-ca-bundle &amp;lt;path&amp;gt;&lt;&#x2F;code&gt; reads a PEM file (or a full chain of them) and appends every &lt;code&gt;CERTIFICATE&lt;&#x2F;code&gt; block to &lt;code&gt;kube::Config.root_cert&lt;&#x2F;code&gt;. Non-certificate blocks in the file are filtered out.&lt;&#x2F;p&gt;
&lt;p&gt;Rendered in Helm as an optional ConfigMap mount; in Kustomize as an opt-in Component. Neither path forces the mount when there is no corporate CA to add.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-rustls-0-23-cryptoprovider-gotcha&quot;&gt;The rustls 0.23 CryptoProvider gotcha&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-rustls-0-23-cryptoprovider-gotcha&quot; aria-label=&quot;Anchor link for: the-rustls-0-23-cryptoprovider-gotcha&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;If you build against &lt;code&gt;kube-rs&lt;&#x2F;code&gt; with the &lt;code&gt;rustls-tls&lt;&#x2F;code&gt; feature on the current release, the binary compiles cleanly — and then panics at the first TLS handshake with:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Could not automatically determine the process-level CryptoProvider from Rustls crate features&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;rustls&lt;&#x2F;code&gt; 0.23 no longer picks a crypto backend based on downstream feature flags. The application itself must call &lt;code&gt;rustls::crypto::ring::default_provider().install_default()&lt;&#x2F;code&gt; before any TLS work happens. In k8s-scale-app-rs this runs as the first step in &lt;code&gt;main()&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Two notes for anyone hitting the same wall: the panic never fires at compile time, and no test caught it locally because integration tests without a cluster do not open a TLS connection. It only surfaces when the CronJob actually tries to reach the API server.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;two-deploy-paths-independent&quot;&gt;Two deploy paths, independent&lt;a class=&quot;zola-anchor&quot; href=&quot;#two-deploy-paths-independent&quot; aria-label=&quot;Anchor link for: two-deploy-paths-independent&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Helm and Kustomize both ship in the repo, and either can be used on its own:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Helm chart&lt;&#x2F;strong&gt; with &lt;code&gt;values-{dev,preprod,prod}.yaml&lt;&#x2F;code&gt;, a &lt;code&gt;mode: scale | restart&lt;&#x2F;code&gt; toggle that swaps the subcommand argument and drops the unused replicas env, and &lt;code&gt;serviceAccount.create&lt;&#x2F;code&gt; &#x2F; &lt;code&gt;rbac.create&lt;&#x2F;code&gt; &#x2F; &lt;code&gt;extraCA.enabled&lt;&#x2F;code&gt; toggles for the pieces that a given cluster may or may not need.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Kustomize&lt;&#x2F;strong&gt; with a base that has only the CronJob, plus three opt-in Components (&lt;code&gt;serviceaccount&#x2F;&lt;&#x2F;code&gt;, &lt;code&gt;rbac&#x2F;&lt;&#x2F;code&gt;, &lt;code&gt;extra-ca&#x2F;&lt;&#x2F;code&gt;) that overlays include as needed. Overlays under &lt;code&gt;overlays&#x2F;{dev,preprod,prod}&#x2F;&lt;&#x2F;code&gt; set namespace and image tag and a patch that touches only the fields that vary per stage.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;No Helm-inside-Kustomize glue, no Kustomize-on-Helm-render. Whichever tool matches an existing pipeline is the one to use.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;two-container-images&quot;&gt;Two container images&lt;a class=&quot;zola-anchor&quot; href=&quot;#two-container-images&quot; aria-label=&quot;Anchor link for: two-container-images&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The default image and a &lt;code&gt;-debug&lt;&#x2F;code&gt; variant, both built in the same GitHub Actions matrix — the
production&#x2F;&lt;code&gt;-debug&lt;&#x2F;code&gt; split itself is a general pattern, covered in more depth in
&lt;a href=&quot;https:&#x2F;&#x2F;blog.none.at&#x2F;blog&#x2F;2026&#x2F;2026-07-01-k8s-openshift-bp-workloads&#x2F;#choose-your-base-image-carefully&quot;&gt;Part 2 of the K8s&#x2F;OpenShift Best Practices series&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;File&lt;&#x2F;th&gt;&lt;th&gt;Base&lt;&#x2F;th&gt;&lt;th&gt;Contents&lt;&#x2F;th&gt;&lt;th&gt;Approx. size&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Containerfile&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;registry.access.redhat.com&#x2F;ubi10&#x2F;ubi-micro&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Binary + CA trust store + &lt;code&gt;&#x2F;licenses&#x2F;&lt;&#x2F;code&gt;, no package manager&lt;&#x2F;td&gt;&lt;td&gt;≈35 MB&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Containerfile.debug&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;registry.access.redhat.com&#x2F;ubi10&#x2F;ubi-minimal&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Everything above + &lt;code&gt;bash&lt;&#x2F;code&gt;, &lt;code&gt;curl&lt;&#x2F;code&gt;, &lt;code&gt;bind-utils&lt;&#x2F;code&gt; (&lt;code&gt;dig&lt;&#x2F;code&gt;)&lt;&#x2F;td&gt;&lt;td&gt;≈115 MB&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;&lt;code&gt;ubi10-micro&lt;&#x2F;code&gt; is not fully distroless in the Google sense — bash and coreutils are still there — but it has no package manager and no network tools, which fits the pod’s actual operational surface for a scheduled scale or restart call. The &lt;code&gt;-debug&lt;&#x2F;code&gt; variant exists for the day a pod loops in &lt;code&gt;ImagePullBackOff&lt;&#x2F;code&gt; or a network policy silently drops the API-server traffic and someone needs to &lt;code&gt;kubectl exec&lt;&#x2F;code&gt; in with tools to check.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;why-ubi10&quot;&gt;Why UBI10&lt;a class=&quot;zola-anchor&quot; href=&quot;#why-ubi10&quot; aria-label=&quot;Anchor link for: why-ubi10&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;The base itself, not just the two variants built from it, was a deliberate choice. I went with
Red Hat’s UBI over an Alpine or a generic Docker Hub base image for one reason: it gives the
image a level of maintenance stability I don’t have to police myself — CVE scanning and rebuilds
are Red Hat’s job, not something I track on my own. That comes with a tradeoff worth stating
plainly: UBI is not “digitally sovereign” in the sense the upcoming Digital Sovereignty in
Practice series covers — it is a US vendor’s supply chain, the same as pulling from Docker Hub
or almost any other public registry would be. I did not find a base that solved that concern
without giving up the maintenance guarantees I wanted, so UBI10 here is a compromise I made
deliberately, not a claim that the underlying problem is solved.&lt;&#x2F;p&gt;
&lt;p&gt;Both images ship an aggregated &lt;code&gt;&#x2F;licenses&#x2F;LICENSES.txt&lt;&#x2F;code&gt; bundled with &lt;code&gt;cargo-about&lt;&#x2F;code&gt; during the container build, generated from a whitelist of accepted SPDX identifiers. A new dependency that pulls in an unfamiliar license fails the container build on purpose.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;supply-chain&quot;&gt;Supply chain&lt;a class=&quot;zola-anchor&quot; href=&quot;#supply-chain&quot; aria-label=&quot;Anchor link for: supply-chain&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;git001&#x2F;k8s-scale-app-rs&#x2F;blob&#x2F;main&#x2F;.github&#x2F;workflows&#x2F;build-publish.yaml&quot;&gt;build-publish workflow&lt;&#x2F;a&gt; runs three jobs on every push, plus a release job on tag pushes:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;test&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — &lt;code&gt;cargo fmt --check&lt;&#x2F;code&gt;, &lt;code&gt;cargo clippy -D warnings&lt;&#x2F;code&gt;, &lt;code&gt;cargo test --release&lt;&#x2F;code&gt;. Cluster tests auto-skip in CI (no &lt;code&gt;KUBECONFIG&lt;&#x2F;code&gt;, no in-cluster SA token).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;build-image&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — matrix over &lt;code&gt;Containerfile&lt;&#x2F;code&gt; and &lt;code&gt;Containerfile.debug&lt;&#x2F;code&gt;. Each variant is built with buildx, pushed to &lt;code&gt;ghcr.io&#x2F;git001&#x2F;k8s-scale-app-rs&lt;&#x2F;code&gt;, smoke-tested (&lt;code&gt;--version&lt;&#x2F;code&gt;, &lt;code&gt;scale --help&lt;&#x2F;code&gt;, &lt;code&gt;restart --help&lt;&#x2F;code&gt;), then:
&lt;ul&gt;
&lt;li&gt;signed with &lt;strong&gt;cosign keyless&lt;&#x2F;strong&gt; via the GitHub OIDC token (no key management — Fulcio issues a short-lived certificate, the signature is logged to Rekor);&lt;&#x2F;li&gt;
&lt;li&gt;has an &lt;strong&gt;SPDX-JSON SBOM&lt;&#x2F;strong&gt; generated with syft and attached via &lt;code&gt;cosign attest --type spdxjson&lt;&#x2F;code&gt;;&lt;&#x2F;li&gt;
&lt;li&gt;has &lt;strong&gt;SLSA build provenance&lt;&#x2F;strong&gt; pushed with &lt;code&gt;actions&#x2F;attest-build-provenance&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;publish-chart&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — &lt;code&gt;helm lint&lt;&#x2F;code&gt; and &lt;code&gt;helm package&lt;&#x2F;code&gt; on every push; OCI push to &lt;code&gt;oci:&#x2F;&#x2F;ghcr.io&#x2F;git001&#x2F;charts&#x2F;k8s-scale-app-rs&lt;&#x2F;code&gt;, plus cosign sign and SLSA provenance only on tag events (avoids overwrite conflicts on GHCR-immutable tags).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;release&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; (tag events only) — creates the GitHub Release with auto-generated notes from commits since the previous tag, attaches the packaged chart &lt;code&gt;.tgz&lt;&#x2F;code&gt; as an asset, and prepends the release body with pull and verify snippets.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;All of that runs with &lt;code&gt;permissions: contents:read, packages:write, id-token:write, attestations:write&lt;&#x2F;code&gt; — the minimum needed for OIDC-based signing plus attestation upload.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;verifying-a-published-image&quot;&gt;Verifying a published image&lt;a class=&quot;zola-anchor&quot; href=&quot;#verifying-a-published-image&quot; aria-label=&quot;Anchor link for: verifying-a-published-image&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;A consumer that wants to check what came out of that pipeline needs cosign v2:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;IMG&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;g&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;h&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;r&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;i&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;o&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;g&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;i&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;0&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;0&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;1&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;k&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;8&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;s&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;s&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;a&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;l&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;e&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;a&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;p&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;p&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;r&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;s&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;v&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;1&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;0&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;IDENTITY&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;^https:&#x2F;&#x2F;github.com&#x2F;git001&#x2F;k8s-scale-app-rs&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;ISSUER&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;h&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;p&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;s&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;o&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;k&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;e&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;n&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;a&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;i&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;o&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;n&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;s&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;g&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;i&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;h&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;u&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;b&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;u&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;s&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;e&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;r&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;o&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;n&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;e&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;n&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;o&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;m&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;cosign&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; verify&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;  -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-certificate-identity-regexp&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;IDENTITY&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;  -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-certificate-oidc-issuer&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;ISSUER&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;IMG&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;cosign&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; verify-attestation&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-type&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; spdxjson&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;  -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-certificate-identity-regexp&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;IDENTITY&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;  -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-certificate-oidc-issuer&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;ISSUER&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;IMG&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;cosign&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; verify-attestation&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-type&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; slsaprovenance&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;  -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-certificate-identity-regexp&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;IDENTITY&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;  -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-certificate-oidc-issuer&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;ISSUER&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;IMG&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For enforcement inside the cluster, a cosign-aware admission controller — &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;kyverno.io&#x2F;&quot;&gt;Kyverno&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;sigstore&#x2F;policy-controller&quot;&gt;Sigstore policy-controller&lt;&#x2F;a&gt;, or &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;sse-secure-systems.github.io&#x2F;connaisseur&#x2F;&quot;&gt;Connaisseur&lt;&#x2F;a&gt; — can turn the signature into a hard gate that rejects any pod trying to pull an image that was not produced by exactly this workflow. That is a separate deployment concern; the tool repo does not ship the policy itself.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;mirroring-the-image-into-another-registry&quot;&gt;Mirroring the image into another registry&lt;a class=&quot;zola-anchor&quot; href=&quot;#mirroring-the-image-into-another-registry&quot; aria-label=&quot;Anchor link for: mirroring-the-image-into-another-registry&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Verifying a signature only works if the signature actually travels with the image. Not every &lt;code&gt;docker pull &amp;amp;&amp;amp; docker push&lt;&#x2F;code&gt;-style mirroring tool preserves it — some tools do, some quietly drop it, and one (&lt;code&gt;oc-mirror v2&lt;&#x2F;code&gt;) solves a related but entirely different signature problem. I wrote up a full, sourced comparison — &lt;a href=&quot;https:&#x2F;&#x2F;blog.none.at&#x2F;blog&#x2F;2026&#x2F;2026-07-06-mirroring-signed-images-private-registry&#x2F;&quot;&gt;Which Tool Mirrors a Cosign-Signed Image into a Private Registry?&lt;&#x2F;a&gt; — with &lt;code&gt;regctl&lt;&#x2F;code&gt; coming out as the most complete option for this specific image.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;code-and-repository&quot;&gt;Code and repository&lt;a class=&quot;zola-anchor&quot; href=&quot;#code-and-repository&quot; aria-label=&quot;Anchor link for: code-and-repository&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;Repository: &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;git001&#x2F;k8s-scale-app-rs&quot;&gt;k8s-scale-app-rs on GitHub&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Container images: &lt;code&gt;ghcr.io&#x2F;git001&#x2F;k8s-scale-app-rs:v1.0.0&lt;&#x2F;code&gt; and &lt;code&gt;ghcr.io&#x2F;git001&#x2F;k8s-scale-app-rs:v1.0.0-debug&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Helm chart: &lt;code&gt;oci:&#x2F;&#x2F;ghcr.io&#x2F;git001&#x2F;charts&#x2F;k8s-scale-app-rs:1.0.0&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Feedback and PRs welcome.&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
