<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Github-Actions on Fabio Lopes</title><link>https://fabiolopes.page/tags/github-actions/</link><description>Recent content in Github-Actions on Fabio Lopes</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><copyright>Fabio M. Lopes</copyright><lastBuildDate>Thu, 29 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://fabiolopes.page/tags/github-actions/index.xml" rel="self" type="application/rss+xml"/><item><title>Shallow Git Clones: An Often-Overlooked CI/CD Optimization</title><link>https://fabiolopes.page/p/shallow-git-clones-an-often-overlooked-ci-cd-optimization/</link><pubDate>Thu, 29 Oct 2026 00:00:00 +0000</pubDate><guid>https://fabiolopes.page/p/shallow-git-clones-an-often-overlooked-ci-cd-optimization/</guid><description>&lt;img src="https://fabiolopes.page/p/shallow-git-clones-an-often-overlooked-ci-cd-optimization/git-depth_hu_bd9914647f27ef17.png" alt="Featured image of post Shallow Git Clones: An Often-Overlooked CI/CD Optimization" /&gt;&lt;h1 id="shallow-git-clones-an-often-overlooked-cicd-optimization"&gt;&lt;a href="#shallow-git-clones-an-often-overlooked-cicd-optimization" class="header-anchor"&gt;&lt;/a&gt;Shallow Git Clones: An Often-Overlooked CI/CD Optimization&#10;&lt;/h1&gt;&lt;p&gt;Today, my ISP left me without internet for the entire afternoon. My desktop doesn&amp;rsquo;t have a Wi-Fi adapter, so I switched to my backup laptop and connected through my phone&amp;rsquo;s 5G hotspot.&lt;/p&gt;&#10;&lt;p&gt;While refreshing my development environment, I started thinking about mobile data consumption. I needed to clone a few repositories, but downloading their entire Git history seemed unnecessary.&lt;/p&gt;&#10;&lt;p&gt;That&amp;rsquo;s when I remembered a small Git option:&lt;/p&gt;&#10;&lt;div class="code-example"&gt;&#10; &lt;div class="code-toolbar"&gt;&lt;span&gt;Source code&lt;/span&gt;&lt;span class="code-language"&gt;bash&lt;/span&gt;&lt;/div&gt;&#10; &lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git clone --depth &lt;span class="m"&gt;1&lt;/span&gt; https://github.com/example/project.git&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;It retrieves just the latest commit on the selected branch, rather than its complete history.&lt;/p&gt;&#10;&lt;p&gt;Nothing particularly sophisticated, but it reminded me of an optimization we sometimes overlook in CI/CD pipelines.&lt;/p&gt;&#10;&lt;h2 id="how-much-git-history-does-a-pipeline-really-need"&gt;&lt;a href="#how-much-git-history-does-a-pipeline-really-need" class="header-anchor"&gt;&lt;/a&gt;How much Git history does a pipeline really need?&#10;&lt;/h2&gt;&lt;p&gt;Think about a typical pipeline. It checks out source code, installs dependencies, runs tests, builds an artifact, and perhaps deploys it.&lt;/p&gt;&#10;&lt;p&gt;In many cases, everything operates on a single commit. The pipeline doesn&amp;rsquo;t need to know what happened hundreds or thousands of commits ago.&lt;/p&gt;&#10;&lt;p&gt;Yet a conventional Git clone downloads the repository&amp;rsquo;s reachable history.&lt;/p&gt;&#10;&lt;p&gt;For small repositories, the difference might be negligible. For repositories with years of development and large historical objects, fetching unnecessary history can consume bandwidth, disk space, and time.&lt;/p&gt;&#10;&lt;p&gt;This is especially relevant with ephemeral CI runners, where each execution starts with a fresh workspace.&lt;/p&gt;&#10;&lt;p&gt;Of course, shallow cloning doesn&amp;rsquo;t reduce the size of the current working tree. If the repository contains large files today, they still need to be downloaded.&lt;/p&gt;&#10;&lt;h2 id="applying-this-to-cicd-platforms"&gt;&lt;a href="#applying-this-to-cicd-platforms" class="header-anchor"&gt;&lt;/a&gt;Applying this to CI/CD platforms&#10;&lt;/h2&gt;&lt;p&gt;Most CI platforms already support shallow checkouts, often without requiring a manual Git command.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;GitHub Actions&lt;/strong&gt;&lt;/p&gt;&#10;&lt;div class="code-example"&gt;&#10; &lt;div class="code-toolbar"&gt;&lt;span&gt;Source code&lt;/span&gt;&lt;span class="code-language"&gt;yaml&lt;/span&gt;&lt;/div&gt;&#10; &lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;span class="lnt"&gt;4&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v4&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fetch-depth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;GitHub Actions already defaults to fetching a single commit through &lt;code&gt;actions/checkout&lt;/code&gt;. Setting &lt;code&gt;fetch-depth: 1&lt;/code&gt; makes that intention explicit.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;GitLab CI&lt;/strong&gt;&lt;/p&gt;&#10;&lt;div class="code-example"&gt;&#10; &lt;div class="code-toolbar"&gt;&lt;span&gt;Source code&lt;/span&gt;&lt;span class="code-language"&gt;yaml&lt;/span&gt;&lt;/div&gt;&#10; &lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;GIT_DEPTH&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;GitLab supports configuring the checkout depth globally or per job. Newly created projects currently default to a shallow depth of 20, so it&amp;rsquo;s worth checking the existing configuration first.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Azure Pipelines&lt;/strong&gt;&lt;/p&gt;&#10;&lt;div class="code-example"&gt;&#10; &lt;div class="code-toolbar"&gt;&lt;span&gt;Source code&lt;/span&gt;&lt;span class="code-language"&gt;yaml&lt;/span&gt;&lt;/div&gt;&#10; &lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;span class="lnt"&gt;4&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;self&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fetchDepth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fetchTags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Azure Pipelines supports shallow fetching directly in its checkout configuration. Disabling tag synchronization can also avoid unnecessary transfers when tags aren&amp;rsquo;t required.&lt;/p&gt;&#10;&lt;p&gt;These are straightforward configurations, but I wouldn&amp;rsquo;t apply them without checking whether the pipeline actually needs historical Git data.&lt;/p&gt;&#10;&lt;h2 id="a-practical-example-ibb-kids"&gt;&lt;a href="#a-practical-example-ibb-kids" class="header-anchor"&gt;&lt;/a&gt;A practical example: IBB Kids&#10;&lt;/h2&gt;&lt;p&gt;One of my projects, IBB Kids, is a small React and Express application deployed to a Linux VM through GitHub Actions.&lt;/p&gt;&#10;&lt;p&gt;Unlike pipelines that clone a repository into a fresh runner on every execution, its deployment workflow maintains persistent Git working copies on the target server.&lt;/p&gt;&#10;&lt;p&gt;The relevant part looks like this:&lt;/p&gt;&#10;&lt;div class="code-example"&gt;&#10; &lt;div class="code-toolbar"&gt;&lt;span&gt;Source code&lt;/span&gt;&lt;span class="code-language"&gt;bash&lt;/span&gt;&lt;/div&gt;&#10; &lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git fetch origin &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$DEPLOY_BRANCH&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git checkout --detach &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$DEPLOY_SHA&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;The workflow fetches the branch and checks out the exact commit associated with the deployment.&lt;/p&gt;&#10;&lt;p&gt;Since the repository already exists on the server, Git generally transfers only missing objects. This is different from repeatedly performing full clones on ephemeral runners.&lt;/p&gt;&#10;&lt;p&gt;There&amp;rsquo;s also an important detail: the target SHA may no longer be the latest commit by the time the deployment runs. Limiting the fetch to a single branch-tip commit could make that SHA unavailable locally.&lt;/p&gt;&#10;&lt;p&gt;For this reason, I wouldn&amp;rsquo;t blindly introduce &lt;code&gt;--depth 1&lt;/code&gt; into this particular workflow. Its existing design has different requirements, and I haven&amp;rsquo;t measured whether a shallow fetch would improve anything.&lt;/p&gt;&#10;&lt;p&gt;It&amp;rsquo;s a useful example of why checkout behavior should be evaluated in context.&lt;/p&gt;&#10;&lt;h2 id="where-shallow-cloning-can-become-a-problem"&gt;&lt;a href="#where-shallow-cloning-can-become-a-problem" class="header-anchor"&gt;&lt;/a&gt;Where shallow cloning can become a problem&#10;&lt;/h2&gt;&lt;p&gt;Some pipelines depend on information beyond the current commit:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Versioning:&lt;/strong&gt; Tools such as &lt;code&gt;git describe&lt;/code&gt; may require historical commits and tags.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Release automation:&lt;/strong&gt; Changelogs and release notes may depend on commit history.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Change detection:&lt;/strong&gt; Comparing branches or commits requires the relevant history and merge bases.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Static analysis:&lt;/strong&gt; Some tools use Git metadata to attribute changes or identify newly introduced code.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Retries and delayed jobs:&lt;/strong&gt; Particularly in GitLab CI, an overly shallow fetch can make an older target commit unavailable.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;If a pipeline needs that information, a deeper checkout or full history may be justified.&lt;/p&gt;&#10;&lt;p&gt;For example, in GitHub Actions:&lt;/p&gt;&#10;&lt;div class="code-example"&gt;&#10; &lt;div class="code-toolbar"&gt;&lt;span&gt;Source code&lt;/span&gt;&lt;span class="code-language"&gt;yaml&lt;/span&gt;&lt;/div&gt;&#10; &lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fetch-depth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;This retrieves the full history and tags covered by the checkout action.&lt;/p&gt;&#10;&lt;p&gt;The risk with shallow cloning is that some problems aren&amp;rsquo;t obvious. A build might succeed while a versioning or release-related operation produces incomplete results.&lt;/p&gt;&#10;&lt;h2 id="optimize-what-matters"&gt;&lt;a href="#optimize-what-matters" class="header-anchor"&gt;&lt;/a&gt;Optimize what matters&#10;&lt;/h2&gt;&lt;p&gt;My internet outage was a reminder that network transfer isn&amp;rsquo;t free. On a mobile hotspot, the constraint was immediately visible. In CI/CD environments, unnecessary transfers are easier to overlook because the infrastructure handles them automatically.&lt;/p&gt;&#10;&lt;p&gt;But optimizing checkout depth only makes sense if it addresses a meaningful cost.&lt;/p&gt;&#10;&lt;p&gt;I would look at checkout duration, transferred data, and total pipeline execution time before considering the change worthwhile. A faster clone may not matter much if dependency installation or testing dominates the workflow.&lt;/p&gt;&#10;&lt;p&gt;And if a job downloads additional history later, the initial savings may disappear.&lt;/p&gt;&#10;&lt;p&gt;My preference is to &lt;strong&gt;fetch as much Git history as the workflow needs, and no more&lt;/strong&gt;.&lt;/p&gt;&#10;&lt;p&gt;A small configuration detail won&amp;rsquo;t transform every pipeline. But questioning routine operations, even something as ordinary as cloning a repository, is part of keeping our systems efficient and understandable.&lt;/p&gt;&#10;&lt;p&gt;Sometimes, all it takes to revisit an old assumption is an afternoon without internet.&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h3 id="references"&gt;&lt;a href="#references" class="header-anchor"&gt;&lt;/a&gt;References&#10;&lt;/h3&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://www.theserverside.com/blog/Coffee-Talk-Java-News-Stories-and-Opinions/How-and-when-to-perform-a-depth-1-git-clone" target="_blank" rel="noopener"&#10; &gt;How and When to Perform a Depth-1 Git Clone&lt;/a&gt;, TheServerSide&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://github.com/actions/checkout" target="_blank" rel="noopener"&#10; &gt;GitHub Actions Checkout&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://docs.gitlab.com/ci/runners/configure_runners/" target="_blank" rel="noopener"&#10; &gt;GitLab Runner Configuration&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://learn.microsoft.com/en-us/azure/devops/pipelines/yaml-schema/steps-checkout" target="_blank" rel="noopener"&#10; &gt;Azure Pipelines Checkout Schema&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item></channel></rss>