<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title>aahlenst.dev</title>
		<link>https://aahlenst.dev/blog/</link>
		<description>Recent content in Blog on aahlenst.dev</description>
		<generator>Hugo -- gohugo.io</generator>
		<language>en-us</language>
		<lastBuildDate>Sun, 12 Apr 2026 12:00:00 +0200</lastBuildDate><atom:link href="https://aahlenst.dev/blog/index.xml" rel="self" type="application/rss+xml" />
		<item>
			<title>Autoscaling Forgejo Runner</title>
			<link>https://aahlenst.dev/blog/autoscaling-forgejo-runner/</link>
			<pubDate>Sun, 12 Apr 2026 12:00:00 +0200</pubDate>
			
			<guid>https://aahlenst.dev/blog/autoscaling-forgejo-runner/</guid>
			<description>&lt;p&gt;Over the last months, the Forgejo community equipped &lt;a href=&#34;https://forgejo.org/&#34;&gt;Forgejo&lt;/a&gt; and &lt;a href=&#34;https://code.forgejo.org/forgejo/runner&#34;&gt;Forgejo Runner&lt;/a&gt; with the necessary building blocks for autoscaling Forgejo Runner. These building blocks should enable developers of workload managers and autoscalers to build reliable and secure integrations for Forgejo Actions. This post aims to provide an overview of the new capabilities, explain how they fit together, and highlight some peculiarities to watch out for.&lt;/p&gt;
&lt;div class=&#34;not-prose bg-blue-50 border-l-4 border-blue-400 p-4&#34;&gt;
	&lt;div class=&#34;flex&#34;&gt;
		&lt;div class=&#34;shrink-0&#34;&gt;
			&lt;svg class=&#34;h-8 w-8 text-blue-400&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34;
				 aria-hidden=&#34;true&#34;&gt;
				&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34;
					  d=&#34;M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z&#34;&gt;&lt;/path&gt;
			&lt;/svg&gt;
		&lt;/div&gt;
		&lt;div class=&#34;admonition ml-3 text-blue-700&#34;&gt;
			Forgejo 15.0 and Forgejo Runner 12.8 or newer are required unless noted otherwise.
		&lt;/div&gt;
	&lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&#34;autoscaling-in-a-nutshell&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#autoscaling-in-a-nutshell&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Autoscaling in a Nutshell&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;To scale Forgejo Runner instances up and down, an external system has to perform the following tasks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Monitor Forgejo&amp;rsquo;s job queue for jobs that are ready to run.&lt;/li&gt;
&lt;li&gt;Create runners in response to jobs that are ready to run.&lt;/li&gt;
&lt;li&gt;Launch Forgejo Runner to run a job.&lt;/li&gt;
&lt;li&gt;Remove runners that are no longer needed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Start by asking Forgejo for jobs that are ready to run:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ curl http://localhost:3000/api/v1/repos/andreas/test/actions/runners/jobs
[{&amp;#34;id&amp;#34;:982,&amp;#34;attempt&amp;#34;:1,&amp;#34;handle&amp;#34;:&amp;#34;33ba7d51-59c6-44f8-9d2b-1b94f4033973&amp;#34;,&amp;#34;repo_id&amp;#34;:1,&amp;#34;owner_id&amp;#34;:1,&amp;#34;name&amp;#34;:&amp;#34;build&amp;#34;,&amp;#34;needs&amp;#34;:null,&amp;#34;runs_on&amp;#34;:[&amp;#34;debian&amp;#34;],&amp;#34;task_id&amp;#34;:0,&amp;#34;status&amp;#34;:&amp;#34;waiting&amp;#34;}]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then, create a runner to run that job:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ curl -X POST \
	-H &amp;#34;Content-Type: application/json&amp;#34; \
	-d &amp;#39;{&amp;#34;name&amp;#34;:&amp;#34;0CEY3JWJ6A4ZK&amp;#34;,&amp;#34;ephemeral&amp;#34;:true}&amp;#39; \
	http://localhost:3000/api/v1/repos/andreas/test/actions/runners
{&amp;#34;id&amp;#34;:5,&amp;#34;uuid&amp;#34;:&amp;#34;d4637a60-7fea-4075-a81c-7e4ae01bdf0d&amp;#34;,&amp;#34;token&amp;#34;:&amp;#34;e1f3d26e034687a2b6856c4c6c3d87a469ff3bea&amp;#34;}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Run the job:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ echo -n &amp;#34;e1f3d26e034687a2b6856c4c6c3d87a469ff3bea&amp;#34; &amp;gt; /tmp/runner-token
$ forgejo-runner one-job \
	--url http://localhost:3000/ \
	--uuid d4637a60-7fea-4075-a81c-7e4ae01bdf0d \
	--token-url file:/tmp/runner-token \
	--label debian:docker://node:lts \
	--handle 33ba7d51-59c6-44f8-9d2b-1b94f4033973 \
	--wait
INFO[0000] No configuration file specified; using default settings. 
INFO[2026-04-11T20:18:39+02:00] Starting job
INFO[2026-04-11T20:18:39+02:00] runner: 0CEY3JWJ6A4ZK, with version: v12.8.2, with labels: [debian], ephemeral: true, declared successfully 
INFO[2026-04-11T20:18:39+02:00] single task poller launched
INFO[2026-04-11T20:18:39+02:00] single task poller successfully fetched one task from http://localhost:3000/ 
INFO[2026-04-11T20:18:39+02:00] task 917 repo is andreas/test https://data.forgejo.org http://localhost:3000/ 
INFO[2026-04-11T20:18:47+02:00] Cleaning up network for job build, and network name is: WORKFLOW-891112e47856a708ddac3fdd3d034bc0 
INFO[2026-04-11T20:18:47+02:00] single task poller is shutting down 
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And that&amp;rsquo;s it! Because I created an ephemeral runner, Forgejo will remove the runner automatically after the job has completed.&lt;/p&gt;
&lt;h2 id=&#34;about-scopes&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#about-scopes&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;About Scopes&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Forgejo Actions has four scopes: repository, user, organization, and instance. For example, a single runner can be bound to a repository, a user, an organization, or the entire instance. While a runner that is bound to a single repository can only run jobs triggered by that repository, a runner that is bound to an entire organization can run jobs triggered by any repository belonging to that organization. Variables and secrets work similarly.&lt;/p&gt;
&lt;p&gt;When managing runners using the web UI, you can manage the runners of the current scope. In addition, all runners of superior scopes are visible. The instance scope works slightly different. It shows &lt;em&gt;all&lt;/em&gt; runners because it doubles as admin scope.&lt;/p&gt;
&lt;p&gt;The HTTP API uses the same scopes. That means a single endpoint like &lt;code&gt;actions/runners/jobs&lt;/code&gt; is available in each scope:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Admin: &lt;code&gt;/admin/actions/runners/jobs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Organization: &lt;code&gt;/orgs/{org}/actions/runners/jobs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Repository: &lt;code&gt;/repos/{owner}/{repo}/actions/runners/jobs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;User: &lt;code&gt;/user/actions/runners/jobs&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By default, the HTTP API provides the same data as the web UI. However, some endpoints like &lt;code&gt;actions/runners&lt;/code&gt; can only return the data of the current scope if desired.&lt;/p&gt;
&lt;p&gt;The examples in this post usually use a single scope for brevity.&lt;/p&gt;
&lt;h2 id=&#34;monitoring-forgejos-job-queue&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#monitoring-forgejos-job-queue&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Monitoring Forgejo&amp;rsquo;s Job Queue&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;You can monitor Forgejo&amp;rsquo;s job queue either using the HTTP API or, once the PR has landed, &lt;a href=&#34;#webhooks&#34;&gt;webhooks&lt;/a&gt;. This section will use the HTTP API exclusively. However, all concepts discussed here also apply to webhooks.&lt;/p&gt;
&lt;p&gt;A list of all &lt;em&gt;active&lt;/em&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; jobs can be obtained by querying the endpoint &lt;code&gt;actions/runners/jobs&lt;/code&gt; which is exposed in each scope.&lt;/p&gt;
&lt;p&gt;Each scope &amp;ldquo;sees&amp;rdquo; all active jobs of all subordinate scopes. For example, &lt;code&gt;/user/actions/runners/jobs&lt;/code&gt; returns all active jobs of each of the user&amp;rsquo;s repositories. Similarly, &lt;code&gt;/orgs/{org}/actions/runners/jobs&lt;/code&gt; returns all active jobs of each of the organization&amp;rsquo;s repositories.&lt;/p&gt;
&lt;p&gt;To get all active jobs that belong to the repository &lt;code&gt;andreas/test&lt;/code&gt;, run:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ curl http://localhost:3000/api/v1/repos/andreas/test/actions/runners/jobs
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The result:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;id&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;301&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;attempt&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;handle&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;9d52c7d8-aebe-426b-b015-dd453aacaada&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;repo_id&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;owner_id&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;name&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;test&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;needs&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;runs_on&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;debian&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;task_id&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;status&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;waiting&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;not-prose bg-red-50 border-l-4 border-red-400 p-4&#34;&gt;
	&lt;div class=&#34;flex&#34;&gt;
		&lt;div class=&#34;shrink-0&#34;&gt;
			&lt;svg class=&#34;h-8 w-8 text-red-400&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34;
				 aria-hidden=&#34;true&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;
				&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34;
					  d=&#34;M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z&#34;&gt;&lt;/path&gt;
			&lt;/svg&gt;
		&lt;/div&gt;
		&lt;div class=&#34;admonition ml-3 text-red-700&#34;&gt;
			When filtering by label the labels in the query string have to include &lt;em&gt;all&lt;/em&gt; labels that appear in &lt;code&gt;runs_on&lt;/code&gt;. Otherwise, Forgejo will filter the job out.
		&lt;/div&gt;
	&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Each job can be run multiple times. Forgejo increments the &lt;code&gt;attempt&lt;/code&gt; number before each attempt, but the &lt;code&gt;id&lt;/code&gt; remains fixed.&lt;/p&gt;
&lt;p&gt;If you need to uniquely identify a run attempt, use &lt;code&gt;handle&lt;/code&gt;&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;. Do not create an identifier yourself by piecing together multiple fields.&lt;/p&gt;
&lt;h2 id=&#34;runner-management&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#runner-management&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Runner Management&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Once a job is ready to run, a new runner has to be created to run the job. That again can be done with the help of Forgejo&amp;rsquo;s HTTP API.&lt;/p&gt;
&lt;p&gt;Forgejo offers the following endpoints for managing runners:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;List all runners: &lt;code&gt;GET /repos/{owner}/{repo}/actions/runners&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Register a runner: &lt;code&gt;POST /repos/{owner}/{repo}/actions/runners&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Get a particular runner: &lt;code&gt;/repos/{owner}/{repo}/actions/runners/{runner_id}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Delete a particular runner: &lt;code&gt;DELETE /repos/{owner}/{repo}/actions/runners/{runner_id}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The same set of endpoints is available in all other scopes.&lt;/p&gt;
&lt;div class=&#34;not-prose bg-red-50 border-l-4 border-red-400 p-4&#34;&gt;
	&lt;div class=&#34;flex&#34;&gt;
		&lt;div class=&#34;shrink-0&#34;&gt;
			&lt;svg class=&#34;h-8 w-8 text-red-400&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34;
				 aria-hidden=&#34;true&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;
				&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34;
					  d=&#34;M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z&#34;&gt;&lt;/path&gt;
			&lt;/svg&gt;
		&lt;/div&gt;
		&lt;div class=&#34;admonition ml-3 text-red-700&#34;&gt;
			By default, &lt;code&gt;actions/runners&lt;/code&gt; returns &lt;em&gt;all&lt;/em&gt; runners that are visible in a particular scope. For example, in the repository scope, all runners of the repository, the user, or organization it belongs to, and the instance will be returned. Append &lt;code&gt;visible=false&lt;/code&gt; to the query string to only receive the runners of the respective scope.
		&lt;/div&gt;
	&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;To create a new runner, send a &lt;code&gt;POST&lt;/code&gt; request to &lt;code&gt;/repos/{owner}/{repo}/actions/runners&lt;/code&gt; or one of its equivalents in the other scopes:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ curl -X POST \
	-H &amp;#34;Content-Type: application/json&amp;#34; \
	-d &amp;#39;{&amp;#34;name&amp;#34;:&amp;#34;0CEY3JWJ6A4ZK&amp;#34;,&amp;#34;ephemeral&amp;#34;:true}&amp;#39; \
	http://localhost:3000/api/v1/repos/andreas/test/actions/runners
{&amp;#34;id&amp;#34;:5,&amp;#34;uuid&amp;#34;:&amp;#34;d4637a60-7fea-4075-a81c-7e4ae01bdf0d&amp;#34;,&amp;#34;token&amp;#34;:&amp;#34;e1f3d26e034687a2b6856c4c6c3d87a469ff3bea&amp;#34;}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You receive the runner&amp;rsquo;s ID, which is required to use one of the runner-specific endpoints, the runner&amp;rsquo;s UUID, and its token. You have to supply the UUID and the runner token to &lt;code&gt;forgejo-runner&lt;/code&gt; so that it can authenticate itself to Forgejo.&lt;/p&gt;
&lt;h3 id=&#34;persistent-and-ephemeral-runners&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#persistent-and-ephemeral-runners&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Persistent and Ephemeral Runners&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;Forgejo distinguishes two types of runners: &lt;em&gt;ephemeral&lt;/em&gt; and &lt;em&gt;persistent&lt;/em&gt; (the default). Persistent runners can run multiple jobs during their lifetime, whereas ephemeral runners can run at most one job before they get deleted by Forgejo.&lt;/p&gt;
&lt;p&gt;If possible, use ephemeral runners in autoscaling scenarios, especially when using Forgejo Runner in host mode. In host mode, the job can get hold of the runner token. With an exfiltrated persistent runner token, an attacker could request additional jobs and, depending on the runner&amp;rsquo;s scope, access secrets and other sensitive resources of other repositories. With an ephemeral runner token, an attacker can only receive the same job that was already compromised. That means that the damage would be limited to a single repository.&lt;/p&gt;
&lt;h2 id=&#34;running-jobs&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#running-jobs&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Running Jobs&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Forgejo Runner offers two separate commands for running jobs: &lt;code&gt;one-job&lt;/code&gt;, and &lt;code&gt;daemon&lt;/code&gt;. While &lt;code&gt;daemon&lt;/code&gt; runs jobs until it is shut down, &lt;code&gt;one-job&lt;/code&gt; does what it says on the tin: it runs exactly one job before it quits.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;one-job&lt;/code&gt; is purpose-built for autoscaling and should be used whenever possible. It is also the only command that supports ephemeral mode; &lt;code&gt;daemon&lt;/code&gt; refuses to work in ephemeral mode.&lt;/p&gt;
&lt;p&gt;A typical usage of &lt;code&gt;forgejo-runner one-job&lt;/code&gt; looks as follows:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ forgejo-runner one-job \
	--url http://localhost:3000/ \
	--uuid d4637a60-7fea-4075-a81c-7e4ae01bdf0d \
	--token-url file:/path/to/runner-token \
	--label debian:docker://node:lts \
	--label gpu:docker://node:lts \
	--handle 33ba7d51-59c6-44f8-9d2b-1b94f4033973 \
	--wait
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;--url&lt;/code&gt;, &lt;code&gt;--uuid&lt;/code&gt;, and &lt;code&gt;--token-url&lt;/code&gt; define the connection to Forgejo, whereas &lt;code&gt;--label&lt;/code&gt; defines the labels this runner instance supports. For &lt;code&gt;--handle&lt;/code&gt; and &lt;code&gt;--wait&lt;/code&gt;, see the next sections.&lt;/p&gt;
&lt;p&gt;While it might be handy to use options in many use cases, it is not required. All options except &lt;code&gt;--handle&lt;/code&gt; and &lt;code&gt;--wait&lt;/code&gt; can also be configured in the runner configuration:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;server&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;connections&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;forgejo&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;url&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;http://localhost:3000/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;uuid&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;d4637a60-7fea-4075-a81c-7e4ae01bdf0d&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;token&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;e1f3d26e034687a2b6856c4c6c3d87a469ff3bea&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;labels&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#ae81ff&#34;&gt;debian:docker://node:lts&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#ae81ff&#34;&gt;gpu:docker://node:lts&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Together with the runner configuration stored in &lt;code&gt;runner-config.yaml&lt;/code&gt;, the invocation above becomes:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ forgejo-runner -c runner-config.yaml one-job \
	--handle 33ba7d51-59c6-44f8-9d2b-1b94f4033973 \
	--wait
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;what-the-handle-is-good-for&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#what-the-handle-is-good-for&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;What the Handle is Good For&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;--handle&lt;/code&gt; instructs Forgejo to return the run attempt identified by the handle &amp;ndash; and only that run attempt. If the run attempt is not ready to run or already being executed by another runner, Forgejo will not return any other job, even if there are others waiting.&lt;/p&gt;
&lt;p&gt;Its purpose is to prevent race conditions, and to simplify the implementation of autoscalers.&lt;/p&gt;
&lt;p&gt;As an example for a race condition, consider two waiting jobs: A with the label set &lt;code&gt;[debian]&lt;/code&gt;, and B with the label set &lt;code&gt;[debian, gpu]&lt;/code&gt;. In response to those waiting jobs, two Forgejo Runner instances are started: 1 with the label set &lt;code&gt;[debian]&lt;/code&gt; (for A), and 2 with the label set &lt;code&gt;[debian, gpu]&lt;/code&gt; (for B). If, for some reason, 2 asks Forgejo first for the next pending job, it will receive A because the label set of 1 is a superset of the label set of A. Once 1 is ready and asks for a waiting job, it cannot run B because its label set does not contain &lt;code&gt;gpu&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If both Forgejo Runner instances were to use &lt;code&gt;--handle&lt;/code&gt;, they would both receive the intended job. Crisis averted!&lt;/p&gt;
&lt;p&gt;There are more potential concurrency issues that can only be avoided if the workload manager or autoscaler knows which runner is executing a particular job. Without &lt;code&gt;--handle&lt;/code&gt;, that would be much harder and require additional APIs in Forgejo.&lt;/p&gt;
&lt;div class=&#34;not-prose bg-red-50 border-l-4 border-red-400 p-4&#34;&gt;
	&lt;div class=&#34;flex&#34;&gt;
		&lt;div class=&#34;shrink-0&#34;&gt;
			&lt;svg class=&#34;h-8 w-8 text-red-400&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34;
				 aria-hidden=&#34;true&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;
				&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34;
					  d=&#34;M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z&#34;&gt;&lt;/path&gt;
			&lt;/svg&gt;
		&lt;/div&gt;
		&lt;div class=&#34;admonition ml-3 text-red-700&#34;&gt;
			For &lt;code&gt;--handle&lt;/code&gt; to do its magic, all runners operating in a particular scope have to participate and use &lt;code&gt;--handle&lt;/code&gt;, too. Otherwise, jobs might still end up on the wrong runner.
		&lt;/div&gt;
	&lt;/div&gt;
&lt;/div&gt;

&lt;h3 id=&#34;waiting-is-unavoidable-for-now&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#waiting-is-unavoidable-for-now&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Waiting Is Unavoidable (for Now)&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;--wait&lt;/code&gt; causes &lt;code&gt;forgejo-runner one-job&lt;/code&gt; to ask Forgejo for a job until it receives one. Unfortunately, &lt;code&gt;--wait&lt;/code&gt; must be used for now because not all jobs with the status &lt;code&gt;waiting&lt;/code&gt; can actually be run. See the section about &lt;a href=&#34;#unambiguous-job-statuses&#34;&gt;Unambiguous Job Statuses&lt;/a&gt; for details.&lt;/p&gt;
&lt;div class=&#34;not-prose bg-blue-50 border-l-4 border-blue-400 p-4&#34;&gt;
	&lt;div class=&#34;flex&#34;&gt;
		&lt;div class=&#34;shrink-0&#34;&gt;
			&lt;svg class=&#34;h-8 w-8 text-blue-400&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34;
				 aria-hidden=&#34;true&#34;&gt;
				&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34;
					  d=&#34;M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z&#34;&gt;&lt;/path&gt;
			&lt;/svg&gt;
		&lt;/div&gt;
		&lt;div class=&#34;admonition ml-3 text-blue-700&#34;&gt;
			A timeout was omitted from &lt;code&gt;one-job&lt;/code&gt; because workload managers and autoscalers usually enforce timeouts by themselves.
		&lt;/div&gt;
	&lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&#34;missing-pieces&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#missing-pieces&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Missing Pieces&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;While the building blocks I have discussed above are a good start, there are still multiple pieces missing.&lt;/p&gt;
&lt;h3 id=&#34;webhooks&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#webhooks&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Webhooks&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;While polling Forgejo&amp;rsquo;s HTTP API for jobs that are ready to run is the most reliable mechanism to monitor its job queue, it is not particularly efficient. Operators of larger Forgejo instances like &lt;a href=&#34;https://codeberg.org/&#34;&gt;Codeberg&lt;/a&gt; would certainly be happy if you would not poll it every few seconds. A well established, more efficient mechanism would be &lt;a href=&#34;https://en.wikipedia.org/wiki/Webhook&#34;&gt;webhooks&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Fortunately, there is a &lt;a href=&#34;https://codeberg.org/forgejo/forgejo/pulls/9803&#34;&gt;pull request for adding webhooks that are activated by workflow run and job status changes&lt;/a&gt; that is close to the finish line.&lt;/p&gt;
&lt;h3 id=&#34;runner-removal&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#runner-removal&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Runner Removal&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;If you delete a runner in Forgejo, it will only be marked as deleted, not actually removed from the database. It would make sense if it were used for undo, but it is not. The deleted runners linger forever in Forgejo&amp;rsquo;s database for no particular reason.&lt;/p&gt;
&lt;p&gt;Expect that this soft-deletion will be removed as part of the &lt;a href=&#34;https://codeberg.org/forgejo/discussions/issues/385&#34;&gt;gradual rollout of foreign keys&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;unambiguous-job-statuses&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#unambiguous-job-statuses&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Unambiguous Job Statuses&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;As of Forgejo 15, it is impossible to determine from the outside whether a job is ready to run. The problem is that a job that is currently &lt;code&gt;waiting&lt;/code&gt; might still be blocked by a &lt;a href=&#34;https://forgejo.org/docs/v15.0/user/actions/reference/#concurrencygroup&#34;&gt;concurrency group&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://codeberg.org/forgejo/forgejo/issues/10259&#34;&gt;There is a proposal&lt;/a&gt; to introduce a new status called &lt;code&gt;ready&lt;/code&gt; which would signal that a job is ready to run, no exceptions. It would be augmented by a secondary status that would provide additional details.&lt;/p&gt;
&lt;h3 id=&#34;an-access-token-scope-for-managing-runners-only&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#an-access-token-scope-for-managing-runners-only&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;An Access Token Scope for Managing Runners, Only&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;In Forgejo, &lt;a href=&#34;https://forgejo.org/docs/v15.0/user/token-scope/&#34;&gt;access token scopes&lt;/a&gt; are relatively broad. For example, you can select whether a token can only read a repository or have full control over it. There is nothing in between. That means that even if a token is only used to manage runners, it has full administrative privileges over the respective scope. That is unnecessarily risky.&lt;/p&gt;
&lt;p&gt;There are ideas swirling around about introducing finer grained scopes, for example, &lt;code&gt;write:repository:runners&lt;/code&gt;, but nothing definitive yet.&lt;/p&gt;
&lt;h3 id=&#34;organization-tokens&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#organization-tokens&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Organization Tokens&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;Forgejo only has personal access tokens, but &lt;a href=&#34;https://codeberg.org/forgejo/forgejo/issues/1712&#34;&gt;no organization tokens&lt;/a&gt;. That, paired with the lack of a token scope for managing runners, means that managing runners using the HTTP API is inconvenient and unnecessarily risky.&lt;/p&gt;
&lt;h3 id=&#34;monitoring&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#monitoring&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Monitoring&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;There is currently no mechanism to monitor any aspects of Forgejo Actions, like the length of the job queue. There is an &lt;a href=&#34;https://code.forgejo.org/forgejo/forgejo-actions-feature-requests/issues/96&#34;&gt;open feature request for adding a metrics endpoint&lt;/a&gt;. Please tell us what you need.&lt;/p&gt;
&lt;h2 id=&#34;rejected-ideas&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#rejected-ideas&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Rejected Ideas&lt;/span&gt;
&lt;/h2&gt;
&lt;h3 id=&#34;jit-configuration&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#jit-configuration&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;JIT Configuration&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;When creating a self-hosted runner, GitHub provides the option to generate a &lt;a href=&#34;https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2026-03-10#create-configuration-for-a-just-in-time-runner-for-a-repository&#34;&gt;just-in-time configuration&lt;/a&gt;, or JIT configuration in short. The JIT configuration is Base64-encoded JSON document that contains the runner configuration and can be passed to GitHub Actions Runner &lt;code&gt;./run.sh&lt;/code&gt; with the option &lt;code&gt;--jitconfig&lt;/code&gt;. That avoids the separate configuration step with &lt;code&gt;./config.sh&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We discussed the &lt;a href=&#34;https://code.forgejo.org/forgejo/runner/pulls/1122&#34;&gt;idea to create a similar mechanism for Forgejo Runner&lt;/a&gt;, but ultimately abandoned it because it would tie Forgejo Runner to Forgejo even more. Furthermore, Forgejo has no knowledge of Forgejo Runner&amp;rsquo;s label configuration (think &lt;code&gt;debian-latest:docker://node:lts-trixie&lt;/code&gt;) which is significantly more involved that GitHub&amp;rsquo;s.&lt;/p&gt;
&lt;h3 id=&#34;kubernetes-native-runners&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#kubernetes-native-runners&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Kubernetes-native Runners&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;Forgejo Runner usually runs each job in a separate container that it spawns. Optionally, it creates additional containers to provide &lt;a href=&#34;https://forgejo.org/docs/v15.0/user/actions/reference/#jobsjob_idservices&#34;&gt;services&lt;/a&gt; like a PostgreSQL database. That does not work well with Kubernetes, because it requires nesting containers inside containers. Couldn&amp;rsquo;t Forgejo Runner instead &lt;a href=&#34;https://codeberg.org/forgejo/discussions/issues/66&#34;&gt;delegate the creation and management of containers to Kubernetes&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;While that might indeed be possible, it is unlikely that the capability will be incorporated into Forgejo Runner itself. Forgejo Runner already requires fairly exotic knowledge to work on, and deep knowledge of Kubernetes would narrow the pool of potential maintainers even more. Instead, we are &lt;a href=&#34;https://code.forgejo.org/forgejo/forgejo-actions-feature-requests/issues/107&#34;&gt;currently investigating the addition of a plug-in interface for back-ends&lt;/a&gt;. That would not only enable Kubernetes-native runners, but also using lightweight virtual machines instead of containers for running jobs.&lt;/p&gt;
&lt;p&gt;Prototypes of a &lt;a href=&#34;https://git.erwanleboucher.dev/eleboucher/runner&#34;&gt;Kubernetes-native runner&lt;/a&gt; and a &lt;a href=&#34;https://code.forgejo.org/forgejo/runner/pulls/1382&#34;&gt;back-end based on Firecracker&lt;/a&gt; already exist and can be tried out.&lt;/p&gt;
&lt;h2 id=&#34;recipes&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#recipes&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Recipes&lt;/span&gt;
&lt;/h2&gt;
&lt;h3 id=&#34;mixing-persistent-and-ephemeral-runners&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#mixing-persistent-and-ephemeral-runners&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Mixing Persistent and Ephemeral Runners&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;It is possible to use persistent and ephemeral runners in a single scope without undermining &lt;code&gt;--handle&lt;/code&gt; and causing concurrency issues. The key is to use disjoint label sets for ephemeral and persistent runners. For example, &lt;code&gt;debian&lt;/code&gt; could activate a persistent runner whereas &lt;code&gt;debian-ephemeral&lt;/code&gt; could request an ephemeral runner. It requires that the workload manager or autoscaler ignores &lt;code&gt;debian&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You could also use multiple labels, for example, &lt;code&gt;[debian, ephemeral]&lt;/code&gt; (for an ephemeral runner) and &lt;code&gt;[debian]&lt;/code&gt; (for a persistent runner). While that might look attractive from the onset, it is tricky to use correctly. The first label that Forgejo Runner encounters defines the execution environment that the job will be executed in. That means that &lt;code&gt;[debian, ephemeral]&lt;/code&gt; will be executed in &lt;code&gt;debian&lt;/code&gt;, whereas &lt;code&gt;[ephemeral, debian]&lt;/code&gt; will be executed in &lt;code&gt;ephemeral&lt;/code&gt;. Teaching the workload manager or autoscaler to pass all labels to Forgejo Runner with identical mappings will make it work in any case:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ forgejo-runner one-job \
	--label debian:docker://node:lts \
	--label ephemeral:docker://node:lts \
	...
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;pre-warmed-runners&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#pre-warmed-runners&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Pre-Warmed Runners&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;Pre-warmed runners are typically virtual machines that are waiting for a job to run. That is supposed to reduce latency. The problem is that without a job to run, there is no handle. The best you can do in this case is to start the virtual machine, but not Forgejo Runner. Only start Forgejo Runner once a job is ready to run.&lt;/p&gt;
&lt;p&gt;If that is not an option, see &lt;a href=&#34;#mixing-persistent-and-ephemeral-runners&#34;&gt;Mixing Persistent and Ephemeral Runners&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Active jobs are those that are either ready to run (unless blocked by a concurrency group) or currently running.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;It might look like an UUIDv4, but it is not guaranteed to be an UUIDv4.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
		</item>
		
		<item>
			<title>systemd Credentials in Spring Boot</title>
			<link>https://aahlenst.dev/blog/systemd-credentials-in-spring-boot/</link>
			<pubDate>Thu, 10 Jul 2025 12:00:00 +0100</pubDate>
			
			<guid>https://aahlenst.dev/blog/systemd-credentials-in-spring-boot/</guid>
			<description>&lt;p&gt;&lt;a href=&#34;https://systemd.io/&#34;&gt;systemd&lt;/a&gt;, Linux&amp;rsquo;s most widely used system and service manager, introduced &lt;a href=&#34;https://systemd.io/CREDENTIALS/&#34;&gt;system and service credentials&lt;/a&gt; with version 247. They allow for securely passing passwords and keys to applications, thereby solving a longstanding problem. Let&amp;rsquo;s find out how we can access them in &lt;a href=&#34;https://spring.io/projects/spring-boot&#34;&gt;Spring Boot&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;introducing-systemd-credentials&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#introducing-systemd-credentials&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Introducing systemd Credentials&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;In their simplest form, systemd credentials are straightforward:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[Service]
ExecStart=/path/to/my-program
LoadCredential=my-secret:/etc/my-program/credentials/some-password
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this unit file, we tell systemd to start &lt;code&gt;my-program&lt;/code&gt; and to expose an unencrypted credential to it. The credential is stored in the file &lt;code&gt;/etc/my-program/credentials/some-password&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now, how can &lt;code&gt;my-program&lt;/code&gt; access it? Not by opening &lt;code&gt;/etc/my-program/credentials/some-password&lt;/code&gt;. Instead, systemd will prepare a special directory&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. There, it will place the credential in a file named &lt;code&gt;my-secret&lt;/code&gt;. systemd passes &lt;code&gt;my-program&lt;/code&gt; the path to the special directory using the environment variable &lt;code&gt;CREDENTIALS_DIRECTORY&lt;/code&gt;. If &lt;code&gt;my-program&lt;/code&gt; were a shell script, it could read the secret by opening &lt;code&gt;&amp;quot;$CREDENTIALS_DIRECTORY/my-secret&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There are many more options. It is not only possible to encrypt secrets,  but even to bind them to the &lt;a href=&#34;https://en.wikipedia.org/wiki/Trusted_Platform_Module&#34;&gt;Trusted Platform Module&lt;/a&gt; of a computer. For details, please consult systemd&amp;rsquo;s documentation.&lt;/p&gt;
&lt;p&gt;Now that we know how they work, the only thing left to do is to figure out how we can get Spring Boot to read systemd credentials from files in &lt;code&gt;CREDENTIALS_DIRECTORY&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;externalized-configuration-in-spring-boot&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#externalized-configuration-in-spring-boot&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Externalized Configuration in Spring Boot&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;In Spring Boot, &lt;a href=&#34;https://docs.spring.io/spring-boot/3.5/reference/features/external-config.html&#34;&gt;configuration&lt;/a&gt; is declared as properties like &lt;code&gt;spring.datasource.url&lt;/code&gt;. Those properties are not only used to configure Spring Boot itself. You can define your own properties to make your application configurable through the very same system. Spring Boot can obtain configuration values from a multitude of sources, for example, &lt;a href=&#34;https://en.wikipedia.org/wiki/.properties&#34;&gt;.properties&lt;/a&gt;, the environment, &lt;a href=&#34;https://en.wikipedia.org/wiki/Java_Naming_and_Directory_Interface&#34;&gt;JNDI&lt;/a&gt; or Kubernetes &lt;a href=&#34;https://kubernetes.io/docs/concepts/configuration/configmap/&#34;&gt;ConfigMaps&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In addition to properties, Spring Boot has dedicated &lt;a href=&#34;https://docs.spring.io/spring-boot/3.5/reference/features/ssl.html&#34;&gt;SSL bundles&lt;/a&gt; to load keys and certificates from files.&lt;/p&gt;
&lt;h2 id=&#34;populating-ssl-bundles-from-systemd-credentials&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#populating-ssl-bundles-from-systemd-credentials&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Populating SSL Bundles From systemd Credentials&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;I start with the simplest case: populating SSL bundles from systemd credentials.&lt;/p&gt;
&lt;p&gt;Without systemd credentials, the configuration to define an SSL bundle called &lt;code&gt;mybundle&lt;/code&gt; to be used by the embedded web server looks as follows:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-properties&#34; data-lang=&#34;properties&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;spring.ssl.bundle.pem.mybundle.keystore.certificate&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;file:/path/to/domain.crt&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;spring.ssl.bundle.pem.mybundle.keystore.private-key&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;file:/path/to/domain.key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To load the certificate and private key from a systemd credential, specify them in the unit file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[Service]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ExecStart=/path/to/java -jar /path/to/demo-0.0.1-SNAPSHOT.jar 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;LoadCredential=domain.key:/path/to/domain.key
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;LoadCredential=domain.crt:/path/to/domain.crt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;ExecStart=&lt;/code&gt; defines the executable JAR to launch. The next two lines starting with &lt;code&gt;LoadCredential=&lt;/code&gt; specify the private key (&lt;code&gt;/path/to/domain.key&lt;/code&gt;) and certificate (&lt;code&gt;/path/to/domain.crt&lt;/code&gt;) that will be made available to the application as &lt;code&gt;domain.key&lt;/code&gt; and &lt;code&gt;domain.crt&lt;/code&gt; respectively. The only step remaining is to tell Spring Boot to read both files from &lt;code&gt;CREDENTIALS_DIRECTORY&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-properties&#34; data-lang=&#34;properties&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;spring.ssl.bundle.pem.mybundle.keystore.certificate&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;file:${CREDENTIALS_DIRECTORY}/domain.crt&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;spring.ssl.bundle.pem.mybundle.keystore.private-key&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;file:${CREDENTIALS_DIRECTORY}/domain.key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;using-systemd-credentials-in-any-configuration-property&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#using-systemd-credentials-in-any-configuration-property&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Using systemd Credentials in Any Configuration Property&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Now, most Spring Boot applications will probably want to access a database. And that database will most likely require a password, which can be configured with &lt;code&gt;spring.datasource.password&lt;/code&gt;. Unfortunately, we cannot leverage our newly learnt knowledge and use the &lt;code&gt;file:&lt;/code&gt; prefix to load the database password from a file because &lt;code&gt;file:&lt;/code&gt; is only understood by SSL bundles. Instead, we have to repurpose a &lt;a href=&#34;https://docs.spring.io/spring-boot/3.5/reference/features/external-config.html#features.external-config.files.configtree&#34;&gt;configuration tree&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A configuration tree turns files into Spring Boot properties by converting their file name into the property&amp;rsquo;s name and the file&amp;rsquo;s content into the property&amp;rsquo;s value:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[Service]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ExecStart=/path/to/java \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -jar /path/to/demo-0.0.1-SNAPSHOT.jar \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    --spring.config.import=configtree:${CREDENTIALS_DIRECTORY}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;LoadCredential=spring.datasource.password:/path/to/database-password
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;--spring.config.import&lt;/code&gt; is the magical argument that makes it all work. If you prefer, you can put &lt;code&gt;spring.config.import=configtree:${CREDENTIALS_DIRECTORY}&lt;/code&gt; into your &lt;code&gt;application.properties&lt;/code&gt;, instead.&lt;/p&gt;
&lt;p&gt;If you find it too cumbersome to specify every secret individually, you can treat your entire configuration as a systemd credential and load it from &lt;code&gt;CREDENTIALS_DIRECTORY&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[Service]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ExecStart=/path/to/java \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -jar /path/to/demo-0.0.1-SNAPSHOT.jar \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    --spring.config.import=${CREDENTIALS_DIRECTORY}/application.properties
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;LoadCredential=application.properties:/path/to/application.properties
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;credentials-do-not-belong-in-environment-variables&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#credentials-do-not-belong-in-environment-variables&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Credentials Do Not Belong in Environment Variables&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;While environment variables are handy for configuring an application, they have many drawbacks. Subprocesses inherit them, they are awkward to use with binary data, have size limitations, and can be accessed easily with various tools. &lt;code&gt;ps auxe&lt;/code&gt; lists them and &lt;code&gt;systemctl show &amp;lt;service&amp;gt;&lt;/code&gt; allows any user on a system to print them if they are defined in a unit file.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;To make it only accessible to &lt;code&gt;my-program&lt;/code&gt; enable mount namespacing  with &lt;a href=&#34;https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#PrivateMounts=&#34;&gt;&lt;code&gt;PrivateMounts=&lt;/code&gt;&lt;/a&gt;.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
		</item>
		
		<item>
			<title>Finding EFI Firmware That Is Compatible</title>
			<link>https://aahlenst.dev/blog/finding-efi-firmware-that-is-compatible/</link>
			<pubDate>Sun, 07 Jul 2024 12:00:00 +0200</pubDate>
			
			<guid>https://aahlenst.dev/blog/finding-efi-firmware-that-is-compatible/</guid>
			<description>&lt;p&gt;I run some QEMU/KVM virtual machines as part of one of my GitHub Actions workflows. To reduce the workflow&amp;rsquo;s runtime and to help make it reproducible, it uses pre-built disk images and pre-defined XML domain configurations for &lt;a href=&#34;https://libvirt.org/&#34;&gt;libvirt&lt;/a&gt;. That worked well until I changed the workflow to use the &lt;a href=&#34;https://github.blog/changelog/2024-05-14-github-hosted-runners-public-beta-of-ubuntu-24-04-is-now-available/&#34;&gt;new Ubuntu 24.04 images&lt;/a&gt;. Afterwards, libvirt rejected the domain configuration with an error I had not encountered before:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;libvirt.libvirtError: operation failed: Unable to find &amp;rsquo;efi&amp;rsquo; firmware that is compatible with the current configuration&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Why does a virtual machine image suddenly stop working just because I upgraded Ubuntu? I was stumped, particularly because a quick web search did not turn up much useful information.&lt;/p&gt;
&lt;p&gt;The offending domain configuration looked as follows:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;os&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;firmware=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;efi&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;arch=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;x86_64&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;machine=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;q35&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;hvm&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/type&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;loader&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;readonly=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;yes&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;type=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;pflash&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;/usr/share/OVMF/OVMF_CODE.fd&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/loader&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;nvram&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;template=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;/usr/share/OVMF/OVMF_VARS.fd&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;/var/lib/libvirt/qemu/nvram/mymachine_VARS.fd&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/nvram&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;boot&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;dev=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;hd&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/os&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;&amp;lt;loader/&amp;gt;&lt;/code&gt; describes the UEFI firmware to use. &lt;code&gt;&amp;lt;nvram/&amp;gt;&lt;/code&gt; defines the path to the firmware&amp;rsquo;s variable store. It is machine-specific and contains the path to the boot loader, for example. See &lt;a href=&#34;https://libvirt.org/formatdomain.html#bios-bootloader&#34;&gt;libvirt&amp;rsquo;s Domain XML format documentation&lt;/a&gt; for further information.&lt;/p&gt;
&lt;p&gt;Thankfully, the mailing list &lt;a href=&#34;https://lists.libvirt.org/archives/list/devel@lists.libvirt.org/thread/LJ7WZHSD7J72IMTJZQROFAECLPS2OIHP/&#34;&gt;message accompanying the patch that introduced the error message into libvirt&lt;/a&gt; explains what the problem is:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The [old] message can be misleading, because it seems to suggest that no firmware of the requested type is available on the system.&lt;/p&gt;
&lt;p&gt;What actually happens most of the time, however, is that despite having multiple firmwares of the right type to choose from, none of them is suitable because of lacking some specific feature or being incompatible with some setting that the user has explicitly enabled.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In Stephen Finucane&amp;rsquo;s helpful article &lt;a href=&#34;https://that.guru/blog/uefi-secure-boot-in-libvirt/&#34;&gt;UEFI Support in Libvirt&lt;/a&gt;, I learnt that we can ask libvirt what firmware it knows about by running:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ virsh domcapabilities --machine q35 | xmllint --xpath &amp;#39;/domainCapabilities/os&amp;#39; -
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And sure enough, &lt;code&gt;/usr/share/OVMF/OVMF_CODE.fd&lt;/code&gt; is no longer there because it has been removed&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; from the &lt;a href=&#34;https://packages.ubuntu.com/noble/ovmf/all/filelist&#34;&gt;ovmf package&lt;/a&gt; shipped with Ubuntu 24.04:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;os&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;supported=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;yes&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;enum&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;firmware&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;efi&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/enum&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;loader&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;supported=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;yes&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;/usr/share/OVMF/OVMF_CODE_4M.ms.fd&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;/usr/share/OVMF/OVMF_CODE_4M.secboot.fd&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;/usr/share/OVMF/OVMF_CODE_4M.fd&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;enum&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;rom&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;pflash&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/enum&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;enum&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;readonly&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;yes&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;no&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/enum&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;enum&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;secure&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;yes&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;no&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/enum&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/loader&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/os&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;solving-the-problem-of-the-missing-firmware&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#solving-the-problem-of-the-missing-firmware&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Solving the Problem of the Missing Firmware&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;We have multiple options for dealing with the missing firmware, and none is fun. The reason is that the firmware (&lt;code&gt;OVMF_CODE.fd&lt;/code&gt;) and its variable store (&lt;code&gt;OVMF_VARS.fd&lt;/code&gt;) are inextricably linked. It is impossible to change the firmware only to &lt;code&gt;OVMF_CODE_4M.fd&lt;/code&gt; and continue using the old variable store. The virtual machine simply will not boot.&lt;/p&gt;
&lt;h3 id=&#34;recreating-the-virtual-machine-images&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#recreating-the-virtual-machine-images&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Recreating the Virtual Machine Images&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;As I use &lt;a href=&#34;https://packer.io&#34;&gt;Packer&lt;/a&gt;, re-creating the virtual machine with a different firmware image is a matter of minutes. While older versions of Debian and Ubuntu already come with &lt;code&gt;OVMF_CODE_4M.fd&lt;/code&gt;, Fedora also switched from a raw to a qcow2 file (&lt;code&gt;OVMF_CODE_4M.qcow2&lt;/code&gt;), making sharing the same virtual machine image across distributions impossible. If that is important to you, you might fare better with &lt;a href=&#34;#adding-ovmf_codefd-back-in&#34;&gt;Adding OVMF_CODE.fd Back In&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;migrating-to-ovmf_code_4mfd&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#migrating-to-ovmf_code_4mfd&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Migrating to OVMF_CODE_4M.fd&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;If it is difficult to re-create the virtual machine, there is an option to migrate to &lt;code&gt;OVMF_CODE_4M.fd&lt;/code&gt;. Debian 12 and Ubuntu 24.04 include the program &lt;code&gt;2M_VARS-to-4M_VARS.sh&lt;/code&gt; (&lt;a href=&#34;https://salsa.debian.org/qemu-team/edk2/-/blob/561e9db908b26e79fb8283bfa84908677a2341e6/debian/2M_VARS-to-4M_VARS.sh&#34;&gt;direct link&lt;/a&gt;) in the ovmf package. According to the &lt;a href=&#34;https://salsa.debian.org/qemu-team/edk2/-/blob/debian/debian/howto-2M-to-4M-migration.md?ref_type=heads&#34;&gt;HOWTO&lt;/a&gt;, converting the old variable store to the 4M variant becomes a matter of running:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ 2M_VARS-to-4M_VARS.sh -i mymachine_VARS.fd 
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;adding-ovmf_codefd-back-in&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#adding-ovmf_codefd-back-in&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Adding OVMF_CODE.fd Back In&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;OVMF is portable and can be copied between systems. Unfortunately, it is insufficient to grab  &lt;code&gt;OVMF_CODE.fd&lt;/code&gt; and &lt;code&gt;OVMF_VARS.fd&lt;/code&gt; from a system that still has them and put them into &lt;code&gt;/usr/share/OVMF&lt;/code&gt;. If you do this and run &lt;code&gt;virsh domcapabilities&lt;/code&gt;, they will not show up, and libvirt will not accept the domain configuration despite the firmware image and variable store being in the right place.&lt;/p&gt;
&lt;p&gt;You also need a matching firmware descriptor in &lt;code&gt;/usr/share/qemu/firmware&lt;/code&gt;. These descriptors not only record the known firmware images but also the supported machine types and features like Secure Boot. It is usually sufficient to grab the JSON file that refers to &lt;code&gt;OVMF_CODE.fd&lt;/code&gt; and copy it over. The firmware should then appear in &lt;code&gt;virsh domcapabilities&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;resetting-the-variable-store&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#resetting-the-variable-store&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Resetting the Variable Store&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;That is the approach for adventurous people. Change the firmware to &lt;code&gt;OVMF_CODE_4M.fd&lt;/code&gt; and reset the variable store when starting the domain for the next time:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ virsh start --reset-nvram domain
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then, use the &lt;a href=&#34;https://mricher.fr/post/boot-from-an-efi-shell/&#34;&gt;UEFI shell to manually select the boot loader&lt;/a&gt;, start the system and re-install grub-efi. If the guest is a Windows machine, it should boot right away without you ever seeing the UEFI shell (see below for why this works).&lt;/p&gt;
&lt;h2 id=&#34;solving-the-problem-of-the-incompatible-firmware&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#solving-the-problem-of-the-incompatible-firmware&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Solving the Problem of the Incompatible Firmware&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Not only can the firmware be absent. It can also be incompatible with the domain configuration, resulting in the same error message. Usually, that happens when creating a domain for the first time.&lt;/p&gt;
&lt;p&gt;The key to finding the problem is to look at the domain configuration and compare it with the output of &lt;code&gt;virsh domcapabilities&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s suppose the domain configuration looks as follows:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;os&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;firmware=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;efi&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;arch=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;x86_64&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;machine=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;pc&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;hvm&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/type&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;loader&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;secure=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;no&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;boot&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;dev=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;hd&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/os&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To list the supported configurations, run:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ virsh domcapabilities --machine pc | xmllint --xpath &amp;#39;/domainCapabilities/os&amp;#39; -
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;On my machine, it yields&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;os&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;supported=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;yes&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;enum&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;firmware&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;efi&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/enum&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;loader&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;supported=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;yes&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;/usr/share/edk2/ovmf/OVMF_CODE_4M.qcow2&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;/usr/share/edk2/ovmf/OVMF_CODE.fd&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;enum&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;rom&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;pflash&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/enum&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;enum&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;readonly&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;yes&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;no&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/enum&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;enum&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;secure&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;no&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/enum&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/loader&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/os&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As we gather from the output, the configuration above should work. But if we were to enable Secure Boot, it would no longer work because the only supported value for &lt;code&gt;secure&lt;/code&gt; is &lt;code&gt;no&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Generally speaking, there are two ways to fix such a problem:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Change the domain configuration.&lt;/li&gt;
&lt;li&gt;Install firmware that supports the domain configuration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As such problems usually manifest when creating the domain for the first time, changing the domain configuration to match the capabilities is the best option. Furthermore, Linux distributions typically include firmware that covers all supported combinations. If you encounter a combination that is not valid, that is because it is not supported at all.&lt;/p&gt;
&lt;p&gt;The only case I can think of where installing firmware makes sense is to test old configurations no longer supported by newer Linux distributions. One example would be using firmware for 2 MB flash devices that are no longer present on Ubuntu 24.04 and newer. In those cases, you can follow the advice in the section &lt;a href=&#34;#adding-ovmf_codefd-back-in&#34;&gt;Adding OVMF_CODE.fd Back In&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;the-easy-way-out&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#the-easy-way-out&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;The Easy Way Out&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;The hands-down easiest way to avoid any problems with UEFI firmware is to forego UEFI boot and rely on the venerable BIOS to boot. You have never seen your problems go away this fast.&lt;/p&gt;
&lt;p&gt;If that is not an option, it is time to do it the Windows way. Also known as &lt;em&gt;doing it wrong&lt;/em&gt;. In addition to placing the boot loader into the vendor-specific directory (&lt;code&gt;\EFI\Microsoft\boot&lt;/code&gt;) in the EFI System Partition (ESP), the Windows installer puts it into the Removable Media Path (&lt;code&gt;\EFI\boot\&lt;/code&gt;), too. The reason is that every firmware knows how to start a system from the Removable Media Path because that is how installers are started from removable media (hence the name). By putting the boot loader into the Removable Media Path, Windows avoids problems with buggy boot loaders.&lt;/p&gt;
&lt;p&gt;We can do the same and force the installation of grub-efi onto the Removable Media Path. Because the Removable Media Path is known, we no longer need a variable store (&lt;code&gt;OVMF_VARS.fd&lt;/code&gt; and friends) that tells the firmware where to look for the boot loader. Consequently, we can get rid of the &lt;code&gt;&amp;lt;nvram/&amp;gt;&lt;/code&gt; entry in our domain configuration and tell libvirt to select whatever firmware it finds automatically:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;os&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;firmware=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;efi&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;arch=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;x86_64&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;machine=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;q35&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;hvm&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/type&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;loader&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;secure=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;no&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;boot&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;dev=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;hd&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/os&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That should work everywhere as long as there is some UEFI firmware on the system. Use &lt;code&gt;secure=&#39;yes&#39;&lt;/code&gt; for UEFI Secure Boot.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://unix.stackexchange.com/a/571173/610434&#34;&gt;This excellent post on the Unix &amp;amp; Linux StackExchange&lt;/a&gt; provides a deep dive into the different boot loaders. &lt;a href=&#34;https://wiki.debian.org/UEFI#Force_grub-efi_installation_to_the_removable_media_path&#34;&gt;Force grub-efi installation to the removable media path&lt;/a&gt; on the Debian Wiki&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; provides an  explanation of why using the Removable Media Path is wrong, and how to force grub-efi into the Removable Media Path on Debian. Microsoft has instructions on &lt;a href=&#34;https://learn.microsoft.com/en-us/windows-server/virtualization/hyper-v/supported-ubuntu-virtual-machines-on-hyper-v#notes&#34;&gt;how to do it manually on Ubuntu&lt;/a&gt;&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;The firmware image &lt;code&gt;OVMF_CODE.fd&lt;/code&gt; (and its siblings with a similar name) is for guests with a 2 MB flash device. &lt;a href=&#34;https://salsa.debian.org/qemu-team/edk2/-/blob/47c28ba2eb50e0276cd78dc27bf4cffcee7a0644/debian/ovmf.README.Debian#L48-L50&#34;&gt;2 MB flash is no longer considered sufficient for use with Secure Boot&lt;/a&gt; and does no longer seem to work with recent versions of Windows 11.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;If you ever need to know more about UEFI, the &lt;a href=&#34;https://wiki.debian.org/UEFI&#34;&gt;UEFI page on the Debian Wiki&lt;/a&gt; is a great start.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;Because relying on the Removable Media Path is apparently the only way to boot a Linux guest on a Hyper-V Generation 2 virtual machine.&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
		</item>
		
		<item>
			<title>Storing Blobs on the GitHub Container Registry</title>
			<link>https://aahlenst.dev/blog/storing-blobs-on-github-container-registry/</link>
			<pubDate>Sat, 06 Jul 2024 12:00:00 +0200</pubDate>
			
			<guid>https://aahlenst.dev/blog/storing-blobs-on-github-container-registry/</guid>
			<description>&lt;p&gt;Recently, I had the need to access some virtual machine images in one of my GitHub Actions workflows. As they came in at more than 1 GB, I was hard-pressed for a place to store them that was also easy to access from a GitHub Actions workflow. If only I could upload a ZIP archive to &lt;a href=&#34;https://github.com/features/packages&#34;&gt;GitHub Packages&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;It turns out that it is not only possible but also much easier than I imagined it to be. Let&amp;rsquo;s learn how.&lt;/p&gt;
&lt;h2 id=&#34;container-images-are-nothing-but-fancy-tarballs&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#container-images-are-nothing-but-fancy-tarballs&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Container Images Are Nothing but Fancy Tarballs&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;The key is to realise that container images (those things that you feed to &lt;code&gt;docker run&lt;/code&gt;) are &lt;a href=&#34;https://hackernoon.com/give-me-a-unix-shell-and-ill-build-container-images-for-life&#34;&gt;nothing but tarballs and some metadata bundled together&lt;/a&gt;. There is no rule&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; that says &amp;ldquo;Thou shalt only store operating systems in container images.&amp;rdquo; So, there is nothing that stops us from basically storing &lt;em&gt;anything&lt;/em&gt; in a container image. And those can be uploaded to the GitHub Container Registry (GHCR), which is part of the GitHub Packages offering, or any other container registry.&lt;/p&gt;
&lt;p&gt;Now, we only need to find a tool that makes putting anything into a container image easy. Enter &lt;code&gt;oras&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;upload-anything-with-oras&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#upload-anything-with-oras&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Upload Anything with ORAS&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;oras&lt;/code&gt; is a nifty command-line tool made by the &lt;a href=&#34;https://oras.land/&#34;&gt;ORAS project&lt;/a&gt;. ORAS stands for &amp;ldquo;OCI Registry As Storage&amp;rdquo;, and OCI is the &lt;a href=&#34;https://opencontainers.org/&#34;&gt;Open Container Initiative&lt;/a&gt;. OCI is the standards body regulating the format of container images, registries, and so on. They ensure that any container image can be run by any runtime (Docker, Podman, containerd, …) and can be uploaded to any registry (GHCR, Docker Hub, …). Thanks to this standardisation, &lt;code&gt;oras&lt;/code&gt; is compatible with &lt;a href=&#34;https://oras.land/adopters&#34;&gt;a dozen different registries&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;oras&lt;/code&gt; itself is available for all major operating systems and most common platforms. You can either download and run the binary from &lt;a href=&#34;https://github.com/oras-project/oras/releases&#34;&gt;GitHub Releases&lt;/a&gt; or use &lt;a href=&#34;https://oras.land/docs/installation&#34;&gt;one of the other installation methods&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I have prepared a directory called &lt;code&gt;data&lt;/code&gt; that I want to store on GHCR. It looks like this:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ tree data
data
├── 50m.bin
└── a-directory
    └── hello.txt

2 directories, 2 files
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Uploading it to GHCR is as simple as running:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ oras push ghcr.io/example/data:1 data
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To download the files, create an empty directory and change into it. Then run:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ oras pull ghcr.io/example/data:1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That&amp;rsquo;s it! There is now a directory called &lt;code&gt;data&lt;/code&gt; in the current working directory that looks exactly like the one I uploaded above:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ tree data
data
├── 50m.bin
└── a-directory
    └── hello.txt

2 directories, 2 files
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;While this is not exactly &amp;ldquo;uploading a ZIP archive&amp;rdquo;, it is actually much better: You do not have to create the ZIP archive yourself, checksum verification is built-in, and in some circumstances, it is even possible to change the container image without having to re-upload everything.&lt;/p&gt;
&lt;h2 id=&#34;saving-space-and-bandwith-with-layers&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#saving-space-and-bandwith-with-layers&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Saving Space and Bandwith with Layers&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;In the background, &lt;code&gt;oras&lt;/code&gt; takes the directory and turns it into an &lt;a href=&#34;https://github.com/opencontainers/image-spec/blob/main/spec.md&#34;&gt;OCI container image&lt;/a&gt; before uploading it. That is the same kind of image that Docker uses. If you have used Docker before, you probably heard about &amp;ldquo;layers&amp;rdquo;. In a nutshell, a container image consists of one or more layers. Layer is just a fancy term for a &lt;a href=&#34;https://en.wikipedia.org/wiki/Tarball_(computing)&#34;&gt;tarball&lt;/a&gt;. Having multiple tarballs in an image instead of a single one helps with caching and reducing the amount of storage consumed by all those images in a registry. &lt;code&gt;oras&lt;/code&gt; creates layers, too, as we can see when we look at the manifest&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; of the container image we uploaded:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ oras manifest fetch ghcr.io/example/data:1 | jq
{
  &amp;#34;schemaVersion&amp;#34;: 2,
  &amp;#34;mediaType&amp;#34;: &amp;#34;application/vnd.oci.image.manifest.v1+json&amp;#34;,
  &amp;#34;artifactType&amp;#34;: &amp;#34;application/vnd.unknown.artifact.v1&amp;#34;,
  &amp;#34;config&amp;#34;: {
    &amp;#34;mediaType&amp;#34;: &amp;#34;application/vnd.oci.empty.v1+json&amp;#34;,
    &amp;#34;digest&amp;#34;: &amp;#34;sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a&amp;#34;,
    &amp;#34;size&amp;#34;: 2,
    &amp;#34;data&amp;#34;: &amp;#34;e30=&amp;#34;
  },
  &amp;#34;layers&amp;#34;: [
    {
      &amp;#34;mediaType&amp;#34;: &amp;#34;application/vnd.oci.image.layer.v1.tar+gzip&amp;#34;,
      &amp;#34;digest&amp;#34;: &amp;#34;sha256:8c9d7307a4263817ad8dd2b845c4bac3a4a59621d98a063d3482df77763e7cee&amp;#34;,
      &amp;#34;size&amp;#34;: 52445175,
      &amp;#34;annotations&amp;#34;: {
        &amp;#34;io.deis.oras.content.digest&amp;#34;: &amp;#34;sha256:7520f1358115aa8ffd0ca65b22ba5bf9ef4555e9f9212032f65f8cf91e7ec93a&amp;#34;,
        &amp;#34;io.deis.oras.content.unpack&amp;#34;: &amp;#34;true&amp;#34;,
        &amp;#34;org.opencontainers.image.title&amp;#34;: &amp;#34;data&amp;#34;
      }
    }
  ],
  &amp;#34;annotations&amp;#34;: {
    &amp;#34;org.opencontainers.image.created&amp;#34;: &amp;#34;2024-07-04T15:18:29Z&amp;#34;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There it is, a single layer with the SHA-256 checksum &lt;code&gt;8c9d7307a4263817ad8dd2b845c4bac3a4a59621d98a063d3482df77763e7cee&lt;/code&gt;. There is even an annotation called &lt;code&gt;org.opencontainers.image.title&lt;/code&gt; with the folder&amp;rsquo;s name: &lt;code&gt;data&lt;/code&gt;. The manifest of a &amp;ldquo;normal&amp;rdquo; Docker image does not look much different:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ oras manifest fetch --platform linux/amd64 docker.io/library/postgres:16.3 | jq
{
  &amp;#34;schemaVersion&amp;#34;: 2,
  &amp;#34;mediaType&amp;#34;: &amp;#34;application/vnd.oci.image.manifest.v1+json&amp;#34;,
  &amp;#34;config&amp;#34;: {
    &amp;#34;mediaType&amp;#34;: &amp;#34;application/vnd.oci.image.config.v1+json&amp;#34;,
    &amp;#34;digest&amp;#34;: &amp;#34;sha256:f23dc7cd74bd7693fc164fd829b9a7fa1edf8eaaed488c117312aef2a48cafaa&amp;#34;,
    &amp;#34;size&amp;#34;: 10091
  },
  &amp;#34;layers&amp;#34;: [
    {
      &amp;#34;mediaType&amp;#34;: &amp;#34;application/vnd.oci.image.layer.v1.tar+gzip&amp;#34;,
      &amp;#34;digest&amp;#34;: &amp;#34;sha256:f11c1adaa26e078479ccdd45312ea3b88476441b91be0ec898a7e07bfd05badc&amp;#34;,
      &amp;#34;size&amp;#34;: 29126278
    },
    // Many more layers omitted.
    {
      &amp;#34;mediaType&amp;#34;: &amp;#34;application/vnd.oci.image.layer.v1.tar+gzip&amp;#34;,
      &amp;#34;digest&amp;#34;: &amp;#34;sha256:95c2c2ef9f02d7666e80992c98c53c9ec7b5e8ccf244d00a5c85e46bbc2820ae&amp;#34;,
      &amp;#34;size&amp;#34;: 184
    }
  ],
  &amp;#34;annotations&amp;#34;: {
    &amp;#34;com.docker.official-images.bashbrew.arch&amp;#34;: &amp;#34;amd64&amp;#34;,
    &amp;#34;org.opencontainers.image.base.digest&amp;#34;: &amp;#34;sha256:39868a6f452462b70cf720a8daff250c63e7342970e749059c105bf7c1e8eeaf&amp;#34;,
    &amp;#34;org.opencontainers.image.base.name&amp;#34;: &amp;#34;debian:bookworm-slim&amp;#34;,
    &amp;#34;org.opencontainers.image.created&amp;#34;: &amp;#34;2024-05-09T18:58:11Z&amp;#34;,
    &amp;#34;org.opencontainers.image.revision&amp;#34;: &amp;#34;d08757ccb56ee047efd76c41dbc148e2e2c4f68f&amp;#34;,
    &amp;#34;org.opencontainers.image.source&amp;#34;: &amp;#34;https://github.com/docker-library/postgres.git#d08757ccb56ee047efd76c41dbc148e2e2c4f68f:16/bookworm&amp;#34;,
    &amp;#34;org.opencontainers.image.url&amp;#34;: &amp;#34;https://hub.docker.com/_/postgres&amp;#34;,
    &amp;#34;org.opencontainers.image.version&amp;#34;: &amp;#34;16.3&amp;#34;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Back to those layers. As I mentioned before, it is possible to change the container image created by &lt;code&gt;oras&lt;/code&gt; in some circumstances without re-uploading everything. Those circumstances have a lot to do with those layers. When you run &lt;code&gt;oras push &amp;lt;name&amp;gt; &amp;lt;file&amp;gt; [...]&lt;/code&gt;, &lt;code&gt;oras&lt;/code&gt; creates a separate layer per argument. Let&amp;rsquo;s upload the same directory as before, but this time, specify every file as a separate argument:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ oras push ghcr.io/example/data:1 data/50m.bin data/a-directory/hello.txt
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;While the result on disk is the same when we download the image again, the manifest looks different:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ oras manifest fetch ghcr.io/example/data:1 | jq                            
{
  &amp;#34;schemaVersion&amp;#34;: 2,
  &amp;#34;mediaType&amp;#34;: &amp;#34;application/vnd.oci.image.manifest.v1+json&amp;#34;,
  &amp;#34;artifactType&amp;#34;: &amp;#34;application/vnd.unknown.artifact.v1&amp;#34;,
  &amp;#34;config&amp;#34;: {
    &amp;#34;mediaType&amp;#34;: &amp;#34;application/vnd.oci.empty.v1+json&amp;#34;,
    &amp;#34;digest&amp;#34;: &amp;#34;sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a&amp;#34;,
    &amp;#34;size&amp;#34;: 2,
    &amp;#34;data&amp;#34;: &amp;#34;e30=&amp;#34;
  },
  &amp;#34;layers&amp;#34;: [
    {
      &amp;#34;mediaType&amp;#34;: &amp;#34;application/vnd.oci.image.layer.v1.tar&amp;#34;,
      &amp;#34;digest&amp;#34;: &amp;#34;sha256:1e4c2dd682422beba2fa33db0f926935afe1414f722ee54be7788c6a6c40ebca&amp;#34;,
      &amp;#34;size&amp;#34;: 52428800,
      &amp;#34;annotations&amp;#34;: {
        &amp;#34;org.opencontainers.image.title&amp;#34;: &amp;#34;data/50m.bin&amp;#34;
      }
    },
    {
      &amp;#34;mediaType&amp;#34;: &amp;#34;application/vnd.oci.image.layer.v1.tar&amp;#34;,
      &amp;#34;digest&amp;#34;: &amp;#34;sha256:03ba204e50d126e4674c005e04d82e84c21366780af1f43bd54a37816b6ab340&amp;#34;,
      &amp;#34;size&amp;#34;: 13,
      &amp;#34;annotations&amp;#34;: {
        &amp;#34;org.opencontainers.image.title&amp;#34;: &amp;#34;data/a-directory/hello.txt&amp;#34;
      }
    }
  ],
  &amp;#34;annotations&amp;#34;: {
    &amp;#34;org.opencontainers.image.created&amp;#34;: &amp;#34;2024-07-05T13:51:19Z&amp;#34;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There is now one layer per file, two in total. That means you can now change the contents of the image in place by adding or removing layers. Let&amp;rsquo;s add another file, &lt;code&gt;data/10m.bin&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ oras push ghcr.io/example/data:1 data/50m.bin data/10m.bin data/a-directory/hello.txt
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You will see that &lt;code&gt;oras&lt;/code&gt; only uploads &lt;code&gt;data/10m.bin&lt;/code&gt; because the other two files (layers) are already part of the image. Omit &lt;code&gt;data/50m.bin&lt;/code&gt; and &lt;code&gt;oras&lt;/code&gt; will only delete its layer, leaving everything else in place:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ oras push ghcr.io/example/data:1 data/10m.bin data/a-directory/hello.txt
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So if you expect that you need to update parts of the container image frequently or you want to save space&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; when storing multiple images that share some contents, it might be beneficial to put every file, or at least every folder, in a separate layer as shown in the preceding examples. If you want to save on typing, &lt;code&gt;find&lt;/code&gt; and &lt;code&gt;xargs&lt;/code&gt; can help:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ find data -type f -print0 | xargs -r -0 oras push ghcr.io/example/data:1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;oras&lt;/code&gt; has more to offer, but what we have seen so far should suffice for everyday use.&lt;/p&gt;
&lt;h2 id=&#34;oras-in-a-github-actions-workflow&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#oras-in-a-github-actions-workflow&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;ORAS in a GitHub Actions Workflow&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;This is a minimal GitHub Actions workflow to download our &lt;code&gt;data&lt;/code&gt; folder onto the runner:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Build&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;on&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;push&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;jobs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;build&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Build&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;runs-on&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;ubuntu-latest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;permissions&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;contents&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;read&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;packages&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;read &lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# Required to access GHCR&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;steps&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Install oras&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;run&lt;/span&gt;: |&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;          sudo snap install oras --classic&lt;/span&gt;          
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Download data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;run&lt;/span&gt;: |&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;          oras login --username &amp;#34;${{ github.actor }}&amp;#34; --password &amp;#34;${{ secrets.GITHUB_TOKEN }}&amp;#34; ghcr.io
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;          oras pull ghcr.io/example/data:1&lt;/span&gt;          
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The highlights:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You need to declare the permission &lt;code&gt;packages: read&lt;/code&gt; to access GHCR. If you use a different container registry, you can omit it.&lt;/li&gt;
&lt;li&gt;I install &lt;code&gt;oras&lt;/code&gt; using &lt;code&gt;snap&lt;/code&gt;. Any other method is fine, too.&lt;/li&gt;
&lt;li&gt;Log into GHCR with &lt;code&gt;${{ github.actor }}&lt;/code&gt; as username and &lt;code&gt;${{ secrets.GITHUB_TOKEN }}&lt;/code&gt; as password. This also saves you some money&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then, you can use &lt;code&gt;oras&lt;/code&gt; as usual.&lt;/p&gt;
&lt;p&gt;If the container images you are accessing &lt;strong&gt;are private&lt;/strong&gt;, and they are private by default, you also have to link the image with the repository that the GitHub Actions workflow is part of. Otherwise, you get permission errors. There are two ways to do this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://docs.github.com/en/packages/learn-github-packages/connecting-a-repository-to-a-package&#34;&gt;Connect a repository to a package&lt;/a&gt; using the GitHub UI.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You can add the annotation &lt;code&gt;org.opencontainers.image.source&lt;/code&gt; to the container image. Assuming you want to access the image in &lt;code&gt;https://github.com/example/my-repository&lt;/code&gt;, then the command would look as follows:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ oras push ghcr.io/example/data:1 \
    -a &amp;#34;org.opencontainers.image.source=https://github.com/example/my-repository&amp;#34; \
    data/50m.bin data/10m.bin data/a-directory/hello.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;docker-can-do-this-too&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#docker-can-do-this-too&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Docker Can Do This, Too&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Before I stumbled upon ORAS, I tried my luck with a normal image builder. My preferred tool is &lt;a href=&#34;https://buildah.io/&#34;&gt;Buildah&lt;/a&gt;, and it can actually do it. The key is to use the empty base image &lt;code&gt;scratch&lt;/code&gt;. The equivalent to &lt;code&gt;oras push ghcr.io/example/data:1 data&lt;/code&gt; looks as follows:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ export newcontainer=$(buildah from scratch)
$ buildah unshare
$ buildah copy $newcontainer data /data
$ buildah unmount $newcontainer
$ buildah commit $newcontainer data
$ buildah rm $newcontainer
$ buildah push data:latest ghcr.io/example/data:1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When you think that this is kinda gross, it absolutely is.&lt;/p&gt;
&lt;p&gt;Docker does not fare better. First, we need a &lt;code&gt;Dockerfile&lt;/code&gt; next to the &lt;code&gt;data&lt;/code&gt; folder:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-Dockerfile&#34; data-lang=&#34;Dockerfile&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; scratch&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;COPY&lt;/span&gt; data /data&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, we can build the image and push it to GHCR:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ docker build -t ghcr.io/example/data:1 .
$ docker image push ghcr.io/example/data:1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href=&#34;https://github.com/containers/skopeo&#34;&gt;Skopeo&lt;/a&gt; is probably the best tool (relatively speaking) to get the &lt;code&gt;data&lt;/code&gt; folder back:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ skopeo copy docker://ghcr.io/example/data:1 dir:output
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This command will extract the image into the pre-existing folder &lt;code&gt;output&lt;/code&gt;. Unfortunately, we are far from done. We still have to look into the manifest to figure out which file contains the filesystem layer and what compression algorithm was used. Then, we can extract it with &lt;code&gt;tar&lt;/code&gt; to get our folder &lt;code&gt;data&lt;/code&gt; back.&lt;/p&gt;
&lt;p&gt;This is absolutely no fun, and nobody should do it. I only wanted to mention it. After all, you never know when this otherwise useless knowledge might come in handy.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;GitHub itself showcases that &lt;a href=&#34;https://github.blog/2021-06-21-github-packages-container-registry-generally-available/&#34;&gt;Homebrew stores at least half a petabyte of binaries on GHCR&lt;/a&gt;. If you are curious how Homebrew does it: &lt;a href=&#34;https://github.com/Homebrew/brew/blob/8c4c7319fc6ba3a69b1ba65659b03d418ebbfb2f/Library/Homebrew/github_packages.rb#L261-L465&#34;&gt;Homebrew writes the OCI image itself&lt;/a&gt; and then uploads it using &lt;a href=&#34;https://github.com/containers/skopeo&#34;&gt;skopeo&lt;/a&gt;.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;A manifest is a piece of metadata that describes the contents of a container image.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;Container registries usually store each layer only once, even if it is part of hundreds or thousands of images.&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://docs.github.com/en/billing/managing-billing-for-github-packages/about-billing-for-github-packages&#34;&gt;Data transfer is free of charge&lt;/a&gt; when GHCR is accessed with &lt;code&gt;GITHUB_TOKEN&lt;/code&gt;.&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
		</item>
		
		<item>
			<title>Helping You Find the JDK That Fits Your Needs</title>
			<link>https://aahlenst.dev/blog/helping-you-find-the-jdk-that-fits-your-needs/</link>
			<pubDate>Wed, 27 Sep 2023 12:00:00 +0200</pubDate>
			
			<guid>https://aahlenst.dev/blog/helping-you-find-the-jdk-that-fits-your-needs/</guid>
			<description>&lt;p&gt;In the last few years, the choice of JDKs has exploded. Almost everyone offers one, even &lt;a href=&#34;https://microsoft.com/openjdk/&#34;&gt;Microsoft&lt;/a&gt;. &lt;a href=&#34;https://faculty.washington.edu/jdb/345/345%20Articles/Iyengar%20%26%20Lepper%20(2000).pdf&#34;&gt;Unsurprisingly&lt;/a&gt;, people struggle to pick a JDK and are looking for help.&lt;/p&gt;
&lt;p&gt;While we can learn that &lt;a href=&#34;https://medium.com/@javachampions/java-is-still-free-3-0-0-ocrt-2021-bca75c88d23b&#34;&gt;Java Is Still Free&lt;/a&gt; and get told &lt;a href=&#34;https://whichjdk.com/&#34;&gt;which JDK we should use&lt;/a&gt;, nobody seems to provide a comprehensive overview of the differences between the various JDKs. The best I could find is &lt;a href=&#34;https://www.azul.com/products/core/jdk-comparison-matrix/&#34;&gt;OpenJDK vs Oracle JDK – Comparison Table&lt;/a&gt; authored by Azul&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Therefore, I have created &lt;a href=&#34;https://jdkcomparison.com/&#34;&gt;JDK Comparison&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;introducing-jdk-comparison&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#introducing-jdk-comparison&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Introducing JDK Comparison&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://jdkcomparison.com/&#34;&gt;JDK Comparison&lt;/a&gt; allows you to compare most&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; JDKs on the market, feature by feature. Multiple filters help you narrow your choices. There is even a button that lets you focus on the differences by hiding the commonalities. Dozens of explanations and footnotes should help you understand what each feature means and provide additional sourcing.&lt;/p&gt;
&lt;p&gt;While compiling the list, I learnt some interesting things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It is hard&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; to figure out what additional patches the vendors apply to their JDKs. In most cases, it is nothing more than a custom trust store and a handful of backports (&lt;em&gt;Customisations&lt;/em&gt; → &lt;em&gt;Customisations&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;If you need a &lt;a href=&#34;https://jdkcomparison.com/?platforms=Windows%2C+ARM%2C+64-bit&#34;&gt;JDK for Windows on ARM&lt;/a&gt;, you have only two options for JDK 21 and three options for JDK 17 (&lt;em&gt;Platforms: Windows&lt;/em&gt; → &lt;em&gt;ARM, 64-bit&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;Alibaba Dragonwell and Eclipse Temurin are the only JDKs with a &lt;a href=&#34;https://ntia.gov/page/software-bill-materials&#34;&gt;Software Bill of Materials&lt;/a&gt; (&lt;em&gt;Security&lt;/em&gt; → &lt;em&gt;SBOM&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;Eclipse Temurin is the only JDK with Windows-based container images (&lt;em&gt;Platforms: Windows&lt;/em&gt; → &lt;em&gt;Container Images&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;Neither Amazon nor Microsoft offer support for their JDKs for workloads that do not run on their clouds (&lt;em&gt;Support&lt;/em&gt; → &lt;em&gt;Paid Support&lt;/em&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Apart from the comparison itself, there is a page that answers &lt;a href=&#34;https://jdkcomparison.com/faq&#34;&gt;frequently asked questions&lt;/a&gt;. For example, it details the options you have if you want to &lt;a href=&#34;https://jdkcomparison.com/faq#what-are-my-options-if-i-need-javafx&#34;&gt;move away from Oracle JDK 8 and need JavaFX&lt;/a&gt; or &lt;a href=&#34;https://jdkcomparison.com/faq#what-are-my-options-if-i-need-java-web-start&#34;&gt;Web Start&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;tell-me-what-you-think&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#tell-me-what-you-think&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Tell Me What You Think&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;I am happy to add features to the comparison, expand the FAQ, or fix errors. I would also be thrilled to accept &lt;a href=&#34;https://github.com/aahlenst/jdkcomparison&#34;&gt;contributions&lt;/a&gt;. If you find the comparison helpful, it would be terrific if you spread the word or &lt;a href=&#34;https://aahlenst.dev/about/&#34;&gt;tell me directly about it&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Unfortunately, it contains some puzzling statements. For example, it claims that Amazon does not have the &amp;ldquo;Engineering capacity to root-cause &amp;amp; fix bugs&amp;rdquo; despite employing multiple prolific OpenJDK contributors.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;The FAQ outlines &lt;a href=&#34;https://jdkcomparison.com/faq#what-jdks-do-you-plan-on-adding-why-are-some-missing&#34;&gt;which JDKs are on the to-do list and why some will not be added&lt;/a&gt;.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;If you work for a vendor: Please publish change logs and link them from your downloads page. Ideally, publish the full source code that you used to build the JDK (not &lt;code&gt;src.zip&lt;/code&gt;) on the very same download page.&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
		</item>
		
		<item>
			<title>Testing Container Images</title>
			<link>https://aahlenst.dev/blog/testing-container-images/</link>
			<pubDate>Fri, 23 Dec 2022 12:00:00 +0100</pubDate>
			
			<guid>https://aahlenst.dev/blog/testing-container-images/</guid>
			<description>&lt;p&gt;More and more software is delivered as a container image for container runtimes like &lt;a href=&#34;https://www.docker.com/&#34;&gt;Docker&lt;/a&gt;, &lt;a href=&#34;https://podman.io/&#34;&gt;Podman&lt;/a&gt;, and &lt;a href=&#34;https://containerd.io/&#34;&gt;Containerd&lt;/a&gt;. Even simple container images require substantial logic for their creation and configuration (for example, the &lt;a href=&#34;https://github.com/nginxinc/docker-nginx/blob/5ce65c3efd395ee2d82d32670f233140e92dba99/Dockerfile-debian.template&#34;&gt;official nginx image&lt;/a&gt;). Hence, container images should be automatically tested like all other software deliverables to prevent bugs and security issues from creeping in.&lt;/p&gt;
&lt;p&gt;In this article, I present multiple tools that help to examine container images and their configuration.&lt;/p&gt;
&lt;p&gt;The basis forms a hypothetical container image for the webserver &lt;a href=&#34;https://nginx.org/&#34;&gt;nginx&lt;/a&gt; called &lt;code&gt;myimage&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-Dockerfile&#34; data-lang=&#34;Dockerfile&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; debian:buster&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;RUN&lt;/span&gt; apt-get update &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; DEBIAN_FRONTEND&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;noninteractive apt-get install -y &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;        tini nginx procps&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;ENTRYPOINT&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/usr/bin/tini&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;--&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;nginx&amp;#34;&lt;/span&gt;]&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;EXPOSE&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; 80&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;STOPSIGNAL&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; SIGQUIT&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;CMD&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;-g&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;daemon off;&amp;#34;&lt;/span&gt;]&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With each tool, I want to test the following aspects of the resulting container image:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Is nginx installed?&lt;/li&gt;
&lt;li&gt;Do the nginx workers run as an unprivileged user?&lt;/li&gt;
&lt;li&gt;Is nginx properly exposed to the host and serves a welcome page when accessed from the host?&lt;/li&gt;
&lt;li&gt;Does the container image only expose port 80 by default?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While these are very simple tests, they illustrate whether and how it is possible to assess the internal state of a container, the external state as it can be observed from the host, and the configuration of the container image.&lt;/p&gt;
&lt;p&gt;The complete source code can be found in my &lt;a href=&#34;https://github.com/aahlenst/testing-container-images&#34;&gt;sample repository on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;bash-automated-testing-system&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#bash-automated-testing-system&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Bash Automated Testing System&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://bats-core.readthedocs.io/&#34;&gt;Bash Automated Testing System&lt;/a&gt;, Bats for short, is a test framework for Bash. Being able to write the tests in Bash is the biggest advantage of Bats, but also its biggest drawback.&lt;/p&gt;
&lt;p&gt;What is great about writing tests in Bash is that there is no additional API or framework to learn. You can write the tests using the same commands you type into your terminal. Furthermore, anything that you can do in the terminal can be tested. That is not the case with the other tools presented here that provide dedicated but often limited built-in inspections to assess the state of a system.&lt;/p&gt;
&lt;p&gt;What is bad about writing tests in Bash is that you have to use Bash. Not because Bash were a bad choice, but because you suddenly have to deal with &lt;a href=&#34;https://ddanilov.me/docker-run-exec-and-carriage-return/&#34;&gt;unexpected carriage returns returned from Docker&lt;/a&gt; that are also invisible&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, lots of quoting, and &lt;a href=&#34;https://bats-core.readthedocs.io/en/stable/tutorial.html#dealing-with-output&#34;&gt;workarounds required by Bats to capture output&lt;/a&gt;. Furthermore, the lack of an API means that some of the tests cannot be portable&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;, and you need to figure out how to use tools like &lt;a href=&#34;https://www.man7.org/linux/man-pages/man1/ps.1.html&#34;&gt;ps&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Apart from that, you get what you would expect from a basic test framework, like &lt;a href=&#34;https://bats-core.readthedocs.io/en/stable/tutorial.html#avoiding-costly-repeated-setups&#34;&gt;setup functions&lt;/a&gt;, &lt;a href=&#34;https://github.com/bats-core/bats-assert&#34;&gt;assertions&lt;/a&gt;, and test reports that your build server can process, thanks to &lt;a href=&#34;https://testanything.org/&#34;&gt;TAP&lt;/a&gt; support.&lt;/p&gt;
&lt;p&gt;The test to check whether all nginx workers run as unprivileged user shows the advantages and problems of using Bats pretty well:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-Bash&#34; data-lang=&#34;Bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@test &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;nginx workers do not run as root&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    MASTER_PID&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;docker exec -i $CONTAINER_ID ps -ouser&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;,pid&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; -C nginx | awk &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;($1==&amp;#34;root&amp;#34;){print $2}&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    RESULT&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;docker exec -e MASTER_PID&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;$MASTER_PID -i $CONTAINER_ID ps -ouser&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; --ppid $MASTER_PID | uniq&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    assert_equal &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$RESULT&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;www-data&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;bats-in-a-nutshell&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#bats-in-a-nutshell&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Bats in a Nutshell&lt;/span&gt;
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;All tests could be implemented: ✅&lt;/li&gt;
&lt;li&gt;Generates test reports: ✅&lt;/li&gt;
&lt;li&gt;Supported container runtimes: any with a CLI&lt;/li&gt;
&lt;li&gt;Pros: Bash, easy to adopt&lt;/li&gt;
&lt;li&gt;Cons: You need to deal with Bash&amp;rsquo;s quirks, installation requires submodules, no built-in inspections&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/aahlenst/testing-container-images/tree/main/bats&#34;&gt;Complete source code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;goss&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#goss&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Goss&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://goss.rocks/&#34;&gt;Goss&lt;/a&gt; is similar to &lt;a href=&#34;#serverspec&#34;&gt;Serverspec&lt;/a&gt; and &lt;a href=&#34;#testinfra&#34;&gt;Testinfra&lt;/a&gt; in the sense that it is not a test framework but a tool to inspect the state of a system. But it is still very different.&lt;/p&gt;
&lt;p&gt;First, Goss runs on the target system. It &amp;ldquo;sees&amp;rdquo; the container only from the inside. Consequently, it is not possible to test whether nginx is properly exposed to the host or whether the image is correctly configured. You might not need that functionality, but it is something to be aware of.&lt;/p&gt;
&lt;p&gt;Then, Goss is declarative. Instead of writing tests, you describe the desired system state in a YAML file. Goss takes that YAML file and checks whether the system matches the expectations outlined in it. Fortunately, Goss can assist you in creating the YAML file. For example, run&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-Shell&#34; data-lang=&#34;Shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;goss add package nginx
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;and Goss will add an entry like&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-YAML&#34; data-lang=&#34;YAML&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;package&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;nginx&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;installed&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;versions&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    - &lt;span style=&#34;color:#ae81ff&#34;&gt;1.17.8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;to your YAML file if nginx is already installed on the system. If you do not care about the exact version being installed, you can remove the &lt;code&gt;versions&lt;/code&gt; attribute by hand.&lt;/p&gt;
&lt;p&gt;Like many declarative tools, Goss provides some means to escape the confines of being entirely declarative. Some attributes accept regular expressions, and there is basic support for some programming constructs like loops:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-YAML&#34; data-lang=&#34;YAML&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;file&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{- &lt;span style=&#34;color:#ae81ff&#34;&gt;range mkSlice &amp;#34;/etc/passwd&amp;#34; &amp;#34;/etc/group&amp;#34;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {{&lt;span style=&#34;color:#f92672&#34;&gt;.}}&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;exists&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;mode&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;0644&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;owner&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;root&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;group&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;root&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;filetype&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;file&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{&lt;span style=&#34;color:#ae81ff&#34;&gt;end}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Unfortunately, the syntax is proprietary and incompatible with &lt;code&gt;goss add&lt;/code&gt; (as of Goss 0.3.20).&lt;/p&gt;
&lt;p&gt;Like Testinfra and Serverspec, Goss has a decent but still limited number of inspections built-in. If something is not supported by Goss, you can fall back to the command line, like in this test that checks whether all nginx workers run as an unprivileged user:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-YAML&#34; data-lang=&#34;YAML&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;command&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;ps -ouser= --ppid $(ps -ouser=,pid= -C nginx | awk &amp;#39;($1==&amp;#34;root&amp;#34;){print $2}&amp;#39;) | uniq:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;exit-status&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;stdout&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    - &lt;span style=&#34;color:#ae81ff&#34;&gt;www-data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;stderr&lt;/span&gt;: []
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;timeout&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;10000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;goss-in-a-nutshell&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#goss-in-a-nutshell&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Goss in a Nutshell&lt;/span&gt;
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;All tests could be implemented: ❌&lt;/li&gt;
&lt;li&gt;Generates test reports: ✅&lt;/li&gt;
&lt;li&gt;Supported container runtimes: Docker, Docker Compose, Kubernetes&lt;/li&gt;
&lt;li&gt;Pros: No additional dependencies required, tests can be generated automatically, fast&lt;/li&gt;
&lt;li&gt;Cons: Can only assess the container internally, limited expressiveness due to YAML&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/aahlenst/testing-container-images/tree/main/goss&#34;&gt;Complete source code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;serverspec&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#serverspec&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Serverspec&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://serverspec.org/&#34;&gt;Serverspec&lt;/a&gt; is an extension for &lt;a href=&#34;https://rspec.info/&#34;&gt;RSpec&lt;/a&gt;, a BDD framework for Ruby. That means that you write your tests in Ruby as RSpec specifications.&lt;/p&gt;
&lt;p&gt;Serverspec provides many built-in inspections, called &lt;a href=&#34;https://serverspec.org/resource_types.html&#34;&gt;resource types&lt;/a&gt;, that simplify writing tests and give some portability. As a result, checking whether nginx is installed is as simple as&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-Ruby&#34; data-lang=&#34;Ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;describe package(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;nginx&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  it { should be_installed }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;regardless of the Linux distribution you are using. If the built-in inspections do not cut it, you can still fall back to the command line as I had to check whether all nginx workers run as an unprivileged user:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-Ruby&#34; data-lang=&#34;Ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;describe command(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;ps -ouser= --ppid $(ps -ouser=,pid= -C nginx | awk \&amp;#39;($1==&amp;#34;root&amp;#34;){print $2}\&amp;#39;) | uniq&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  its(&lt;span style=&#34;color:#e6db74&#34;&gt;:stdout&lt;/span&gt;) { should eq(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;www-data&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;) }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Thanks to Serverspec being a Rspec extension, you get all the features you can expect from a mature testing framework.&lt;/p&gt;
&lt;p&gt;The development of Serverspec has been slow in the past years, and the author only accepts pull requests, but no issues.&lt;/p&gt;
&lt;h3 id=&#34;serverspec-in-a-nutshell&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#serverspec-in-a-nutshell&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Serverspec in a Nutshell&lt;/span&gt;
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;All tests could be implemented: ✅&lt;/li&gt;
&lt;li&gt;Generates test reports: ✅&lt;/li&gt;
&lt;li&gt;Supported container runtimes: Docker&lt;/li&gt;
&lt;li&gt;Pros: Many built-in inspections, based on a mature test framework&lt;/li&gt;
&lt;li&gt;Cons: none&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/aahlenst/testing-container-images/tree/main/serverspec&#34;&gt;Complete source code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;testcontainers&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#testcontainers&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Testcontainers&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://testcontainers.org/&#34;&gt;Testcontainers&lt;/a&gt; is actually a tool that helps you write integration tests for your applications. But you can still misuse it for testing container images. In that regard, it is most similar to &lt;a href=&#34;#bash-automated-testing-system&#34;&gt;Bats&lt;/a&gt;. But instead of writing your tests in Bash, you can use Java.&lt;/p&gt;
&lt;div class=&#34;not-prose bg-blue-50 border-l-4 border-blue-400 p-4&#34;&gt;
	&lt;div class=&#34;flex&#34;&gt;
		&lt;div class=&#34;shrink-0&#34;&gt;
			&lt;svg class=&#34;h-8 w-8 text-blue-400&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34;
				 aria-hidden=&#34;true&#34;&gt;
				&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34;
					  d=&#34;M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z&#34;&gt;&lt;/path&gt;
			&lt;/svg&gt;
		&lt;/div&gt;
		&lt;div class=&#34;admonition ml-3 text-blue-700&#34;&gt;
			Testcontainers supports many programming languages besides Java, like C# or Go. I have not tried those and do not know what their capabilities are. This section only discusses the Java version.
		&lt;/div&gt;
	&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Testcontainers excels at simplifying the interaction with Docker. Starting, stopping, and inspecting containers is a breeze. On the other hand, it does not provide any built-in inspections, so you have to write them yourself. For example, the test to check whether all nginx workers run as an unprivileged user looks like that:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-Java&#34; data-lang=&#34;Java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Test&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;testNginxWorkersDoNotRunAsRoot&lt;/span&gt;() &lt;span style=&#34;color:#66d9ef&#34;&gt;throws&lt;/span&gt; IOException, InterruptedException {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; masterPidCmd &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; container.&lt;span style=&#34;color:#a6e22e&#34;&gt;execInContainer&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/bin/bash&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;-c&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ps -ouser=,pid= -C nginx | awk &amp;#39;($1==\&amp;#34;root\&amp;#34;){print $2}&amp;#39;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; masterPid &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Integer.&lt;span style=&#34;color:#a6e22e&#34;&gt;parseInt&lt;/span&gt;(masterPidCmd.&lt;span style=&#34;color:#a6e22e&#34;&gt;getStdout&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;trim&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; workersUserCmd &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; container.&lt;span style=&#34;color:#a6e22e&#34;&gt;execInContainer&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/bin/bash&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;-c&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			String.&lt;span style=&#34;color:#a6e22e&#34;&gt;format&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ps -ouser= --ppid %d | uniq&amp;#34;&lt;/span&gt;, masterPid));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	assertThat(workersUserCmd.&lt;span style=&#34;color:#a6e22e&#34;&gt;getStdout&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;trim&lt;/span&gt;()).&lt;span style=&#34;color:#a6e22e&#34;&gt;isEqualTo&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;www-data&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As we are using Java, we are free to choose between any of Java&amp;rsquo;s build tools, testing frameworks, and assertion libraries. I picked &lt;a href=&#34;https://junit.org/junit5&#34;&gt;JUnit 5&lt;/a&gt; and &lt;a href=&#34;https://assertj.github.io/&#34;&gt;AssertJ&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Testcontainers probably makes the most sense if the container tests are part of a larger Java project because you are probably using some of the tools already and can leverage &lt;a href=&#34;https://gradle.org/&#34;&gt;Gradle&lt;/a&gt; or &lt;a href=&#34;https://maven.apache.org/&#34;&gt;Maven&lt;/a&gt; to drive the build. But thanks to &lt;a href=&#34;https://jbang.dev/&#34;&gt;JBang&lt;/a&gt;, even a standalone usage is possible, as can be seen in the &lt;a href=&#34;https://github.com/aahlenst/testing-container-images/tree/main/testcontainers&#34;&gt;sample source code&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;testcontainers-in-a-nutshell&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#testcontainers-in-a-nutshell&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Testcontainers in a Nutshell&lt;/span&gt;
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;All tests could be implemented: ✅&lt;/li&gt;
&lt;li&gt;Generates test reports: ✅&lt;/li&gt;
&lt;li&gt;Supported container runtimes: Docker&lt;/li&gt;
&lt;li&gt;Pros: Simplifies interaction with Docker&lt;/li&gt;
&lt;li&gt;Cons: no built-in inspections&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/aahlenst/testing-container-images/tree/main/testcontainers&#34;&gt;Complete source code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;testinfra&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#testinfra&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Testinfra&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://testinfra.readthedocs.io/&#34;&gt;Testinfra&lt;/a&gt; is a plug-in for &lt;a href=&#34;https://docs.pytest.org/&#34;&gt;pytest&lt;/a&gt;, a testing framework for Python. As a result, you write your tests in Python.&lt;/p&gt;
&lt;p&gt;Testinfra includes many built-in inspections, called &lt;a href=&#34;https://testinfra.readthedocs.io/en/latest/modules.html&#34;&gt;modules&lt;/a&gt;, that simplify writing tests and provide some portability. As a result, checking whether nginx is installed is as simple as&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-Python&#34; data-lang=&#34;Python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;test_nginx_is_installed&lt;/span&gt;(host):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    nginx &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; host&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;package(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;nginx&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;assert&lt;/span&gt; nginx&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;is_installed
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;regardless of the Linux distribution you are using. If any of the built-in inspections is not sufficient, you can fall back to the command line. But I did not have to do this a single time. Consequently, testing whether all nginx workers run as an unprivileged user is surprisingly readable:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-Python&#34; data-lang=&#34;Python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;test_nginx_workers_do_not_run_as_root&lt;/span&gt;(host):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    master &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; host&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;process&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get(user&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;root&amp;#34;&lt;/span&gt;, comm&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;nginx&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    workers &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; host&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;process&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;filter(ppid&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;master&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pid)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    users &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; set([worker&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ruser &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; worker &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; workers])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;assert&lt;/span&gt; set([&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;www-data&amp;#39;&lt;/span&gt;]) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; users
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Another advantage of Testinfra is that it supports more container runtimes than any other tool listed here.&lt;/p&gt;
&lt;p&gt;Thanks to Testinfra being a pytest plug-in, you get all the features you can expect from a mature testing framework.&lt;/p&gt;
&lt;h3 id=&#34;testinfra-in-a-nutshell&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#testinfra-in-a-nutshell&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Testinfra in a Nutshell&lt;/span&gt;
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;All tests could be implemented: ✅&lt;/li&gt;
&lt;li&gt;Generates test reports: ✅&lt;/li&gt;
&lt;li&gt;Supported container runtimes: Docker, Kubernetes, LXC, OpenShift, Podman&lt;/li&gt;
&lt;li&gt;Pros: Large number of built-in inspections, based on mature test framework&lt;/li&gt;
&lt;li&gt;Cons: none&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/aahlenst/testing-container-images/tree/main/testinfra&#34;&gt;Complete source code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;For some extra fun.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;While, for example, &lt;code&gt;ps&lt;/code&gt; works the same across Linux distributions, each family of distributions uses different package managers. Therefore, there is no single command to check whether a package is installed that works across all Linux distributions.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
		</item>
		
		<item>
			<title>Comparing Parallel Test Execution in JUnit 5, Gradle, and Maven</title>
			<link>https://aahlenst.dev/blog/comparing-parallel-test-execution-in-junit5-gradle-maven/</link>
			<pubDate>Thu, 01 Dec 2022 12:00:00 +0100</pubDate>
			
			<guid>https://aahlenst.dev/blog/comparing-parallel-test-execution-in-junit5-gradle-maven/</guid>
			<description>&lt;p&gt;JUnit 5, Gradle, and Maven: All offer parallel execution of unit tests. With so many options, I wondered whether it makes a difference what I pick. It does.&lt;/p&gt;
&lt;h2 id=&#34;capabilities-of-the-contenders&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#capabilities-of-the-contenders&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Capabilities of the Contenders&lt;/span&gt;
&lt;/h2&gt;
&lt;h3 id=&#34;junit-5&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#junit-5&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;JUnit 5&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://junit.org/junit5/docs/5.9.0/user-guide/index.html#writing-tests-parallel-execution&#34;&gt;Parallel test execution is an experimental feature of JUnit 5&lt;/a&gt; that has been available since version 5.3. Once enabled, you can define whether you want to run top-level test classes or methods sequentially, concurrently or any combination thereof. JUnit 5 runs all tests in a single JVM using a &lt;a href=&#34;https://docs.oracle.com/en/java/javase/19/docs/api/java.base/java/util/concurrent/ForkJoinPool.html&#34;&gt;ForkJoinPool&lt;/a&gt;. There are various knobs to control how many tests should be run in parallel.&lt;/p&gt;
&lt;h3 id=&#34;gradle&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#gradle&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Gradle&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://docs.gradle.org/7.6/userguide/performance.html#execute_tests_in_parallel&#34;&gt;Gradle can execute tests in parallel, too&lt;/a&gt;. It works with JUnit 5, JUnit 4, &lt;a href=&#34;https://github.com/bmuschko/parallel-testng/&#34;&gt;TestNG&lt;/a&gt; and probably other frameworks. &lt;code&gt;maxParallelForks&lt;/code&gt; is used to enable (or disable) parallel test execution and control how many tests should be run in parallel. Any value bigger than 1 enables test parallelism. Compared to the parallel test execution built into JUnit 5, Gradle can only run test classes in parallel, not individual test methods. Each test class is assigned to a single worker process (a forked JVM). In other words: Gradle runs all test methods of a single test class sequentially in the same JVM that is not shared with any other test.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-groovy&#34; data-lang=&#34;groovy&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;tasks&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;withType&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;Test&lt;span style=&#34;color:#f92672&#34;&gt;).&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;configureEach&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Creates half as many forks as there are CPU cores.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    maxParallelForks &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Runtime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;runtime&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;availableProcessors&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;().&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;intdiv&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;?:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;maven&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#maven&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Maven&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://maven.apache.org/surefire/maven-surefire-plugin/examples/fork-options-and-parallel-execution.html#parallel-test-execution&#34;&gt;Maven&amp;rsquo;s Surefire plug-in can execute tests in parallel as well&lt;/a&gt;. Its capabilities differ by testing framework: &lt;a href=&#34;https://maven.apache.org/surefire/maven-surefire-plugin/examples/junit.html#running-tests-in-parallel&#34;&gt;JUnit 4&lt;/a&gt;, &lt;a href=&#34;https://maven.apache.org/surefire/maven-surefire-plugin/examples/testng.html&#34;&gt;TestNG&lt;/a&gt;. However, &lt;a href=&#34;https://maven.apache.org/surefire/maven-surefire-plugin/examples/junit-platform.html#running-tests-in-parallel&#34;&gt;there is no dedicated support for parallel testing within the same JVM in Maven for JUnit 5&lt;/a&gt;. You can use the parallel test execution built into JUnit 5 instead.&lt;/p&gt;
&lt;p&gt;It is important to note that all of Maven&amp;rsquo;s options mentioned so far achieve concurrency within the same JVM as JUnit 5. If you want that Maven runs each test in a separate process, you need the option &lt;a href=&#34;https://maven.apache.org/surefire/maven-surefire-plugin/examples/fork-options-and-parallel-execution.html#forked-test-execution&#34;&gt;&lt;code&gt;forkCount&lt;/code&gt;&lt;/a&gt; with a value higher than 1. It works with all test frameworks supported by Surefire, including JUnit 5.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;project&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;xmlns=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;http://maven.apache.org/POM/4.0.0&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;xmlns:xsi=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;http://www.w3.org/2001/XMLSchema-instance&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         &lt;span style=&#34;color:#a6e22e&#34;&gt;xsi:schemaLocation=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;modelVersion&amp;gt;&lt;/span&gt;4.0.0&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/modelVersion&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;!-- other options --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;build&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;plugins&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;maven-surefire-plugin&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;3.0.0-M7&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;!-- Creates half as many forks as there are CPU cores. --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;forkCount&amp;gt;&lt;/span&gt;0.5C&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/forkCount&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;reuseForks&amp;gt;&lt;/span&gt;true&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/reuseForks&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;!-- other plug-ins --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/plugins&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/build&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/project&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;concurrency-within-the-same-jvm-is-dangerous&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#concurrency-within-the-same-jvm-is-dangerous&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Concurrency Within the Same JVM Is Dangerous&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;In summary, there is one big difference between the parallel test execution of JUnit 5, Gradle, and Maven: Whether the concurrently running tests share a single JVM or not. That has a massive impact on your tests.&lt;/p&gt;
&lt;p&gt;In short, once your tests share a single JVM, your tests become multi-threaded code which can cause all kinds of issues. For example, changing the default locale in one method impacts all other running tests. Or a single test does no longer have its Testcontainers &lt;code&gt;Container&lt;/code&gt; for itself &lt;a href=&#34;https://www.testcontainers.org/test_framework_integration/junit_5/&#34;&gt;when using the recommended patterns&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;JUnit 5 supports &lt;a href=&#34;https://junit.org/junit5/docs/5.9.0/user-guide/index.html#writing-tests-parallel-execution-synchronization&#34;&gt;several synchronisation mechanisms&lt;/a&gt; to prepare your tests for parallel execution within the same JVM. But for me, that requires too much thought and work for very little gain&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; if you can use multiple workers in Gradle and Maven without requiring any&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; code changes.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Spawning multiple threads is more efficient than creating multiple processes.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;As long as your tests do not share external resources, like a single database.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
		</item>
		
		<item>
			<title>Fast, Reliable Integration Tests with Spring Boot and Flyway</title>
			<link>https://aahlenst.dev/blog/fast-reliable-integration-tests-with-spring/</link>
			<pubDate>Fri, 25 Nov 2022 12:00:00 +0100</pubDate>
			
			<guid>https://aahlenst.dev/blog/fast-reliable-integration-tests-with-spring/</guid>
			<description>&lt;p&gt;For integration tests to be effective, they must be reliable and fast. If tests take too long to run, nobody will run them. If tests are unreliable, everybody will ignore them.&lt;/p&gt;
&lt;p&gt;To be reliable, tests may not result in false positives or negatives. Furthermore, they should not be flaky. That can be achieved by isolating the tests from each other, for example, by supplying a dedicated test database to each test with the help of &lt;a href=&#34;https://www.testcontainers.org/&#34;&gt;Testcontainers&lt;/a&gt;. Additionally, tests should start from a known, consistent good state. That can be achieved by resetting the database to a known, good state before every test. Unfortunately, all that additional work makes tests slower.&lt;/p&gt;
&lt;p&gt;In this post, we will look at various ways to write reliable integration tests in Spring Boot with the help of Flyway and Testcontainers. And then, we will look at which one is the fastest.&lt;/p&gt;
&lt;p&gt;A working implementation of all approaches with the full source code can be found in &lt;a href=&#34;https://github.com/aahlenst/fast-tests-spring-boot-flyway&#34;&gt;my sample project on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;setting-the-stage&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#setting-the-stage&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Setting the Stage&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;If we follow the &lt;a href=&#34;https://docs.spring.io/spring-boot/docs/3.0.0/reference/html/features.html#features.testing.spring-boot-applications.autoconfigured-jdbc&#34;&gt;Spring Boot documentation about writing tests&lt;/a&gt; &lt;a href=&#34;https://docs.spring.io/spring-boot/docs/3.0.0/reference/html/howto.html#howto.testing.testcontainers&#34;&gt;with Testcontainers&lt;/a&gt;, we probably end up with something like the following test class to test an &lt;code&gt;AuthorRepository&lt;/code&gt; that gives us access to book authors stored in the database:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@JdbcTest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@ContextConfiguration&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	initializers &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; AuthorRepositoryTest.&lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	classes &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; AuthorRepository.&lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@AutoConfigureTestDatabase&lt;/span&gt;(replace &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; AutoConfigureTestDatabase.&lt;span style=&#34;color:#a6e22e&#34;&gt;Replace&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;NONE&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Commit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Testcontainers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;AuthorRepositoryTest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;implements&lt;/span&gt; ApplicationContextInitializer&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;ConfigurableApplicationContext&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;@Containers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;final&lt;/span&gt; PostgreSQLContainer postgres &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; PostgreSQLContainer(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;postgres:15.1&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;@Autowired&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	AuthorRepository authorRepository;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;initialize&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;@NotNull&lt;/span&gt; ConfigurableApplicationContext applicationContext) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		TestPropertyValues
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			.&lt;span style=&#34;color:#a6e22e&#34;&gt;of&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;spring.datasource.url=&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; postgres.&lt;span style=&#34;color:#a6e22e&#34;&gt;getJdbcUrl&lt;/span&gt;(),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;spring.datasource.password=&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; postgres.&lt;span style=&#34;color:#a6e22e&#34;&gt;getPassword&lt;/span&gt;(),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;spring.datasource.username=&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; postgres.&lt;span style=&#34;color:#a6e22e&#34;&gt;getUsername&lt;/span&gt;(),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;spring.flyway.clean-disabled=false&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			.&lt;span style=&#34;color:#a6e22e&#34;&gt;applyTo&lt;/span&gt;(applicationContext);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;@Test&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;add_insertsAuthor&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; author &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Author(AuthorId.&lt;span style=&#34;color:#a6e22e&#34;&gt;from&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;82f4870d-f2e5-4a9f-a2b2-b297f66733a0&amp;#34;&lt;/span&gt;), &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Brian Goetz&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;authorRepository&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;add&lt;/span&gt;(author);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		assertThat(&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;authorRepository&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;findAll&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			.&lt;span style=&#34;color:#a6e22e&#34;&gt;extracting&lt;/span&gt;(Author::name)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			.&lt;span style=&#34;color:#a6e22e&#34;&gt;containsExactly&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Bert Bates&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Brian Goetz&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Joshua Bloch&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Kathy Sierra&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Trisha Gee&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Spring Boot automatically initialises the database before the first test method is run but does nothing after that. Every other test after &lt;code&gt;add_insertsAuthor()&lt;/code&gt; has to somehow deal with another author’s presence (or absence, in the case of a test failure) in the database. That makes tests brittle.&lt;/p&gt;
&lt;p&gt;One alternative could be that tests clean up after themselves. But that needs work and is problematic if we want to diagnose a test failure. Would it not be much better if the database was automatically brought into a consistent state before every test?&lt;/p&gt;
&lt;p&gt;Spring Boot offers at least three different ways to do this that actually work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using a custom &lt;code&gt;FlywayMigrationStrategy&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Invoking Flyway from a &lt;code&gt;TestExecutionListener&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Eschewing Flyway and using &lt;code&gt;@Sql&lt;/code&gt; with a SQL script instead.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are ways that do not work properly, too. For example, using a JUnit setup method (a method annotated with &lt;code&gt;@BeforeEach&lt;/code&gt; in JUnit 5) to invoke Flyway makes &lt;code&gt;@Sql&lt;/code&gt; unusable. The reason is that the &lt;code&gt;TestExecutionListener&lt;/code&gt; that processes &lt;code&gt;@Sql&lt;/code&gt; annotations runs before &lt;code&gt;@BeforeEach&lt;/code&gt;. Thus, the database would still be empty when &lt;code&gt;@Sql&lt;/code&gt; is applied.&lt;/p&gt;
&lt;h2 id=&#34;the-options-explained&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#the-options-explained&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;The Options, Explained&lt;/span&gt;
&lt;/h2&gt;
&lt;h3 id=&#34;flywaymigrationstrategy&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#flywaymigrationstrategy&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;FlywayMigrationStrategy&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;When Spring Boot starts, its autoconfiguration for Flyway invokes the registered &lt;a href=&#34;https://docs.spring.io/spring-boot/docs/3.0.0/api/org/springframework/boot/autoconfigure/flyway/FlywayMigrationStrategy.html&#34;&gt;&lt;code&gt;FlywayMigrationStrategy&lt;/code&gt;&lt;/a&gt; to apply all pending migrations. By supplying your own &lt;code&gt;FlywayMigrationStrategy&lt;/code&gt;, you can alter that behaviour and invoke &lt;code&gt;clean()&lt;/code&gt; before &lt;code&gt;migrate()&lt;/code&gt; and thereby reset the test database:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Bean&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; FlywayMigrationStrategy &lt;span style=&#34;color:#a6e22e&#34;&gt;flywayMigrationStrategy&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; flyway &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		flyway.&lt;span style=&#34;color:#a6e22e&#34;&gt;clean&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		flyway.&lt;span style=&#34;color:#a6e22e&#34;&gt;migrate&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As mentioned before, the &lt;code&gt;FlywayMigrationStrategy&lt;/code&gt; is only applied when the application starts. To make Spring apply it again before each test, you have to trigger a context refresh by annotating the test class with &lt;code&gt;@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This approach requires the least amount of code but is also the slowest. &lt;a href=&#34;https://docs.spring.io/spring-framework/docs/6.0.2/reference/html/testing.html#spring-testing-annotation-dirtiescontext&#34;&gt;&lt;code&gt;@DirtiesContext&lt;/code&gt;&lt;/a&gt; causes Spring to rebuild its context. That is an expensive operation, especially if you declare many beans or use multiple starters.&lt;/p&gt;
&lt;p&gt;For a complete and commented implementation of this approach, &lt;a href=&#34;https://github.com/aahlenst/fast-tests-spring-boot-flyway/blob/main/src/test/java/com/example/testing/FlywayMigrationStrategyAuthorRepositoryTest.java&#34;&gt;see the relevant test in the sample project&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;flyway-with-testexecutionlistener&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#flyway-with-testexecutionlistener&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Flyway with TestExecutionListener&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://docs.spring.io/spring-framework/docs/6.0.2/javadoc-api/org/springframework/test/context/TestExecutionListener.html&#34;&gt;&lt;code&gt;TestExecutionListener&lt;/code&gt;&lt;/a&gt; is an API provided by Spring to react to test-execution events. For example, it allows running custom code before (or after) the execution of each test method. Therefore, it can serve as a replacement for &lt;code&gt;FlywayMigrationStrategy&lt;/code&gt; without the need for a context refresh.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CleanDatabaseTestExecutionListener&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;implements&lt;/span&gt; TestExecutionListener, Ordered
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;beforeTestMethod&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;@NotNull&lt;/span&gt; TestContext testContext) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; flyway &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; testContext.&lt;span style=&#34;color:#a6e22e&#34;&gt;getApplicationContext&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;getBean&lt;/span&gt;(Flyway.&lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		flyway.&lt;span style=&#34;color:#a6e22e&#34;&gt;clean&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		flyway.&lt;span style=&#34;color:#a6e22e&#34;&gt;migrate&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getOrder&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#75715e&#34;&gt;// Ensures that this TestExecutionListener is run before&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#75715e&#34;&gt;// SqlScriptExecutionTestListener which handles @Sql.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Ordered.&lt;span style=&#34;color:#a6e22e&#34;&gt;HIGHEST_PRECEDENCE&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Register the &lt;code&gt;TestExecutionListener&lt;/code&gt; by annotating your test classes with &lt;a href=&#34;https://docs.spring.io/spring-framework/docs/6.0.2/reference/html/testing.html#testcontext-tel-config-registering-tels&#34;&gt;&lt;code&gt;@TestExecutionListeners&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@TestExecutionListeners&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; CleanDatabaseTestExecutionListener.&lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	mergeMode &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; MERGE_WITH_DEFAULTS
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The last step is to register a &lt;code&gt;FlywayMigrationStrategy&lt;/code&gt; that does nothing. Otherwise, the autoconfiguration would apply the migrations and the &lt;code&gt;TestExecutionListener&lt;/code&gt; would apply them a second time before the first test runs.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Bean&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; FlywayMigrationStrategy &lt;span style=&#34;color:#a6e22e&#34;&gt;flywayMigrationStrategy&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; flyway &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; { &lt;span style=&#34;color:#75715e&#34;&gt;/* Do nothing. */&lt;/span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;While this approach requires significantly more code than &lt;code&gt;FlywayMigrationStrategy&lt;/code&gt;, it is considerably faster, too, because we could get rid of the expensive context refresh after each test.&lt;/p&gt;
&lt;p&gt;For a complete and commented implementation of this approach, &lt;a href=&#34;https://github.com/aahlenst/fast-tests-spring-boot-flyway/blob/main/src/test/java/com/example/testing/FlywayMigrationAuthorRepositoryTest.java&#34;&gt;see the relevant test in the sample project&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;sql-almost-without-flyway&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#sql-almost-without-flyway&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;@Sql (Almost) Without Flyway&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;The previous approaches suffer from a common problem: They run Flyway before every test and have to apply each and every migration over and over again to bring the test database into a consistent state. That is fine if a project only has a few migrations. But as soon as there are more than a couple of migrations, their execution time starts adding up. I pulled up an old project with around 50 migrations and measured how long they take to run: between 1 and 2 seconds in total. There are about 600 test methods across all integration tests. Therefore, a single build spends between 10 and 20 CPU minutes applying migrations alone – what a waste.&lt;/p&gt;
&lt;p&gt;The solution is to remove Flyway from the integration tests:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Apply all migrations on an empty database, for example, with the help of Flyway&amp;rsquo;s plug-ins for &lt;a href=&#34;https://flywaydb.org/documentation/usage/gradle/&#34;&gt;Gradle&lt;/a&gt; and &lt;a href=&#34;https://flywaydb.org/documentation/usage/maven/&#34;&gt;Maven&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Dump the database to a SQL script with &lt;a href=&#34;https://www.postgresql.org/docs/current/app-pgdump.html&#34;&gt;pg_dump&lt;/a&gt;, &lt;a href=&#34;https://dev.mysql.com/doc/refman/8.0/en/mysqldump.html&#34;&gt;mysqldump&lt;/a&gt;, and so on. Specify the necessary options so that the generated script includes the commands to drop all database objects if they already exist. That ensures that the database does not contain any leftovers from previous test runs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use the SQL script in the integration tests to reinitialise the database before every test instead of Flyway:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@JdbcTest&lt;/span&gt;(properties &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;spring.flyway.enabled=false&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Sql&lt;/span&gt;(scripts &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/db.sql&amp;#34;&lt;/span&gt;, config &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;@SqlConfig&lt;/span&gt;(transactionMode &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ISOLATED))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Other annotations&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;AuthorRepositoryTest&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// Tests&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This approach significantly cuts down on the total runtime because it creates all database objects once per test without replaying the entire migration history.&lt;/p&gt;
&lt;p&gt;Unfortunately, this speed-up comes at a cost: The SQL script needs to be checked into source control. And someone needs to keep the database dump in sync with the Flyway migrations. However, it is possible to avoid the maintenance burden by automating the generation of the database dump. Please see &lt;a href=&#34;https://aahlenst.dev/blog/keeping-the-growing-number-of-flyway-migrations-in-check/&#34;&gt;Keeping the Growing Number of Flyway Migrations in Check&lt;/a&gt; for how to do this.&lt;/p&gt;
&lt;p&gt;For a complete and commented implementation of this approach, &lt;a href=&#34;https://github.com/aahlenst/fast-tests-spring-boot-flyway/blob/main/src/test/java/com/example/testing/DatabaseDumpAuthorRepositoryTest.java&#34;&gt;see the relevant test in the sample project&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;performance&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#performance&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Performance&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;I did some measurements to give you a very rough idea of how the various approaches perform relative to each other:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Method&lt;/th&gt;
          &lt;th style=&#34;text-align: right&#34;&gt;Runtime&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;FlywayMigrationStrategy&lt;/code&gt;&lt;/td&gt;
          &lt;td style=&#34;text-align: right&#34;&gt;100%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Flyway with &lt;code&gt;TestExecutionListener&lt;/code&gt;&lt;/td&gt;
          &lt;td style=&#34;text-align: right&#34;&gt;~70%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;@Sql&lt;/code&gt; without Flyway&lt;/td&gt;
          &lt;td style=&#34;text-align: right&#34;&gt;~50%&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The numbers are indicative at best because they highly depend on the specifics of your project and the hardware. For example, if your application defines a lot of beans or uses many starters, the &lt;code&gt;FlywayMigrationStrategy&lt;/code&gt; will perform worse because the context refresh takes even longer. Therefore, the gap between it and the other two methods widens. If you have many Flyway migrations, &lt;code&gt;@Sql&lt;/code&gt; without Flyway will be even faster.&lt;/p&gt;
&lt;h2 id=&#34;alternatives&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#alternatives&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Alternatives&lt;/span&gt;
&lt;/h2&gt;
&lt;h3 id=&#34;rollback&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#rollback&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Rollback&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;One popular method to keep the test database in a consistent state is to automatically rollback the transaction after each test. &lt;a href=&#34;https://docs.spring.io/spring-framework/docs/6.0.2/reference/html/testing.html#testcontext-tx-rollback-and-commit-behavior&#34;&gt;It is even the default behaviour of the testing support in Spring Framework&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I believe it is not wise to rely on rollback because it might hide errors. Database systems do not necessarily check every constraint after every statement but might defer it until later in the transaction. One example is &lt;a href=&#34;https://www.postgresql.org/docs/15/ddl-constraints.html#DDL-CONSTRAINTS-FK&#34;&gt;PostgreSQL&amp;rsquo;s behaviour with ON DELETE NO ACTION&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Another drawback of testing with rollback is that it might hinder or even make it impossible to test complex scenarios. For example, in batch processing it is common that the transaction is committed after each batch has been processed. How are you supposed to test that only the last (failing) batch is discarded with rollback testing?&lt;/p&gt;
&lt;h3 id=&#34;baseline-migrations&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#baseline-migrations&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Baseline Migrations&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://flywaydb.org/try-flyway-teams-edition&#34;&gt;Flyway Teams Edition&lt;/a&gt;, a paid version of Flyway, offers &lt;a href=&#34;https://flywaydb.org/documentation/concepts/baselinemigrations&#34;&gt;Baseline Migrations&lt;/a&gt; &lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. In a nutshell, you can define a separate SQL script that brings a database to the state of a specific version (the baseline version), skipping all migrations up to and including the baseline version.&lt;/p&gt;
&lt;p&gt;If you keep only a handful of migrations around, using Flyway with baseline migrations should perform similarly to &lt;code&gt;@Sql&lt;/code&gt; without Flyway. However, someone has to manually create the baseline migration. And you need Flyway Teams Edition.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Not to be confused with Flyway&amp;rsquo;s &lt;a href=&#34;https://flywaydb.org/documentation/command/baseline&#34;&gt;baseline command&lt;/a&gt; that allows you to introduce Flyway to existing databases and to delete old migrations without Flyway complaining about them being absent.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
		</item>
		
		<item>
			<title>Keeping the Growing Number of Flyway Migrations in Check</title>
			<link>https://aahlenst.dev/blog/keeping-the-growing-number-of-flyway-migrations-in-check/</link>
			<pubDate>Thu, 24 Nov 2022 12:00:00 +0100</pubDate>
			
			<guid>https://aahlenst.dev/blog/keeping-the-growing-number-of-flyway-migrations-in-check/</guid>
			<description>&lt;p&gt;&lt;a href=&#34;https://flywaydb.org&#34;&gt;Flyway&lt;/a&gt; is a fantastic tool for managing an application&amp;rsquo;s database alongside the application&amp;rsquo;s source code. But even in small projects, the number of migrations grows inevitably. That is no problem for application startup. But if you use Flyway to initialise the database before every integration test to ensure your tests are independent and reliable, the tests take longer with every migration you add. For example, if you have 50 migrations that take 1 second to run and there are 600 test methods, you would spend 10 CPU minutes alone on creating test databases. There must be a better way.&lt;/p&gt;
&lt;p&gt;Fortunately, there is. Unfortunately, it requires a fair number of custom Gradle tasks.&lt;/p&gt;
&lt;p&gt;The idea is to automatically do the following before running the tests:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Provision a database container.&lt;/li&gt;
&lt;li&gt;Apply all migrations to the database with Flyway.&lt;/li&gt;
&lt;li&gt;Create a database dump and place it on the classpath for future consumption.&lt;/li&gt;
&lt;li&gt;Remove the container.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Before a test is about to run, the test database can be initialised with the previously created database dump without any involvement of Flyway. That is much faster than applying each migration individually. Furthermore, the time needed to create the test database increases with the number of database objects and no longer with the number of migrations.&lt;/p&gt;
&lt;p&gt;The implementation is relatively straightforward thanks to the &lt;a href=&#34;https://bmuschko.github.io/gradle-docker-plugin/&#34;&gt;Gradle Docker plugin&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-groovy&#34; data-lang=&#34;groovy&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; dumpPath &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; project&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;buildDir&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;toPath&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;().&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;resolve&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;resources/test/db.sql&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;task &lt;span style=&#34;color:#a6e22e&#34;&gt;pullDatabaseImage&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;type: DockerPullImage&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; stateFile &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; project&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;buildDir&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;toPath&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;().&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;resolve&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;tmp/squash.txt&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	inputs&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;files&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;fileTree&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;src/main/resources/db/migration&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;withPropertyName&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;migrations&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;withPathSensitivity&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;PathSensitivity&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;RELATIVE&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	outputs&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;file&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;stateFile&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;withPropertyName&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;stateFile&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	image &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;docker.io/library/postgres:15.1&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	doLast &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		Files&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;writeString&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;stateFile&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; getImage&lt;span style=&#34;color:#f92672&#34;&gt;().&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;(),&lt;/span&gt; StandardOpenOption&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;CREATE&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			StandardOpenOption&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;TRUNCATE_EXISTING&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;task &lt;span style=&#34;color:#a6e22e&#34;&gt;createDatabaseContainer&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;type: DockerCreateContainer&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	dependsOn pullDatabaseImage
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	onlyIf &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; pullDatabaseImage&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;didWork&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	targetImageId pullDatabaseImage&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;getImage&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	hostConfig&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;portBindings&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;5432:5432&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	hostConfig&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;autoRemove&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	withEnvVar&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;POSTGRES_USER&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;flyway&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	withEnvVar&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;POSTGRES_PASSWORD&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;flyway&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	withEnvVar&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;POSTGRES_DB&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;squash&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;task &lt;span style=&#34;color:#a6e22e&#34;&gt;startDatabaseContainer&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;type: DockerStartContainer&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	dependsOn createDatabaseContainer
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	onlyIf &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; createDatabaseContainer&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;didWork&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	targetContainerId createDatabaseContainer&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;getContainerId&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;task &lt;span style=&#34;color:#a6e22e&#34;&gt;stopDatabaseContainer&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;type: DockerStopContainer&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	dependsOn startDatabaseContainer
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	onlyIf &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; startDatabaseContainer&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;didWork&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	targetContainerId startDatabaseContainer&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;getContainerId&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;task &lt;span style=&#34;color:#a6e22e&#34;&gt;runFlywayMigrations&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;type: FlywayMigrateTask&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	dependsOn startDatabaseContainer
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	onlyIf &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; startDatabaseContainer&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;didWork&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	driver &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;org.postgresql.Driver&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	url &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;jdbc:postgresql://localhost:5432/squash&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	user &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;flyway&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	password &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;flyway&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	connectRetries &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;task &lt;span style=&#34;color:#a6e22e&#34;&gt;dumpDatabase&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;type: DockerExecContainer&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	dependsOn runFlywayMigrations
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	onlyIf &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; runFlywayMigrations&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;didWork&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	targetContainerId startDatabaseContainer&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;getContainerId&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	withCommand&lt;span style=&#34;color:#f92672&#34;&gt;([&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;pg_dump&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;--clean&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;--if-exists&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;--inserts&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                 &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;--disable-dollar-quoting&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;--no-owner&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;-d&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;squash&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                 &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;-U&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;flyway&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;--file&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;/tmp/db.sql&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;task &lt;span style=&#34;color:#a6e22e&#34;&gt;copyDatabaseDumpToHost&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;type: DockerCopyFileFromContainer&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	dependsOn dumpDatabase
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	finalizedBy stopDatabaseContainer
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	onlyIf &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; dumpDatabase&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;didWork&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	outputs&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;file&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;dumpPath&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	targetContainerId startDatabaseContainer&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;getContainerId&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	remotePath &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;/tmp/db.sql&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	hostPath &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; dumpPath&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;toString&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;tasks&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;withType&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;ProcessResources&lt;span style=&#34;color:#f92672&#34;&gt;).&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;configureEach&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	dependsOn copyDatabaseDumpToHost
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Two things to highlight:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The database dump is written to &lt;code&gt;build/resources/test/db.sql&lt;/code&gt;. As such, it ends up on the test classpath of the project. Tests can pick it up from there by referencing &lt;code&gt;classpath:/db.sql&lt;/code&gt; (Spring notation).&lt;/li&gt;
&lt;li&gt;The tasks participate in incremental builds. As a result, they only run if any of the migrations change or the database dump is missing. Consequently, the tasks do not impact build times during development once the database dump has been created. And the database dump is never outdated.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The example uses &lt;a href=&#34;https://postgresql.org/&#34;&gt;PostgreSQL&lt;/a&gt;. It should be possible to adapt it to any other database system (like MariaDB or even Oracle) as long as you can get ahold of a container image with that database system.&lt;/p&gt;
&lt;p&gt;A working implementation with the full source code can be found in &lt;a href=&#34;https://github.com/aahlenst/fast-tests-spring-boot-flyway&#34;&gt;my sample project on GitHub&lt;/a&gt;. The most relevant parts are &lt;a href=&#34;https://github.com/aahlenst/fast-tests-spring-boot-flyway/blob/main/build.gradle#L54-L158&#34;&gt;the build file&lt;/a&gt; and &lt;a href=&#34;https://github.com/aahlenst/fast-tests-spring-boot-flyway/blob/main/src/test/java/com/example/testing/DatabaseDumpAuthorRepositoryTest.java&#34;&gt;the test that uses the created database dump&lt;/a&gt;.&lt;/p&gt;
</description>
		</item>
		
		<item>
			<title>Managing Xcode on the Command Line</title>
			<link>https://aahlenst.dev/blog/managing-xcode-on-the-command-line/</link>
			<pubDate>Thu, 23 Dec 2021 12:00:00 +0100</pubDate>
			
			<guid>https://aahlenst.dev/blog/managing-xcode-on-the-command-line/</guid>
			<description>&lt;p&gt;&lt;a href=&#34;https://github.com/xcpretty/xcode-install&#34;&gt;xcode-install&lt;/a&gt; is a nifty tool to install and manage Xcode over the command line. Furthermore, it supports installing multiple Xcode versions side-by-side. It can even install all kinds of simulators and works great with automation tools like Ansible.&lt;/p&gt;
&lt;div class=&#34;not-prose bg-green-50 border-l-4 border-green-400 p-4&#34;&gt;
	&lt;div class=&#34;flex&#34;&gt;
		&lt;div class=&#34;shrink-0&#34;&gt;
			&lt;svg class=&#34;h-8 w-8 text-green-400&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34;
				 aria-hidden=&#34;true&#34;&gt;
				&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34;
					  d=&#34;M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z&#34;&gt;&lt;/path&gt;
			&lt;/svg&gt;
		&lt;/div&gt;
		&lt;div class=&#34;admonition ml-3 text-green-700&#34;&gt;
			Are you more of a GUI person? You might find &lt;a href=&#34;https://github.com/RobotsAndPencils/XcodesApp&#34;&gt;XcodesApp&lt;/a&gt; helpful.
		&lt;/div&gt;
	&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;xcode-install uses &lt;a href=&#34;https://fastlane.tools/&#34;&gt;fastlane&lt;/a&gt; under the covers. It downloads everything from the &lt;a href=&#34;https://developer.apple.com/&#34;&gt;Apple Developer Portal&lt;/a&gt;. Consequently, you need a free ADP login to use xcode-install.&lt;/p&gt;
&lt;h2 id=&#34;authenticating-to-the-apple-developer-portal&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#authenticating-to-the-apple-developer-portal&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;Authenticating to the Apple Developer Portal&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Unfortunately, automatically logging into the Apple Developer Portal is a bit of a pain since Apple introduced 2FA. If you still have an old account without 2FA enabled, you can get away with a few environment variables:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ec2-user@Mac-mini ~ % export XCODE_INSTALL_USER=yourappleid
ec2-user@Mac-mini ~ % export XCODE_INSTALL_PASSWORD=yourpassword
ec2-user@Mac-mini ~ % export SPACESHIP_SKIP_2FA_UPGRADE=1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you do not want that fastlane stores your credentials, set &lt;code&gt;FASTLANE_DONT_STORE_PASSWORD=1&lt;/code&gt;, too.&lt;/p&gt;
&lt;p&gt;If your account has 2FA enabled, you have to create a login cookie first:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ec2-user@Mac-mini ~ % fastlane spaceauth -u yourappleid
ec2-user@Mac-mini ~ % export XCODE_INSTALL_USER=yourappleid
ec2-user@Mac-mini ~ % export XCODE_INSTALL_PASSWORD=yourpassword
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The login cookie expires and needs to be renewed from time to time.&lt;/p&gt;
&lt;p&gt;If you cannot create the login cookie on the remote machine, you have still options. &lt;code&gt;fastlane spaceauth&lt;/code&gt; prints the login cookie, including the necessary &lt;code&gt;export&lt;/code&gt; command to the terminal. This allows you to &amp;ldquo;transfer&amp;rdquo; the cookie to the remote shell. If this is not possible, either, you can take advantage of fastlane storing the cookie in &lt;code&gt;~/.fastlane/spaceship&lt;/code&gt;. Copy &lt;code&gt;~/.fastlane/spaceship&lt;/code&gt; with all its contents to the remote machine, and you should have the login cookie available.&lt;/p&gt;
&lt;h2 id=&#34;a-short-tour-of-xcversion&#34; class=&#34;group flex whitespace-pre-wrap items-center -ml-4 pl-4&#34;&gt; &lt;a class=&#34;absolute -ml-8 flex items-center opacity-0 border-0 group-hover:opacity-100&#34; href=&#34;#a-short-tour-of-xcversion&#34; aria-label=&#34;anchor&#34;&gt;
        &lt;div class=&#34;w-6 h-6 text-red-400 ring-1 ring-red-900/5 rounded-md shadow-xs flex items-center justify-center hover:ring-red-900/10 hover:shadow-sm hover:text-red-700 dark:bg-red-700 dark:text-red-300 dark:shadow-none dark:ring-0&#34;&gt;
            &lt;svg class=&#34;w-4 h-4&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34; d=&#34;M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;
        &lt;/div&gt;   
    &lt;/a&gt;&lt;span&gt;A Short Tour of xcversion&lt;/span&gt;
&lt;/h2&gt;
&lt;div class=&#34;not-prose bg-red-50 border-l-4 border-red-400 p-4&#34;&gt;
	&lt;div class=&#34;flex&#34;&gt;
		&lt;div class=&#34;shrink-0&#34;&gt;
			&lt;svg class=&#34;h-8 w-8 text-red-400&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; viewBox=&#34;0 0 24 24&#34;
				 aria-hidden=&#34;true&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;
				&lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; stroke-width=&#34;2&#34;
					  d=&#34;M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z&#34;&gt;&lt;/path&gt;
			&lt;/svg&gt;
		&lt;/div&gt;
		&lt;div class=&#34;admonition ml-3 text-red-700&#34;&gt;
			Before installing Xcode, ensure that you have at least 30 GB of free disk space available.
		&lt;/div&gt;
	&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Installing Xcode is a matter of running&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ec2-user@Mac-mini ~ % xcversion install 13.2.1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;and some patience. Unpacking and installing Xcode takes roughly half an hour. To see all available Xcode versions, run&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ec2-user@Mac-mini ~ % xcversion list
4.3 for Lion
(...)
13.2
13.2.1 (installed)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Installing simulators is easy, too. &lt;code&gt;xcversion simulators&lt;/code&gt; gives you a list of all available simulators including their installation status. To install a specific simulator, for example, tvOS 15, run:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ec2-user@Mac-mini ~ % xcversion simulators --install=&amp;#34;tvOS 15.0&amp;#34;
&lt;/code&gt;&lt;/pre&gt;</description>
		</item>
		
	</channel>
</rss>
