<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Devops on Kinoko's TIL Log</title><link>https://kinoko-tech-blog-theta.vercel.app/tags/devops/</link><description>Recent content in Devops on Kinoko's TIL Log</description><generator>Hugo -- gohugo.io</generator><language>en</language><managingEditor>pippimotta@gmail.com (Kinoko)</managingEditor><webMaster>pippimotta@gmail.com (Kinoko)</webMaster><copyright>&amp;copy; Brewed by Kinoko</copyright><lastBuildDate>Tue, 17 Mar 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://kinoko-tech-blog-theta.vercel.app/tags/devops/index.xml" rel="self" type="application/rss+xml"/><item><title>RBAC Model</title><link>https://kinoko-tech-blog-theta.vercel.app/posts/rbac-model/</link><pubDate>Tue, 17 Mar 2026 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/posts/rbac-model/</guid><description>&lt;h2 id="the-point"&gt;The Point&lt;/h2&gt;
&lt;p&gt;RBAC (Role-Based Access Control) is a permissions model where you first define what roles can do, then assign roles to people. K8s uses RBAC to control who can perform which operations on which resources. CD tools use RBAC to control who can deploy to which environment.&lt;/p&gt;
&lt;h2 id="explanation"&gt;Explanation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Three core elements of RBAC&lt;/strong&gt;&lt;/p&gt;





&lt;pre tabindex="0"&gt;&lt;code&gt;Subject (who) → RoleBinding (assignment) → Role (what they can do)&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Subject&lt;/strong&gt;: a user, group, or service account (programmatic identity)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Role&lt;/strong&gt;: defines a set of permissions (which operations on which resources)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RoleBinding&lt;/strong&gt;: assigns a Role to a Subject&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Intuitive analogy: a Role is a job title (engineer, manager), a RoleBinding is the &amp;ldquo;appointment letter,&amp;rdquo; and the Subject is the person being appointed.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="the-point">The Point</h2>
<p>RBAC (Role-Based Access Control) is a permissions model where you first define what roles can do, then assign roles to people. K8s uses RBAC to control who can perform which operations on which resources. CD tools use RBAC to control who can deploy to which environment.</p>
<h2 id="explanation">Explanation</h2>
<p><strong>Three core elements of RBAC</strong></p>





<pre tabindex="0"><code>Subject (who)  →  RoleBinding (assignment)  →  Role (what they can do)</code></pre><ul>
<li><strong>Subject</strong>: a user, group, or service account (programmatic identity)</li>
<li><strong>Role</strong>: defines a set of permissions (which operations on which resources)</li>
<li><strong>RoleBinding</strong>: assigns a Role to a Subject</li>
</ul>
<p>Intuitive analogy: a Role is a job title (engineer, manager), a RoleBinding is the &ldquo;appointment letter,&rdquo; and the Subject is the person being appointed.</p>
<hr>
<p><strong>RBAC in Kubernetes</strong></p>
<p>K8s RBAC controls &ldquo;who can do what against the K8s API.&rdquo;</p>
<p><strong>Resources &amp; Verbs</strong></p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nt">rules</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl">- <span class="nt">apiGroups</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">  </span><span class="nt">resources</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;pods&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;services&#34;</span><span class="p">]</span><span class="w">   </span><span class="c"># resource types</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">  </span><span class="nt">verbs</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;get&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;list&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;watch&#34;</span><span class="p">]</span><span class="w">   </span><span class="c"># allowed operations</span></span></span></code></pre></div><p>Common verbs: <code>get</code> <code>list</code> <code>watch</code> <code>create</code> <code>update</code> <code>patch</code> <code>delete</code></p>
<p><strong>Role vs ClusterRole</strong></p>
<table>
	<thead>
			<tr>
					<th></th>
					<th>Role</th>
					<th>ClusterRole</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Scope</td>
					<td>Single namespace</td>
					<td>Entire cluster</td>
			</tr>
			<tr>
					<td>Best for</td>
					<td>Restrict a team to their own namespace</td>
					<td>Cross-namespace or cluster-level resources (nodes, PVs)</td>
			</tr>
	</tbody>
</table>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># Role: only effective in the production namespace</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Role</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">production</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">pod-reader</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nt">rules</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl">- <span class="nt">apiGroups</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">  </span><span class="nt">resources</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;pods&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">  </span><span class="nt">verbs</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;get&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;list&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c"># RoleBinding: assign this Role to alice</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">RoleBinding</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">production</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="nt">subjects</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl">- <span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">User</span><span class="w">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">alice</span><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="nt">roleRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w">  </span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Role</span><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">pod-reader</span></span></span></code></pre></div><p><strong>Service Account: identity for programs</strong></p>
<p>Programs running in pods (like a CD tool agent) are not people. K8s uses Service Accounts to give them an identity, then uses RoleBinding to control what they can do:</p>





<pre tabindex="0"><code>CD tool agent (Pod)
  → uses ServiceAccount: octopus-agent
  → RoleBinding → ClusterRole: deploy-permissions
  → can apply manifests, update Deployments</code></pre><hr>
<p><strong>RBAC in CD Tools (Octopus)</strong></p>
<p>The CD tool has its own RBAC layer, controlling &ldquo;who can operate which project / environment&rdquo;:</p>





<pre tabindex="0"><code>Engineer alice
  → belongs to Team: backend-team
  → Team is assigned Role: deployer (can deploy, cannot change project settings)
  → restricted to Environment: dev, staging (cannot touch prod)</code></pre><p>Typical role hierarchy:</p>
<table>
	<thead>
			<tr>
					<th>Role</th>
					<th>Permissions</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Viewer</td>
					<td>Can only view deploy status</td>
			</tr>
			<tr>
					<td>Deployer</td>
					<td>Can trigger deploys</td>
			</tr>
			<tr>
					<td>Project Lead</td>
					<td>Can modify deploy settings</td>
			</tr>
			<tr>
					<td>Admin</td>
					<td>Full control</td>
			</tr>
	</tbody>
</table>
<h2 id="knowledge-sugar">Knowledge Sugar</h2>
<p><strong>Why does K8s need RBAC?</strong></p>
<p>The K8s API can do a lot &ndash; delete Pods, modify Secrets, scale Deployments. Without access control, anyone or any program that can connect to the cluster can do anything. RBAC lets you:</p>
<ul>
<li>Restrict devs to only operate within their own namespace</li>
<li>Give CI/CD agents only manifest apply permission, not Secret deletion</li>
<li>Give SRE cluster-level read access without prod write access</li>
</ul>
<p><strong>Principle of Least Privilege</strong></p>
<p>The design philosophy behind RBAC: give each subject only the minimum permissions needed to do its job. If a CD tool agent only needs <code>apply</code>, do not give it <code>delete</code>.</p>
<p>For CD tool background, see the WarpCD vs Octopus Deploy post.</p>
]]></content:encoded></item><item><title>WarpCD vs Octopus Deploy</title><link>https://kinoko-tech-blog-theta.vercel.app/posts/warpcd-vs-octopus-deploy/</link><pubDate>Tue, 17 Mar 2026 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/posts/warpcd-vs-octopus-deploy/</guid><description>&lt;h2 id="the-point"&gt;The Point&lt;/h2&gt;
&lt;p&gt;WarpSpeedCD (internally called WarpCD) is a GitOps pull model &amp;ndash; Git is the single source of truth, and a bot syncs changes to the cluster. Octopus is a push model &amp;ndash; a pipeline actively triggers deploys, with approval and release management handled in the Octopus UI. The fundamental difference is who drives the deploy.&lt;/p&gt;
&lt;h2 id="explanation"&gt;Explanation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;WarpCD (GitOps / Pull model)&lt;/strong&gt;&lt;/p&gt;





&lt;pre tabindex="0"&gt;&lt;code&gt;Code PR merged
 ↓
WarpCD bot opens a PR in the K8s repo (updates manifests)
 ↓
Manual review &amp;amp; approve PR
 ↓
PR merged → cluster auto-syncs (pull)&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Source of truth&lt;/strong&gt;: K8s manifests live in a Git repo; cluster state always follows Git&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audit trail&lt;/strong&gt;: every deploy is a Git commit, giving you history for free&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Approval&lt;/strong&gt;: through the PR review process, consistent with code review&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Octopus Deploy (Push model)&lt;/strong&gt;&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="the-point">The Point</h2>
<p>WarpSpeedCD (internally called WarpCD) is a GitOps pull model &ndash; Git is the single source of truth, and a bot syncs changes to the cluster. Octopus is a push model &ndash; a pipeline actively triggers deploys, with approval and release management handled in the Octopus UI. The fundamental difference is who drives the deploy.</p>
<h2 id="explanation">Explanation</h2>
<p><strong>WarpCD (GitOps / Pull model)</strong></p>





<pre tabindex="0"><code>Code PR merged
    ↓
WarpCD bot opens a PR in the K8s repo (updates manifests)
    ↓
Manual review &amp; approve PR
    ↓
PR merged → cluster auto-syncs (pull)</code></pre><ul>
<li><strong>Source of truth</strong>: K8s manifests live in a Git repo; cluster state always follows Git</li>
<li><strong>Audit trail</strong>: every deploy is a Git commit, giving you history for free</li>
<li><strong>Approval</strong>: through the PR review process, consistent with code review</li>
</ul>
<p><strong>Octopus Deploy (Push model)</strong></p>





<pre tabindex="0"><code>CI build completes → artifact (image) pushed to registry
    ↓
Octopus detects new version, creates a Release
    ↓
Promotion flow: dev → staging → prod (each stage can have an approval gate)
    ↓
Octopus actively applies manifests to the cluster (push)</code></pre><ul>
<li><strong>Source of truth</strong>: Octopus&rsquo;s Release and Project configuration</li>
<li><strong>Approval</strong>: approval gates configured in the Octopus UI, not through Git PRs</li>
<li><strong>Runbook</strong>: can run operational tasks (DB migration, rollback, smoke test)</li>
</ul>
<p><strong>Core differences</strong></p>
<table>
	<thead>
			<tr>
					<th></th>
					<th>WarpCD (GitOps)</th>
					<th>Octopus Deploy</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Deploy driver</td>
					<td>Cluster pulls from Git</td>
					<td>Pipeline pushes to cluster</td>
			</tr>
			<tr>
					<td>Approval location</td>
					<td>Git PR review</td>
					<td>Octopus UI approval gate</td>
			</tr>
			<tr>
					<td>Source of truth</td>
					<td>Git repo (K8s manifests)</td>
					<td>Octopus Release + Git</td>
			</tr>
			<tr>
					<td>Audit trail</td>
					<td>Git commit history</td>
					<td>Octopus deployment log</td>
			</tr>
			<tr>
					<td>Operational tasks</td>
					<td>Needs additional tooling</td>
					<td>Built-in Runbook</td>
			</tr>
			<tr>
					<td>Multi-env promotion</td>
					<td>Via branch / folder structure</td>
					<td>Built-in lifecycle (dev -&gt; stg -&gt; prod)</td>
			</tr>
	</tbody>
</table>
<h2 id="knowledge-sugar">Knowledge Sugar</h2>
<p><strong>How to do a CD pipeline migration?</strong></p>
<p>Switching from GitOps to Octopus is not just migrating YAML &ndash; it is <strong>replacing the entire deploy driver</strong>:</p>
<ol>
<li><strong>Artifacts stay the same</strong>: container images and Helm charts do not need changes</li>
<li><strong>K8s manifests may be kept</strong>: Octopus can use kubectl/Helm steps to apply existing manifests</li>
<li><strong>What actually migrates is the process</strong>:
<ul>
<li>Replace WarpCD bot&rsquo;s PR flow with Octopus&rsquo;s Release + Promotion</li>
<li>Replace Git PR approval with Octopus&rsquo;s approval gates</li>
<li>Create corresponding Projects + Steps in Octopus for each service&rsquo;s deploy config</li>
</ul>
</li>
</ol>
<p><strong>Typical migration steps</strong></p>





<pre tabindex="0"><code>1. Create environments in Octopus (dev / staging / prod)
2. Create an Octopus Project for each service, configure deploy steps
3. Set up approval gates to replace PR review
4. Run shadow mode (old and new in parallel) to verify results match
5. Once confirmed, turn off the WarpCD bot -- switchover complete</code></pre>]]></content:encoded></item><item><title>GitLab CI &amp; Docker-in-Docker</title><link>https://kinoko-tech-blog-theta.vercel.app/posts/gitlab-ci-docker-in-docker/</link><pubDate>Sat, 28 Feb 2026 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/posts/gitlab-ci-docker-in-docker/</guid><description>&lt;h2 id="the-point"&gt;The Point&lt;/h2&gt;
&lt;p&gt;Running &lt;code&gt;docker build&lt;/code&gt; inside GitLab CI does not work out of the box &amp;ndash; the job itself already runs in a container with no Docker daemon. You need &lt;strong&gt;Docker-in-Docker (DinD)&lt;/strong&gt; as a sidecar service to provide the daemon.&lt;/p&gt;
&lt;h2 id="explanation"&gt;Explanation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Overall structure&lt;/strong&gt;&lt;/p&gt;





&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;stages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;test &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# runs first&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;build &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# runs after (only if all test jobs pass)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Jobs in the same stage run in parallel; stages run sequentially.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="the-point">The Point</h2>
<p>Running <code>docker build</code> inside GitLab CI does not work out of the box &ndash; the job itself already runs in a container with no Docker daemon. You need <strong>Docker-in-Docker (DinD)</strong> as a sidecar service to provide the daemon.</p>
<h2 id="explanation">Explanation</h2>
<p><strong>Overall structure</strong></p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">  </span>- <span class="l">test   </span><span class="w"> </span><span class="c"># runs first</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">  </span>- <span class="l">build  </span><span class="w"> </span><span class="c"># runs after (only if all test jobs pass)</span></span></span></code></pre></div><p>Jobs in the same stage run in parallel; stages run sequentially.</p>
<hr>
<p><strong><code>run_tests</code> job &ndash; typical job structure</strong></p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nt">run_tests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">python:3.11-slim </span><span class="w"> </span><span class="c"># set up container based on this image where job runs</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">  </span><span class="nt">before_script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">    </span>- <span class="l">apt-get update &amp;&amp; apt-get install -y make gcc python3-dev</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="w">    </span>- <span class="l">make test</span></span></span></code></pre></div><ul>
<li><code>image</code>: which Docker image to use as the execution environment for this job</li>
<li><code>before_script</code>: runs before every <code>script</code>, good for dependency installation</li>
<li><code>script</code>: the main commands to execute</li>
</ul>
<hr>
<p><strong><code>build_image</code> job &ndash; Docker-specific details</strong></p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="nt">build_image</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">build</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker:20.10.16          </span><span class="w"> </span><span class="c"># Docker CLI</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">  </span><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">    </span>- <span class="l">docker:20.10.16-dind        </span><span class="w"> </span><span class="c"># Docker daemon (sidecar)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">  </span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">    </span><span class="nt">DOCKER_TLS_CERTDIR</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;/certs&#34;</span><span class="w">   </span><span class="c"># enable TLS for secure communication</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">  </span><span class="nt">before_script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">    </span>- <span class="l">docker login -u $REGISTRY_USER -p $REGISTRY_PASS</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">    </span>- <span class="l">docker build -t $IMAGE_NAME:$IMAGE_TAG .</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">    </span>- <span class="l">docker push $IMAGE_NAME:$IMAGE_TAG</span></span></span></code></pre></div><p><strong>Why do you need <code>services: docker:dind</code>?</strong></p>
<p>Each GitLab CI job runs inside a container. That container has no Docker daemon by default, so <code>docker build</code> fails.</p>
<p><code>services</code> is GitLab CI&rsquo;s sidecar mechanism &ndash; it starts an extra container alongside the job container, both on the same network. <code>docker:dind</code> is an image with a built-in Docker daemon, designed for exactly this:</p>





<pre tabindex="0"><code>job container (docker:20.10.16, has CLI)
        ↕ TLS-encrypted communication
sidecar (docker:20.10.16-dind, has daemon)</code></pre><p><strong>What is <code>DOCKER_TLS_CERTDIR: &quot;/certs&quot;</code>?</strong></p>
<p>Here &ldquo;CLI&rdquo; means the side that runs <code>docker build</code> &ndash; <code>docker build</code> itself just sends a request to the daemon via the Docker API, and the daemon does the actual build. TLS protects this API communication:</p>





<pre tabindex="0"><code>job container: docker build ...
     ↕ Docker API over TLS
sidecar: daemon actually runs the build</code></pre><p>Setting <code>DOCKER_TLS_CERTDIR: &quot;/certs&quot;</code> makes DinD auto-generate TLS certificates. The CLI side reads them too, and the two complete a handshake before communicating. Setting it to an empty string <code>&quot;&quot;</code> disables TLS, but that is insecure and not recommended.</p>
<p><strong>How are credentials passed in?</strong></p>
<p><code>$REGISTRY_USER</code> and <code>$REGISTRY_PASS</code> are CI/CD Variables configured in the GitLab project settings. They never appear in the YAML file, avoiding hardcoded secrets.</p>
<h2 id="knowledge-sugar">Knowledge Sugar</h2>
<p><strong>Top-level <code>variables</code> vs job-level <code>variables</code></strong></p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nt">variables</span><span class="p">:</span><span class="w">           </span><span class="c"># available to all jobs</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">  </span><span class="nt">IMAGE_NAME</span><span class="p">:</span><span class="w"> </span><span class="l">alienmushroom/demo-app</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="nt">build_image</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">  </span><span class="nt">variables</span><span class="p">:</span><span class="w">         </span><span class="c"># only this job; can override top-level</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w">    </span><span class="nt">DOCKER_TLS_CERTDIR</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;/certs&#34;</span></span></span></code></pre></div><p><strong>Does <code>before_script</code> behave differently from <code>script</code> on failure?</strong>
No &ndash; if any command returns a non-zero exit code, the job is marked as failed and subsequent stages do not run.</p>
<p><strong>Keep versions aligned</strong>
<code>image: docker:20.10.16</code> and <code>services: docker:20.10.16-dind</code> must use the same version number. A mismatch between CLI and daemon versions can cause unexpected issues.</p>
]]></content:encoded></item><item><title>GOPROXY</title><link>https://kinoko-tech-blog-theta.vercel.app/posts/goproxy/</link><pubDate>Wed, 25 Feb 2026 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/posts/goproxy/</guid><description>&lt;h2 id="the-point"&gt;The Point&lt;/h2&gt;
&lt;p&gt;Fetching private Go modules directly from GitHub in CI/CD easily gets rate-limited (403) when there are too many requests. The fix is to set &lt;code&gt;GOPROXY&lt;/code&gt; in your CD yaml to point at an internal Athens proxy server &amp;ndash; Athens caches the modules, so subsequent builds never hit GitHub again.&lt;/p&gt;





&lt;pre tabindex="0"&gt;&lt;code&gt;CI / go build
 |
 Athens (internal proxy, with cache)
 | (only on first fetch)
 GitHub / VCS&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="explanation"&gt;Explanation&lt;/h2&gt;
&lt;p&gt;Go has three environment variables that control how modules are fetched:&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="the-point">The Point</h2>
<p>Fetching private Go modules directly from GitHub in CI/CD easily gets rate-limited (403) when there are too many requests. The fix is to set <code>GOPROXY</code> in your CD yaml to point at an internal Athens proxy server &ndash; Athens caches the modules, so subsequent builds never hit GitHub again.</p>





<pre tabindex="0"><code>CI / go build
      |
   Athens (internal proxy, with cache)
      | (only on first fetch)
   GitHub / VCS</code></pre><h2 id="explanation">Explanation</h2>
<p>Go has three environment variables that control how modules are fetched:</p>
<table>
	<thead>
			<tr>
					<th>Variable</th>
					<th>Example Value</th>
					<th>Controls</th>
					<th>What It Means</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>GOPROXY</code></td>
					<td><code>https://internal-proxy.company.com</code></td>
					<td>Where modules are downloaded from</td>
					<td>Go only fetches from the internal proxy; if the proxy doesn&rsquo;t have it, the build fails (unless you append <code>,direct</code>)</td>
			</tr>
			<tr>
					<td><code>GONOSUMDB</code></td>
					<td><code>company.com/*</code></td>
					<td>Checksum verification source</td>
					<td>Matching modules skip the public <code>sum.golang.org</code> for verification</td>
			</tr>
			<tr>
					<td><code>GOPRIVATE</code></td>
					<td><code>company.com/*</code></td>
					<td>Shorthand for both above</td>
					<td>Simultaneously skips the public proxy and public checksum DB</td>
			</tr>
	</tbody>
</table>
<p><strong>GOPROXY fallback chain</strong></p>
<p><code>GOPROXY</code> supports comma-separated sources &ndash; Go tries them in order:</p>





<pre tabindex="0"><code>GOPROXY=https://internal-proxy.company.com,direct</code></pre><p><code>direct</code> is not another proxy &ndash; it means fetching directly from VCS (GitHub). If the internal proxy doesn&rsquo;t have a particular public module, it falls back to <code>direct</code>.</p>
<h2 id="knowledge-sugar">Knowledge Sugar</h2>
<p><strong>Why can&rsquo;t private modules be sent to <code>sum.golang.org</code>?</strong>
<code>sum.golang.org</code> is a public service. By default, Go sends the module path there for checksum verification. If a private module&rsquo;s path gets sent, it leaks internal repo information. Use <code>GONOSUMDB</code> or <code>GOPRIVATE</code> to exclude them.</p>
<p><strong><code>GOPRIVATE</code> vs setting <code>GOPROXY</code> + <code>GONOSUMDB</code> separately</strong>
<code>GOPRIVATE=company.com/*</code> is equivalent to setting both <code>GONOSUMDB=company.com/*</code> and <code>GONOPROXY=company.com/*</code> (skip proxy, go straight to VCS). If you&rsquo;re using an internal Athens proxy, you usually still need to explicitly set <code>GOPROXY</code>, because <code>GOPRIVATE</code> tells Go &ldquo;don&rsquo;t use a proxy&rdquo; &ndash; which would bypass your Athens instance and hit GitHub directly.</p>
<p><strong>How to pass a GitHub token in Docker builds?</strong>
If your Dockerfile runs <code>go build</code> and needs to fetch private modules, don&rsquo;t use <code>ARG</code> for the token &ndash; <code>ARG</code> values end up in the build history and can be recovered from image layers even after the file is deleted.</p>
<p>The correct approach is Docker BuildKit&rsquo;s secret mount:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dockerfile" data-lang="dockerfile"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">RUN</span> --mount<span class="o">=</span><span class="nv">type</span><span class="o">=</span>secret,id<span class="o">=</span>github_token <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="nv">GITHUB_TOKEN</span><span class="o">=</span><span class="k">$(</span>cat /run/secrets/github_token<span class="k">)</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl">    go build ./...</span></span></code></pre></div><p>The secret is only visible during this <code>RUN</code> step and is never written to any image layer.</p>
]]></content:encoded></item></channel></rss>