<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>GitOps Archives - Linuxcent</title>
	<atom:link href="https://linuxcent.com/tag/gitops/feed/" rel="self" type="application/rss+xml" />
	<link>https://linuxcent.com/tag/gitops/</link>
	<description>Infrastructure security, from the kernel up.</description>
	<lastBuildDate>Mon, 27 Jul 2026 11:57:31 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0.4</generator>

<image>
	<url>https://linuxcent.com/wp-content/uploads/2026/04/favicon-512x512-1-150x150.png</url>
	<title>GitOps Archives - Linuxcent</title>
	<link>https://linuxcent.com/tag/gitops/</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">211632295</site>	<item>
		<title>The Pipeline Gate — Hardened Images as a CI/CD Build Constraint</title>
		<link>https://linuxcent.com/hardened-image-cicd-pipeline-gate/</link>
					<comments>https://linuxcent.com/hardened-image-cicd-pipeline-gate/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Sat, 23 May 2026 02:00:00 +0000</pubDate>
				<category><![CDATA[OS Image Builder]]></category>
		<category><![CDATA[BakeX]]></category>
		<category><![CDATA[CI/CD]]></category>
		<category><![CDATA[DevSecOps]]></category>
		<category><![CDATA[GitOps]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Pipeline]]></category>
		<category><![CDATA[Security]]></category>
		<guid isPermaLink="false">https://linuxcent.com/?p=1831</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 7</span> <span class="rt-label rt-postfix">minutes</span></span>Make hardened OS images a CI/CD build constraint: POST /api/pipeline/scan fails the build if grade < threshold. Unhardened images never reach production.
</p>
<p>The post <a href="https://linuxcent.com/hardened-image-cicd-pipeline-gate/">The Pipeline Gate — Hardened Images as a CI/CD Build Constraint</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 7</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;
    font-size:.88em;line-height:1.6;border-left:4px solid #555}
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}
pre code{background:transparent;padding:0;color:inherit}
pre[data-lang="bash"],pre[data-lang="sh"],
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}
pre[data-lang="yaml"],pre[data-lang="json"],
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}
pre[data-lang="text"],pre[data-lang="output"],
pre[data-lang="console"]{border-left-color:#888}
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;
    transition:opacity .15s,background .15s;line-height:1.6}
pre:hover .lc-copy-btn{opacity:1}
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}
pre:hover .lc-lang-badge{opacity:1}
table{border-collapse:collapse;width:100%;margin:16px 0}
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}
th{background:#f0f0f0;font-weight:600}
tr:nth-child(even){background:#fafafa}
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script></p>
<p><em>OS Hardening as Code, Episode 5</em><br />
<em><a href="https://linuxcent.com/cloud-ami-security-risks-custom-os-images/">Cloud AMI Security Risks</a> · <a href="/linux-hardening-as-code/">Linux Hardening as Code</a> · <a href="/linux-hardening-multi-cloud/">Multi-Cloud OS Hardening</a> · <a href="/automated-compliance-scanning-openscap/">Automated OpenSCAP Compliance</a> · </em><em>CI/CD Compliance Gate</em>**</p>
<blockquote>
<p><strong>Note:</strong> the tool in this series was released as <strong>Stratum</strong> and renamed to <strong>BakeX</strong> at<br />
v0.6.0 — same project, same license, same team. Commands below use the current <code class="" data-line="">bakex</code><br />
CLI. If you arrived here looking for <code class="" data-line="">stratum</code> or <code class="" data-line="">pip install stratumoss</code>, you&#8217;re in the<br />
right place: <a href="https://github.com/invicton/bakex">github.com/invicton/bakex</a>.</p>
</blockquote>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li>A CI/CD compliance gate turns an OS hardening grade from a report into a build constraint — unhardened images fail the pipeline before they can be deployed</li>
<li><code class="" data-line="">POST /api/pipeline/scan</code> scores an image against a <code class="" data-line="">pass_threshold</code> and a <code class="" data-line="">severity_threshold</code>, and returns a <code class="" data-line="">passed</code> boolean</li>
<li><strong>The endpoint returns HTTP 200 even when the gate fails.</strong> <code class="" data-line="">curl -sf</code> will not catch it — you must parse <code class="" data-line="">.passed</code>. This is the single most important detail on this page</li>
<li>The gate is two-dimensional: a score floor <em>and</em> a severity ceiling, so one critical finding blocks a release that scores 94</li>
<li>GitHub Actions, GitLab CI, Jenkins, and Tekton integrations are one curl plus one <code class="" data-line="">jq</code></li>
<li>The structural guarantee: an image that doesn&#8217;t pass the gate doesn&#8217;t reach the deploy job</li>
</ul>
<hr />
<h2 id="the-problem-a-grade-no-one-checks-is-decoration">The Problem: A Grade No One Checks Is Decoration</h2>
<pre><code class="" data-line="">Pipeline without compliance gate:
  Build → Test → Security scan (results to dashboard) → Deploy

What actually happens:
  Build → Test → Security scan → &quot;C grade, but we need to ship&quot; → Deploy anyway
                                           │
                                           └─ Dashboard shows C grade
                                              Nobody is paged
                                              Deployment succeeds
</code></pre>
<p>A CI/CD compliance gate means the pipeline can&#8217;t continue if the grade is below threshold.</p>
<p>EP04 showed that automated OpenSCAP compliance gives every image a verified, reproducible grade before deployment. What it assumed is that someone checks the grade before deploying. They don&#8217;t — not under deadline pressure, not when the image has been &#8220;working fine for months,&#8221; not at 2am.</p>
<p>The same problem that made hardening runbooks skippable applies to compliance grades: if checking the grade is a discretionary step, it will be skipped.</p>
<hr />
<p>A new microservice was deployed from an unhardened base image. The team had built it quickly during a sprint, used a community AMI as the base, and planned to harden it &#8220;in the next sprint.&#8221;</p>
<p>Three weeks later, a penetration test found it. SSH password authentication enabled. Three unnecessary services running — one of them with a known CVE. The finding: the instance had full inbound access from the VPC and was reachable from a compromised adjacent instance.</p>
<p>The deployment had gone through the normal CI/CD pipeline. Unit tests passed. Integration tests passed. A vulnerability scan ran. The scan produced a report that went to a dashboard. Nobody had a gate set up to fail the build if the image was unhardened.</p>
<p>The hardening work from the &#8220;next sprint&#8221; plan would have taken four hours. The pentest remediation took a week, plus the time to investigate what had been exposed during the three weeks the instance was running.</p>
<p>The CI/CD pipeline had every check except the one that would have caught the base image problem before the first deployment.</p>
<hr />
<h2 id="the-pipeline-api">The Pipeline API</h2>
<p>The Pipeline API is a single HTTP endpoint that takes an image ID, scans it, and returns a verdict:</p>
<pre><code class="" data-line="">curl -s -X POST https://bakex.yourdomain.com/api/pipeline/scan \
  -H &quot;X-API-Key: ${BAKEX_TOKEN}&quot; \
  -H &quot;Content-Type: application/json&quot; \
  -d &#039;{
    &quot;image_id&quot;: &quot;ami-0a7f3c9e82d1b4c05&quot;,
    &quot;provider&quot;: &quot;aws&quot;,
    &quot;region&quot;: &quot;us-east-1&quot;,
    &quot;pass_threshold&quot;: 75.0,
    &quot;severity_threshold&quot;: &quot;high&quot;,
    &quot;wait&quot;: true
  }&#039;
</code></pre>
<p>Authentication takes either <code class="" data-line="">X-API-Key</code> or <code class="" data-line="">Authorization: Bearer</code>; keys are created at<br />
<code class="" data-line="">/settings/api-keys</code>. With <code class="" data-line="">wait: true</code> the request blocks until the scan completes — which is what<br />
you want in CI, where a job that returns before the answer exists is worse than a slow one. There&#8217;s<br />
a <code class="" data-line="">timeout_seconds</code> (default 900) for when it doesn&#8217;t.</p>
<p>The response is the same shape whether you passed or failed:</p>
<pre><code class="" data-line="">{
  &quot;job_id&quot;: &quot;7f3c9e82-4d1b-4c05-a7f3-c9e82d1b4c05&quot;,
  &quot;status&quot;: &quot;complete&quot;,
  &quot;passed&quot;: false,
  &quot;grade&quot;: &quot;C&quot;,
  &quot;score_pct&quot;: 72.0,
  &quot;severity_counts&quot;: { &quot;critical&quot;: 0, &quot;high&quot;: 2, &quot;medium&quot;: 5, &quot;low&quot;: 11 },
  &quot;threshold_violations&quot;: [&quot;high&quot;],
  &quot;pass_threshold&quot;: 75.0,
  &quot;severity_threshold&quot;: &quot;high&quot;,
  &quot;image_id&quot;: &quot;ami-0c9d5e3f81a2b6e07&quot;,
  &quot;sarif_url&quot;: &quot;.../api/auditor/scan-image/7f3c9e82.../report?fmt=sarif&quot;,
  &quot;html_report_url&quot;: &quot;.../api/auditor/scan-image/7f3c9e82.../report&quot;
}
</code></pre>
<h3 id="the-detail-that-will-silently-break-your-gate">The detail that will silently break your gate</h3>
<p><strong>A failed gate still returns HTTP 200.</strong> There is no 4xx on failure — the verdict is in the<br />
<code class="" data-line="">passed</code> field, not the status code.</p>
<p>That means the pattern everyone reaches for first is wrong:</p>
<pre><code class="" data-line=""># WRONG — this never fails. -f only reacts to HTTP &gt;= 400,
# and a failed gate returns 200.
curl -sf -X POST .../api/pipeline/scan -d &#039;...&#039; || exit 1
</code></pre>
<p>You have to read the body:</p>
<pre><code class="" data-line=""># RIGHT
RESULT=$(curl -s -X POST &quot;${BAKEX_URL}/api/pipeline/scan&quot; \
  -H &quot;X-API-Key: ${BAKEX_TOKEN}&quot; \
  -H &quot;Content-Type: application/json&quot; \
  -d &quot;{\&quot;image_id\&quot;: \&quot;${AMI_ID}\&quot;, \&quot;pass_threshold\&quot;: 75.0, \&quot;severity_threshold\&quot;: \&quot;high\&quot;}&quot;)

echo &quot;$RESULT&quot; | jq -r &#039;&quot;grade=\(.grade) score=\(.score_pct) passed=\(.passed)&quot;&#039;

if [ &quot;$(echo &quot;$RESULT&quot; | jq -r &#039;.passed&#039;)&quot; != &quot;true&quot; ]; then
  echo &quot;Compliance gate failed — violations: $(echo &quot;$RESULT&quot; | jq -c &#039;.threshold_violations&#039;)&quot;
  echo &quot;Report: $(echo &quot;$RESULT&quot; | jq -r &#039;.html_report_url&#039;)&quot;
  exit 1
fi
</code></pre>
<p>A gate that reports failure and exits 0 is worse than no gate, because it produces a green<br />
pipeline and the belief that something was checked.</p>
<h3 id="two-thresholds-not-one">Two thresholds, not one</h3>
<p><code class="" data-line="">passed</code> is the AND of two independent conditions:</p>
<pre><code class="" data-line="">passed = (score_pct &gt;= pass_threshold) AND (no findings at or above severity_threshold)
</code></pre>
<p><code class="" data-line="">severity_threshold: &quot;high&quot;</code> means any <code class="" data-line="">critical</code> or <code class="" data-line="">high</code> finding fails the build regardless of<br />
score. An image can score 94 — a comfortable A — and still fail on a single critical finding. That<br />
is the right default: scores average away the thing that gets you breached.</p>
<hr />
<h2 id="github-actions-integration">GitHub Actions Integration</h2>
<pre><code class="" data-line=""># .github/workflows/deploy.yml

jobs:
  build-image:
    runs-on: ubuntu-latest
    outputs:
      ami_id: ${{ steps.build.outputs.ami_id }}
    steps:
      - name: Build hardened AMI
        id: build
        run: |
          AMI_ID=$(bakex build blueprints/ubuntu/22.04/cis-l1-aws.yaml --json \
            | jq -r &#039;.artifact_id&#039;)
          echo &quot;ami_id=${AMI_ID}&quot; &gt;&gt; $GITHUB_OUTPUT

  compliance-gate:
    runs-on: ubuntu-latest
    needs: build-image
    steps:
      - name: BakeX compliance gate
        run: |
          RESULT=$(curl -s -X POST ${{ vars.BAKEX_URL }}/api/pipeline/scan \
            -H &quot;X-API-Key: ${{ secrets.BAKEX_TOKEN }}&quot; \
            -H &quot;Content-Type: application/json&quot; \
            -d &quot;{\&quot;image_id\&quot;: \&quot;${{ needs.build-image.outputs.ami_id }}\&quot;,
                 \&quot;pass_threshold\&quot;: 75.0, \&quot;severity_threshold\&quot;: \&quot;high\&quot;}&quot;)

          echo &quot;$RESULT&quot; | jq -r &#039;&quot;grade=\(.grade) score=\(.score_pct)&quot;&#039;

          # Must check .passed — the endpoint returns 200 on failure
          if [ &quot;$(echo &quot;$RESULT&quot; | jq -r &#039;.passed&#039;)&quot; != &quot;true&quot; ]; then
            echo &quot;::error::Compliance gate failed: $(echo &quot;$RESULT&quot; | jq -c &#039;.threshold_violations&#039;)&quot;
            exit 1
          fi

      - name: Upload SARIF to code scanning
        if: always()
        run: |
          curl -s -o bakex.sarif &quot;$(echo &quot;$RESULT&quot; | jq -r &#039;.sarif_url&#039;)&quot;
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: bakex.sarif

  deploy:
    runs-on: ubuntu-latest
    needs: [build-image, compliance-gate]
    steps:
      - name: Deploy to staging
        run: |
          aws autoscaling update-auto-scaling-group \
            --auto-scaling-group-name my-asg \
            --launch-template &quot;ImageId=${{ needs.build-image.outputs.ami_id }}&quot;
</code></pre>
<p>The <code class="" data-line="">deploy</code> job only runs if <code class="" data-line="">compliance-gate</code> passes. The AMI doesn&#8217;t reach the autoscaling group if it doesn&#8217;t meet the grade threshold.</p>
<hr />
<h2 id="gitlab-ci-integration">GitLab CI Integration</h2>
<pre><code class="" data-line=""># .gitlab-ci.yml

stages:
  - build
  - compliance
  - deploy

build-image:
  stage: build
  script:
    - |
      AMI_ID=$(bakex build blueprints/ubuntu/22.04/cis-l1-aws.yaml --json \
        | jq -r &#039;.artifact_id&#039;)
      echo &quot;AMI_ID=${AMI_ID}&quot; &gt;&gt; build.env
  artifacts:
    reports:
      dotenv: build.env

compliance-gate:
  stage: compliance
  needs: [build-image]
  script:
    - |
      RESULT=$(curl -s -X POST ${BAKEX_URL}/api/pipeline/scan \
        -H &quot;X-API-Key: ${BAKEX_TOKEN}&quot; \
        -H &quot;Content-Type: application/json&quot; \
        -d &quot;{\&quot;image_id\&quot;: \&quot;${AMI_ID}\&quot;, \&quot;pass_threshold\&quot;: 75.0,
             \&quot;severity_threshold\&quot;: \&quot;high\&quot;}&quot;)
      echo &quot;$RESULT&quot; | jq -r &#039;&quot;grade=\(.grade) score=\(.score_pct) passed=\(.passed)&quot;&#039;
      test &quot;$(echo &quot;$RESULT&quot; | jq -r &#039;.passed&#039;)&quot; = &quot;true&quot;

deploy:
  stage: deploy
  needs: [build-image, compliance-gate]
  script:
    - ./deploy.sh ${AMI_ID}
</code></pre>
<hr />
<h2 id="what-the-failed-gate-tells-you">What the Failed Gate Tells You</h2>
<p>The value of the CI/CD compliance gate is not just that it blocks bad images — it&#8217;s that the failure output tells engineers what to fix.</p>
<p>The response carries three things an engineer can act on immediately:</p>
<pre><code class="" data-line="">$ echo &quot;$RESULT&quot; | jq &#039;{grade, score_pct, threshold_violations, severity_counts}&#039;
{
  &quot;grade&quot;: &quot;C&quot;,
  &quot;score_pct&quot;: 72.0,
  &quot;threshold_violations&quot;: [&quot;high&quot;],
  &quot;severity_counts&quot;: { &quot;critical&quot;: 0, &quot;high&quot;: 2, &quot;medium&quot;: 5, &quot;low&quot;: 11 }
}
</code></pre>
<p><code class="" data-line="">threshold_violations</code> names the severities that broke the gate — here, two <code class="" data-line="">high</code> findings, not the<br />
score. That distinction matters: an engineer who reads &#8220;grade C&#8221; starts a broad hardening project,<br />
while one who reads &#8220;two high findings&#8221; goes and fixes two things.</p>
<p>For the rule-level detail, follow <code class="" data-line="">sarif_url</code>. Pushing that SARIF into GitHub code scanning (as in<br />
the workflow above) puts each finding on the pull request diff, which is where someone will actually<br />
read it — a link to a dashboard in a CI log is a link nobody clicks.</p>
<hr />
<h2 id="thresholds-by-environment">Thresholds by Environment</h2>
<p>Not all environments need the same bar, and both dimensions are per-request — so the environment<br />
distinction lives in your pipeline, not in BakeX config:</p>
<pre><code class="" data-line=""># Production — high score floor, nothing high or above
PASS=90.0 ; SEV=high

# Staging — lower floor, still no criticals
PASS=75.0 ; SEV=critical

# Development — score only, severity effectively off
PASS=60.0 ; SEV=low

curl -s -X POST &quot;${BAKEX_URL}/api/pipeline/scan&quot; \
  -H &quot;X-API-Key: ${BAKEX_TOKEN}&quot; -H &quot;Content-Type: application/json&quot; \
  -d &quot;{\&quot;image_id\&quot;: \&quot;${AMI_ID}\&quot;, \&quot;pass_threshold\&quot;: ${PASS}, \&quot;severity_threshold\&quot;: \&quot;${SEV}\&quot;}&quot;
</code></pre>
<p>Note that <code class="" data-line="">severity_threshold</code> gets <em>stricter</em> as it goes down the list: <code class="" data-line="">low</code> fails on any finding<br />
at all, <code class="" data-line="">critical</code> fails only on criticals. It reads backwards the first time. Development wanting a<br />
permissive gate wants <code class="" data-line="">critical</code>, not <code class="" data-line="">low</code>.</p>
<hr />
<h2 id="production-gotchas">Production Gotchas</h2>
<p><strong>The 200-on-failure behaviour is the whole ballgame.</strong> Repeating it because it is the one thing that<br />
turns this page from useful to harmful if missed: check <code class="" data-line="">.passed</code>. Never rely on <code class="" data-line="">curl -f</code>, and never<br />
rely on the HTTP status.</p>
<p><strong>Scans take minutes, and <code class="" data-line="">wait: true</code> blocks.</strong> The endpoint provisions an instance from the image<br />
and scans it. With <code class="" data-line="">wait: true</code> your CI job blocks for the duration; <code class="" data-line="">timeout_seconds</code> defaults to<br />
900. Set your CI step timeout above that, or use <code class="" data-line="">wait: false</code> and poll <code class="" data-line="">GET /api/pipeline/scan/{job_id}</code>.</p>
<p><strong>Token rotation.</strong> The API key should rotate on the same schedule as other service credentials, and<br />
environments should use different keys — a leaked staging key must not be able to satisfy a<br />
production gate.</p>
<p><strong>The gate needs a reachable BakeX server.</strong> This is an HTTP API, not a self-contained action: the<br />
runner must reach the BakeX instance, and that instance needs cloud credentials for the provider<br />
whose image it is scanning.</p>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>A CI/CD compliance gate turns a compliance grade from a dashboard metric into a pipeline constraint — the image doesn&#8217;t deploy if it doesn&#8217;t pass</li>
<li><code class="" data-line="">POST /api/pipeline/scan</code> is a single HTTP call that any CI/CD system can make — no agent, no plugin, no SDK required</li>
<li><strong>The endpoint returns 200 even when the gate fails.</strong> Parse <code class="" data-line="">.passed</code>; <code class="" data-line="">curl -sf || exit 1</code> produces a green pipeline and a false sense of security</li>
<li>The verdict is two-dimensional — a score floor AND a severity ceiling — so a single critical finding blocks an image that scores 94</li>
<li><code class="" data-line="">threshold_violations</code> tells an engineer <em>why</em> it failed, which is the difference between &#8220;fix two high findings&#8221; and &#8220;start a hardening project&#8221;</li>
<li>Push the <code class="" data-line="">sarif_url</code> into GitHub code scanning so findings land on the pull request, not in a CI log</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p>The CI/CD compliance gate closes the final gap: even if an unhardened image gets built, it can&#8217;t deploy. EP05 is the bookmark episode — this is the point where OS hardening becomes structurally enforced rather than procedurally expected.</p>
<p>EP06 is the series closer. For five episodes, you&#8217;ve been using BakeX as a user. What does it look like to run it yourself — extend it with a custom provider, deploy it in your own infrastructure, or contribute a blueprint back?</p>
<p>BakeX is Apache 2.0. EP06 is the architecture reveal, the deployment guide, and the extension points for everything the series taught.</p>
<p><em>Next: <a href="/stratum-os-hardening-platform/">BakeX — open-source OS hardening platform for multi-cloud infrastructure</a></em></p>
<p>Get EP06 in your inbox when it publishes → <a href="https://linuxcent.com/subscribe">linuxcent.com/subscribe</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Fhardened-image-cicd-pipeline-gate%2F&amp;linkname=The%20Pipeline%20Gate%20%E2%80%94%20Hardened%20Images%20as%20a%20CI%2FCD%20Build%20Constraint" title="Mastodon" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Flinuxcent.com%2Fhardened-image-cicd-pipeline-gate%2F&amp;linkname=The%20Pipeline%20Gate%20%E2%80%94%20Hardened%20Images%20as%20a%20CI%2FCD%20Build%20Constraint" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_whatsapp" href="https://www.addtoany.com/add_to/whatsapp?linkurl=https%3A%2F%2Flinuxcent.com%2Fhardened-image-cicd-pipeline-gate%2F&amp;linkname=The%20Pipeline%20Gate%20%E2%80%94%20Hardened%20Images%20as%20a%20CI%2FCD%20Build%20Constraint" title="WhatsApp" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Flinuxcent.com%2Fhardened-image-cicd-pipeline-gate%2F&amp;linkname=The%20Pipeline%20Gate%20%E2%80%94%20Hardened%20Images%20as%20a%20CI%2FCD%20Build%20Constraint" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_x" href="https://www.addtoany.com/add_to/x?linkurl=https%3A%2F%2Flinuxcent.com%2Fhardened-image-cicd-pipeline-gate%2F&amp;linkname=The%20Pipeline%20Gate%20%E2%80%94%20Hardened%20Images%20as%20a%20CI%2FCD%20Build%20Constraint" title="X" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Flinuxcent.com%2Fhardened-image-cicd-pipeline-gate%2F&amp;linkname=The%20Pipeline%20Gate%20%E2%80%94%20Hardened%20Images%20as%20a%20CI%2FCD%20Build%20Constraint" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_copy_link" href="https://www.addtoany.com/add_to/copy_link?linkurl=https%3A%2F%2Flinuxcent.com%2Fhardened-image-cicd-pipeline-gate%2F&amp;linkname=The%20Pipeline%20Gate%20%E2%80%94%20Hardened%20Images%20as%20a%20CI%2FCD%20Build%20Constraint" title="Copy Link" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Flinuxcent.com%2Fhardened-image-cicd-pipeline-gate%2F&#038;title=The%20Pipeline%20Gate%20%E2%80%94%20Hardened%20Images%20as%20a%20CI%2FCD%20Build%20Constraint" data-a2a-url="https://linuxcent.com/hardened-image-cicd-pipeline-gate/" data-a2a-title="The Pipeline Gate — Hardened Images as a CI/CD Build Constraint"></a></p><p>The post <a href="https://linuxcent.com/hardened-image-cicd-pipeline-gate/">The Pipeline Gate — Hardened Images as a CI/CD Build Constraint</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/hardened-image-cicd-pipeline-gate/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1831</post-id>	</item>
		<item>
		<title>The Platform Engineering Era: GitOps, AI Workloads, and Leaner Kubernetes (2023–2025)</title>
		<link>https://linuxcent.com/kubernetes-platform-engineering-gitops/</link>
					<comments>https://linuxcent.com/kubernetes-platform-engineering-gitops/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Wed, 15 Apr 2026 19:31:32 +0000</pubDate>
				<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[AI Infrastructure]]></category>
		<category><![CDATA[ArgoCD]]></category>
		<category><![CDATA[Cloud Native]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[GitOps]]></category>
		<category><![CDATA[Platform Engineering]]></category>
		<guid isPermaLink="false">https://linuxcent.com/kubernetes-platform-engineering-gitops/</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 6</span> <span class="rt-label rt-postfix">minutes</span></span>GitOps became the default deployment model. AI workloads hit production clusters. Cluster API matured. Trace how v1.28–v1.32 made Kubernetes the substrate for platform engineering.</p>
<p>The post <a href="https://linuxcent.com/kubernetes-platform-engineering-gitops/">The Platform Engineering Era: GitOps, AI Workloads, and Leaner Kubernetes (2023–2025)</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 6</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;<br />
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;<br />
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;<br />
    font-size:.88em;line-height:1.6;border-left:4px solid #555}<br />
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}<br />
pre code{background:transparent;padding:0;color:inherit}<br />
pre[data-lang="bash"],pre[data-lang="sh"],<br />
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}<br />
pre[data-lang="yaml"],pre[data-lang="json"],<br />
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}<br />
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],<br />
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}<br />
pre[data-lang="text"],pre[data-lang="output"],<br />
pre[data-lang="console"]{border-left-color:#888}<br />
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;<br />
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;<br />
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;<br />
    transition:opacity .15s,background .15s;line-height:1.6}<br />
pre:hover .lc-copy-btn{opacity:1}<br />
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}<br />
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}<br />
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;<br />
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;<br />
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}<br />
pre:hover .lc-lang-badge{opacity:1}<br />
table{border-collapse:collapse;width:100%;margin:16px 0}<br />
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}<br />
th{background:#f0f0f0;font-weight:600}<br />
tr:nth-child(even){background:#fafafa}<br />
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script></p>
<hr />
<h2 id="introduction">Introduction</h2>
<p>By 2023, the question had shifted from &#8220;how do we run Kubernetes?&#8221; to &#8220;how do we let other engineers run their workloads on Kubernetes without becoming a bottleneck?&#8221;</p>
<p>This is the platform engineering problem. And it drove the tooling that defined 2023–2025: GitOps as the deployment standard, Cluster API for Kubernetes-on-Kubernetes provisioning, AI/ML workloads forcing new scheduling capabilities, and the Kubernetes project itself shedding more weight to become faster to release and operate.</p>
<hr />
<h2 id="gitops-principle-becomes-practice">GitOps: Principle Becomes Practice</h2>
<p>GitOps as a term was coined by Weaveworks in 2017. By 2023, it was no longer a debate — it was the default deployment model for organizations running Kubernetes at scale.</p>
<p>The principle: the desired state of your cluster lives in Git. A controller watches the repository and reconciles the cluster state to match. Every deployment is a PR merge. The audit trail is the Git history.</p>
<p><strong>Flux v2</strong> (CNCF graduated) and <strong>ArgoCD</strong> (CNCF incubating) became the two dominant implementations:</p>
<pre><code class="" data-line=""># Flux: GitRepository + Kustomization
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: production-config
  namespace: flux-system
spec:
  interval: 1m
  url: https://github.com/org/k8s-config
  ref:
    branch: main
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: production-apps
  namespace: flux-system
spec:
  interval: 10m
  path: ./clusters/production
  prune: true          # Remove resources deleted from Git
  sourceRef:
    kind: GitRepository
    name: production-config
  healthChecks:
  - apiVersion: apps/v1
    kind: Deployment
    name: api
    namespace: production
</code></pre>
<p>The <code class="" data-line="">prune: true</code> behavior is critical: resources deleted from Git are deleted from the cluster. This is what makes GitOps a security control — unknown resources that aren&#8217;t in Git get removed. No more accumulation of forgotten test deployments, rogue debug pods, or unauthorized configuration changes that outlive the engineer who made them.</p>
<p><strong>ArgoCD&#8217;s Application model</strong> added a UI, synchronization policies, and multi-cluster management:</p>
<pre><code class="" data-line="">apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: production-api
  namespace: argocd
spec:
  project: production
  source:
    repoURL: https://github.com/org/apps
    targetRevision: HEAD
    path: api/production
  destination:
    server: https://kubernetes.default.svc
    namespace: api
  syncPolicy:
    automated:
      prune: true
      selfHeal: true    # Revert manual kubectl changes
    syncOptions:
    - CreateNamespace=true
</code></pre>
<p>The <code class="" data-line="">selfHeal: true</code> option is where GitOps becomes enforceable: any manual change made with kubectl is automatically reverted within the sync interval. For compliance-sensitive environments, this is a configuration drift prevention control.</p>
<hr />
<h2 id="cluster-api-kubernetes-managing-kubernetes">Cluster API: Kubernetes Managing Kubernetes</h2>
<p><strong>Cluster API</strong> (cluster-sigs/cluster-api) flipped the usual model: instead of using tools like Terraform or Ansible to provision Kubernetes clusters, Cluster API lets you manage Kubernetes clusters as Kubernetes resources — using a management cluster to provision and manage workload clusters.</p>
<pre><code class="" data-line=""># Create a new Kubernetes cluster as a Kubernetes resource
apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
  name: workload-cluster-prod
spec:
  clusterNetwork:
    pods:
      cidrBlocks: [&quot;192.168.0.0/16&quot;]
  infrastructureRef:
    apiVersion: infrastructure.cluster.x-k8s.io/v1beta2
    kind: AWSCluster
    name: workload-cluster-prod
  controlPlaneRef:
    apiVersion: controlplane.cluster.x-k8s.io/v1beta1
    kind: KubeadmControlPlane
    name: workload-cluster-prod-control-plane
</code></pre>
<p>Cluster API reconciliation handles cluster provisioning, scaling, upgrades, and deletion — all through the Kubernetes API, with all the tooling (RBAC, audit logging, GitOps integration) that entails. Multi-cluster platform teams could now manage hundreds of workload clusters from a single management cluster.</p>
<hr />
<h2 id="kubernetes-128-sidecar-containers-alpha-august-2023">Kubernetes 1.28 — Sidecar Containers Alpha (August 2023)</h2>
<p>Sidecar containers had been a Kubernetes pattern since 2015 — a helper container in the same pod as the main application. But there was no native sidecar lifecycle management. Sidecars were just regular init containers or additional containers, which meant:<br />
&#8211; Init container sidecars ran before the application and had to block until they succeeded<br />
&#8211; Regular container sidecars had no ordering guarantees at startup<br />
&#8211; At pod termination, sidecars could die before the application finished draining</p>
<p>1.28 introduced native sidecar support: <strong>a new restartPolicy field for init containers</strong>:</p>
<pre><code class="" data-line="">spec:
  initContainers:
  - name: log-collector
    image: fluentbit:latest
    restartPolicy: Always    # This makes it a sidecar
    # Starts before main containers, stays running, stops after main containers exit
  containers:
  - name: application
    image: myapp:latest
</code></pre>
<p>A sidecar container (init container with <code class="" data-line="">restartPolicy: Always</code>):<br />
&#8211; Starts before application containers<br />
&#8211; Stays running throughout the pod lifecycle<br />
&#8211; Terminates automatically after all main containers exit<br />
&#8211; Restarts if it crashes (unlike regular init containers)</p>
<p>This solved the service mesh sidecar problem: Istio and Linkerd injected Envoy proxies as regular containers, leading to race conditions where the proxy hadn&#8217;t started when the application tried to make outbound connections. Native sidecar lifecycle guarantees the proxy is ready before the application starts.</p>
<p>Also in 1.28:<br />
&#8211; <strong>Retroactive default StorageClass assignment</strong>: Existing PVCs without a StorageClass assignment get the default applied retroactively — useful for migrations<br />
&#8211; <strong>Non-graceful node shutdown</strong> stable: Handle node power failures without manual pod cleanup<br />
&#8211; <strong>Recovery from volume expansion failure</strong>: Previously, a failed volume expansion left the PVC in a broken state; 1.28 introduced a mechanism to recover</p>
<hr />
<h2 id="aiml-workloads-force-new-kubernetes-capabilities">AI/ML Workloads Force New Kubernetes Capabilities</h2>
<p>The LLM wave of 2023 drove GPU workloads onto Kubernetes at a scale and urgency the project hadn&#8217;t anticipated. Running LLM inference on Kubernetes required solving problems that CPU-centric cluster scheduling hadn&#8217;t encountered:</p>
<p><strong>GPU topology awareness</strong>: Inference across multiple GPUs requires GPUs connected by NVLink or on the same PCIe switch, not arbitrary GPUs from different nodes or different PCIe buses. The Dynamic Resource Allocation API (1.26 alpha) was designed exactly for this.</p>
<p><strong>Fractional GPU allocation</strong>: NVIDIA&#8217;s time-slicing and MIG (Multi-Instance GPU) allow multiple pods to share a single GPU. The GPU operator (NVIDIA) manages this at the node level:</p>
<pre><code class="" data-line=""># Check GPU resources visible to Kubernetes
kubectl get nodes -o custom-columns=\
  &quot;NODE:.metadata.name,GPU:.status.allocatable.nvidia\.com/gpu&quot;
# NODE       GPU
# gpu-node-1   8
# gpu-node-2   8
</code></pre>
<p><strong>Batch scheduling for training jobs</strong>: Training runs require all workers to start simultaneously — a single missing GPU makes the entire job stall. The Kubernetes Job API doesn&#8217;t guarantee this. Projects like <strong>Volcano</strong> (CNCF incubating) and <strong>Kueue</strong> (Kubernetes SIG Scheduling) added gang scheduling: a job only starts when all requested resources are available.</p>
<pre><code class="" data-line=""># Kueue: queue AI training jobs with resource quotas
apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
  name: gpu-queue
spec:
  namespaceSelector: {}
  resourceGroups:
  - coveredResources: [&quot;nvidia.com/gpu&quot;, &quot;cpu&quot;, &quot;memory&quot;]
    flavors:
    - name: a100-80gb
      resources:
      - name: nvidia.com/gpu
        nominalQuota: 16
</code></pre>
<hr />
<h2 id="kubernetes-129-sidecar-to-beta-load-balancer-ip-mode-december-2023">Kubernetes 1.29 — Sidecar to Beta, Load Balancer IP Mode (December 2023)</h2>
<ul>
<li><strong>Sidecar containers</strong> beta: The lifecycle semantics were refined based on 1.28 alpha feedback</li>
<li><strong>Load balancer IP mode</strong> alpha: Distinguish between load balancers that use virtual IPs (kube-proxy handles the traffic) vs. those that handle traffic directly (no need for kube-proxy rules) — important for eBPF-based load balancers</li>
<li><strong>ReadWriteOncePod volume access</strong> stable</li>
</ul>
<hr />
<h2 id="kubernetes-130-structured-authorization-config-april-2024">Kubernetes 1.30 — Structured Authorization Config (April 2024)</h2>
<ul>
<li><strong>Structured authorization configuration</strong> beta: Define multiple authorization webhooks with explicit ordering, failure modes, and connection settings — replacing the flat <code class="" data-line="">--authorization-mode</code> flag</li>
<li><strong>Sidecar containers</strong> beta continues</li>
<li><strong>Node memory swap support</strong> beta: Allow pods to use swap memory — controversial but necessary for workloads with bursty memory patterns that prefer using swap over OOM kill</li>
</ul>
<pre><code class="" data-line=""># Node with swap enabled — kubelet config
kind: KubeletConfiguration
memorySwap:
  swapBehavior: LimitedSwap
</code></pre>
<p>The swap support feature reversed a long-standing Kubernetes hard stance: swap was disabled since 1.0 because its interaction with Kubernetes memory accounting was unpredictable. The 1.30 approach adds proper accounting and policies.</p>
<hr />
<h2 id="kubernetes-131-cloud-provider-code-removal-complete-august-2024">Kubernetes 1.31 — Cloud Provider Code Removal Complete (August 2024)</h2>
<p>1.31 marked the completion of the cloud provider code removal — the 1.5 million line migration that had been running since 1.26. Core binaries are 40% smaller. The API server, controller manager, and scheduler no longer contain vendor-specific code.</p>
<p>Also in 1.31:<br />
&#8211; <strong>Persistent Volume health monitor</strong> stable<br />
&#8211; <strong>AppArmor support</strong> stable: AppArmor profiles for pods using the native Kubernetes field (not annotations)<br />
&#8211; <strong>Traffic distribution for Services</strong> beta: Express topology preferences for Service routing (prefer local node, prefer same zone)</p>
<pre><code class="" data-line=""># Traffic distribution: prefer endpoints in the same zone
apiVersion: v1
kind: Service
metadata:
  name: api
spec:
  trafficDistribution: PreferClose
  selector:
    app: api
  ports:
  - port: 80
    targetPort: 8080
</code></pre>
<hr />
<h2 id="kubernetes-132-sidecar-stable-dra-beta-december-2024">Kubernetes 1.32 — Sidecar Stable, DRA Beta (December 2024)</h2>
<ul>
<li><strong>Sidecar containers</strong> stable: After nearly a decade of workarounds, the sidecar pattern is a first-class Kubernetes primitive</li>
<li><strong>Dynamic Resource Allocation</strong> beta: GPU and specialized hardware scheduling ready for production evaluation</li>
<li><strong>Job API improvements</strong>: Success and failure policies for indexed jobs — granular control over batch workload behavior</li>
<li><strong>Custom Resource field selectors</strong>: Filter CRDs on arbitrary fields — making large CRD-based systems more efficient to query</li>
</ul>
<hr />
<h2 id="crossplane-kubernetes-as-the-control-plane-for-everything">Crossplane: Kubernetes as the Control Plane for Everything</h2>
<p>Crossplane (CNCF graduated) extended the Kubernetes API model beyond the cluster itself. Using CRDs and controllers, Crossplane lets you manage cloud resources (RDS databases, S3 buckets, VPCs, IAM roles) as Kubernetes resources — provisioned, updated, and deleted through the Kubernetes API.</p>
<pre><code class="" data-line=""># Crossplane: provision an RDS PostgreSQL instance as a Kubernetes resource
apiVersion: database.aws.crossplane.io/v1beta1
kind: RDSInstance
metadata:
  name: production-db
spec:
  forProvider:
    region: us-east-1
    dbInstanceClass: db.r6g.xlarge
    masterUsername: admin
    engine: postgres
    engineVersion: &quot;15&quot;
    allocatedStorage: 100
    multiAZ: true
  writeConnectionSecretsToRef:
    name: production-db-credentials
    namespace: production
</code></pre>
<p>For platform teams, Crossplane means a single control plane — the Kubernetes API — for both compute workloads and cloud infrastructure. GitOps tools (Flux, ArgoCD) manage both.</p>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>GitOps (Flux, ArgoCD) became the production deployment standard — not for ideological reasons, but because the audit trail, drift detection, and self-healing properties solve real operational and compliance problems</li>
<li>Cluster API made Kubernetes cluster lifecycle (provisioning, upgrades, deletion) a Kubernetes-native operation — the same API, tooling, and audit trail</li>
<li>Native sidecar containers (1.28 alpha → 1.32 stable) finally resolved the lifecycle ordering problem that service meshes and log collectors had worked around for years</li>
<li>AI/ML workloads drove new scheduling capabilities (DRA, gang scheduling via Kueue/Volcano) and made GPU topology awareness a first-class concern</li>
<li>Crossplane generalized the Kubernetes API model to cloud infrastructure — the cluster is now a control plane for everything, not just containers</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p><a href="ep06-runtime-reckoning.md">← EP06: The Runtime Reckoning</a> | <a href="ep08-kubernetes-today.md">EP08: Kubernetes Today →</a></p>
<p><em>Series: Kubernetes: From Borg to Platform Engineering | linuxcent.com</em></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-platform-engineering-gitops%2F&amp;linkname=The%20Platform%20Engineering%20Era%3A%20GitOps%2C%20AI%20Workloads%2C%20and%20Leaner%20Kubernetes%20%282023%E2%80%932025%29" title="Mastodon" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-platform-engineering-gitops%2F&amp;linkname=The%20Platform%20Engineering%20Era%3A%20GitOps%2C%20AI%20Workloads%2C%20and%20Leaner%20Kubernetes%20%282023%E2%80%932025%29" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_whatsapp" href="https://www.addtoany.com/add_to/whatsapp?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-platform-engineering-gitops%2F&amp;linkname=The%20Platform%20Engineering%20Era%3A%20GitOps%2C%20AI%20Workloads%2C%20and%20Leaner%20Kubernetes%20%282023%E2%80%932025%29" title="WhatsApp" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-platform-engineering-gitops%2F&amp;linkname=The%20Platform%20Engineering%20Era%3A%20GitOps%2C%20AI%20Workloads%2C%20and%20Leaner%20Kubernetes%20%282023%E2%80%932025%29" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_x" href="https://www.addtoany.com/add_to/x?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-platform-engineering-gitops%2F&amp;linkname=The%20Platform%20Engineering%20Era%3A%20GitOps%2C%20AI%20Workloads%2C%20and%20Leaner%20Kubernetes%20%282023%E2%80%932025%29" title="X" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-platform-engineering-gitops%2F&amp;linkname=The%20Platform%20Engineering%20Era%3A%20GitOps%2C%20AI%20Workloads%2C%20and%20Leaner%20Kubernetes%20%282023%E2%80%932025%29" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_copy_link" href="https://www.addtoany.com/add_to/copy_link?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-platform-engineering-gitops%2F&amp;linkname=The%20Platform%20Engineering%20Era%3A%20GitOps%2C%20AI%20Workloads%2C%20and%20Leaner%20Kubernetes%20%282023%E2%80%932025%29" title="Copy Link" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Flinuxcent.com%2Fkubernetes-platform-engineering-gitops%2F&#038;title=The%20Platform%20Engineering%20Era%3A%20GitOps%2C%20AI%20Workloads%2C%20and%20Leaner%20Kubernetes%20%282023%E2%80%932025%29" data-a2a-url="https://linuxcent.com/kubernetes-platform-engineering-gitops/" data-a2a-title="The Platform Engineering Era: GitOps, AI Workloads, and Leaner Kubernetes (2023–2025)"></a></p><p>The post <a href="https://linuxcent.com/kubernetes-platform-engineering-gitops/">The Platform Engineering Era: GitOps, AI Workloads, and Leaner Kubernetes (2023–2025)</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/kubernetes-platform-engineering-gitops/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1659</post-id>	</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 

Served from: linuxcent.com @ 2026-08-27 00:20:01 by W3 Total Cache
-->