<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title>Mike Street's Blog &amp; Notes</title>
		<link>https://www.mikestreety.co.uk</link>
		<description>Blog posts, notes and links from Mike Street (mikestreety.co.uk)</description>
		<language>en-gb</language>
		<pubDate>Sun, 31 May 2026 17:46:54 GMT</pubDate>
		<lastBuildDate>Sun, 31 May 2026 17:46:54 GMT</lastBuildDate>
		<atom:link href="https://www.mikestreety.co.uk/rss-all.xml" rel="alternate" type="application/xml" />
		<image>
			<url>https://www.mikestreety.co.uk/assets/img/favicon-512.png</url>
			<title>Mike Street's Blog &amp; Notes</title>
			<link>https://www.mikestreety.co.uk</link>
			<width>144</width>
			<height>144</height>
			<description>Lead Developer and CTO</description>
		</image>
		
		
		<item>
			<title>Set up a private packagist using a server and open source</title>
			<link>https://www.mikestreety.co.uk/blog/set-up-a-private-packagist-using-a-server-and-open-source/</link>
			<pubDate>Wed, 27 May 2026 00:00:00 GMT</pubDate>
			<guid>https://www.mikestreety.co.uk/blog/set-up-a-private-packagist-using-a-server-and-open-source/</guid>
			<description><![CDATA[
<p>Publishing private composer packages is a fiddly business - especially if you want a usable UI along with it.</p>
<p>After much research I came across <a href="https://github.com/vtsykun/packeton">Packeton</a> - an open source fork of Packagist which you can run on a web server of your choosing.</p>
<p>This walkthrough sets Packeton up with Docker which requires the least amount of server setup.</p>
<p>The following how-to runs through setting it up and some hurdles I came across. It expects CLI experience and you need to be comfortable with SSH.</p>
<h2>Where to run</h2>
<p>You need a server or VPS for this - I opted for a cloud server from <a href="https://www.hetzner.com/">Hetzner</a> with Ubuntu 24 running.</p>
<h2>Server set up</h2>
<p>Update the server applications and install caddy (which allows web traffic to docker images) and docker itself.</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">apt</span> update <span class="token operator">&amp;&amp;</span> <span class="token function">apt</span> upgrade <span class="token parameter variable">-y</span>
<span class="token function">apt</span> <span class="token function">install</span> <span class="token parameter variable">-y</span> caddy
<span class="token function">curl</span> <span class="token parameter variable">-fsSL</span> https://get.docker.com <span class="token operator">|</span> <span class="token function">sh</span></code></pre>
<h2>DNS</h2>
<p>Point your domain (e.g. <code>packages.yourdomain.com</code>) at the server's public IP</p>
<h2>Firewall</h2>
<p>Set up a firewall with the following inbound rules - I used the firewall built into the Hetzner control panel</p>
<table>
<thead>
<tr>
<th>Port</th>
<th>Protocol</th>
<th>Source</th>
</tr>
</thead>
<tbody>
<tr>
<td>22</td>
<td>TCP</td>
<td>Any IPv4, Any IPv6</td>
</tr>
<tr>
<td>80</td>
<td>TCP</td>
<td>Any IPv4, Any IPv6</td>
</tr>
<tr>
<td>443</td>
<td>TCP</td>
<td>Any IPv4, Any IPv6</td>
</tr>
</tbody>
</table>
<h2>Generate an app secret</h2>
<p>This can be run on the server or your local machine - you just need a 32 character string</p>
<pre class="language-bash"><code class="language-bash">openssl rand <span class="token parameter variable">-hex</span> <span class="token number">32</span></code></pre>
<p>Copy the output for use in the next step. Keep it static — it's used to encrypt SSH keys in the database.</p>
<h2>Create your Docker compose file</h2>
<p>I chose to keep all my Packeton-related files in <code>/opt/packeton</code>. Start off by making the folder &amp; file</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">mkdir</span> <span class="token parameter variable">-p</span> /opt/packeton
<span class="token function">nano</span> /opt/packeton/docker-compose.yml</code></pre>
<p>This utilises a few different settings &amp; configuration. Some points worth noting</p>
<ul>
<li>This include configuration for using Mailgun (we use it on the free tier) for sending the password reset emails</li>
<li>This includes <code>watchtower</code> which will keep packeton updated</li>
</ul>
<pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">services</span><span class="token punctuation">:</span>
  <span class="token key atrule">packeton</span><span class="token punctuation">:</span>
    <span class="token key atrule">image</span><span class="token punctuation">:</span> packeton/packeton<span class="token punctuation">:</span>latest
    <span class="token key atrule">container_name</span><span class="token punctuation">:</span> packeton
    <span class="token key atrule">restart</span><span class="token punctuation">:</span> unless<span class="token punctuation">-</span>stopped
    <span class="token key atrule">environment</span><span class="token punctuation">:</span>
      <span class="token key atrule">APP_SECRET</span><span class="token punctuation">:</span> &lt;output from step 5<span class="token punctuation">></span>
      <span class="token key atrule">ADMIN_USER</span><span class="token punctuation">:</span> admin
      <span class="token key atrule">ADMIN_PASSWORD</span><span class="token punctuation">:</span> changeme
      <span class="token key atrule">ADMIN_EMAIL</span><span class="token punctuation">:</span> you@yourdomain.com
      <span class="token key atrule">PACKAGIST_DIST_HOST</span><span class="token punctuation">:</span> https<span class="token punctuation">:</span>//packages.yourdomain.com
      <span class="token key atrule">TRUSTED_PROXIES</span><span class="token punctuation">:</span> 127.0.0.1
      <span class="token key atrule">MAILER_DSN</span><span class="token punctuation">:</span> smtp<span class="token punctuation">:</span>//you%40yourdomain.com<span class="token punctuation">:</span>PASSWORD@smtp.eu.mailgun.org<span class="token punctuation">:</span><span class="token number">587</span>
      <span class="token key atrule">MAILER_FROM</span><span class="token punctuation">:</span> Your Name &lt;you@yourdomain.com<span class="token punctuation">></span>
    <span class="token key atrule">ports</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> <span class="token string">'127.0.0.1:8080:80'</span>
    <span class="token key atrule">volumes</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> ./data<span class="token punctuation">:</span>/data
  <span class="token key atrule">watchtower</span><span class="token punctuation">:</span>
    <span class="token key atrule">image</span><span class="token punctuation">:</span> containrrr/watchtower
    <span class="token key atrule">restart</span><span class="token punctuation">:</span> unless<span class="token punctuation">-</span>stopped
    <span class="token key atrule">environment</span><span class="token punctuation">:</span>
      <span class="token key atrule">DOCKER_API_VERSION</span><span class="token punctuation">:</span> <span class="token string">"1.40"</span>
    <span class="token key atrule">volumes</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> /var/run/docker.sock<span class="token punctuation">:</span>/var/run/docker.sock
    <span class="token key atrule">command</span><span class="token punctuation">:</span> <span class="token punctuation">-</span><span class="token punctuation">-</span>interval 86400</code></pre>
<div class="info"><strong>Note:</strong> <code>ADMIN_USER</code> and <code>ADMIN_PASSWORD</code> only apply on first run. Change the password afterwards via the console (see Post-setup).</div>
<div class="info"><strong>Note:</strong> If using Mailgun, make sure you use the EU SMTP host (<code>smtp.eu.mailgun.org</code>) if your domain is on the EU region. Use the full email address as the SMTP username, URL-encoding the <code>@</code> as <code>%40</code>.</div>
<h2>Configure Caddy</h2>
<p>Caddy allows a domain name to be forwarded to a running docker container.</p>
<p>Replace the entire contents of <code>/etc/caddy/Caddyfile</code> with:</p>
<pre><code>packages.yourdomain.com {
    reverse_proxy localhost:8080
}
</code></pre>
<p>Then reload caddy:</p>
<pre class="language-bash"><code class="language-bash">systemctl reload caddy</code></pre>
<h2>Start Packeton</h2>
<pre class="language-bash"><code class="language-bash"><span class="token builtin class-name">cd</span> /opt/packeton
<span class="token function">docker</span> compose up <span class="token parameter variable">-d</span></code></pre>
<h2>Verify it all works</h2>
<p>Visit <code>https://packages.yourdomain.com</code> and log in with the admin credentials you set.</p>
<h2>Post-setup</h2>
<h3>Change the admin password</h3>
<p>The <code>ADMIN_PASSWORD</code> env var only applies on first run. Change it via the console:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> <span class="token builtin class-name">exec</span> <span class="token parameter variable">-it</span> packeton bin/console packagist:user:manager admin <span class="token parameter variable">--password</span><span class="token operator">=</span>newpassword</code></pre>
<h3>Create additional admin users</h3>
<pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> <span class="token builtin class-name">exec</span> <span class="token parameter variable">-it</span> packeton bin/console packagist:user:manager newusername <span class="token parameter variable">--password</span><span class="token operator">=</span>newpassword <span class="token parameter variable">--admin</span> --no-interaction</code></pre>
<h3>Configure GitLab OAuth</h3>
<p>First, create a GitLab OAuth application at <code>https://gitlab.com/-/profile/applications</code>:</p>
<ul>
<li><strong>Redirect URIs:</strong><pre><code>https://packages.yourdomain.com/oauth2/gitlab/install
https://packages.yourdomain.com/oauth2/gitlab/check
</code></pre>
</li>
<li><strong>Scopes:</strong> <code>api</code>, <code>read_user</code>, <code>read_repository</code></li>
</ul>
<p>Then create <code>/opt/packeton/data/config.yaml</code>:</p>
<pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">packeton</span><span class="token punctuation">:</span>
    <span class="token key atrule">integrations</span><span class="token punctuation">:</span>
        <span class="token key atrule">gitlab</span><span class="token punctuation">:</span>
            <span class="token key atrule">base_url</span><span class="token punctuation">:</span> <span class="token string">'https://gitlab.com/'</span>
            <span class="token key atrule">clone_preference</span><span class="token punctuation">:</span> <span class="token string">'clone_https'</span>
            <span class="token key atrule">gitlab</span><span class="token punctuation">:</span>
                <span class="token key atrule">client_id</span><span class="token punctuation">:</span> <span class="token string">'xxx'</span>
                <span class="token key atrule">client_secret</span><span class="token punctuation">:</span> <span class="token string">'xxx'</span></code></pre>
<p>Restart Packeton to apply:</p>
<pre class="language-bash"><code class="language-bash"><span class="token builtin class-name">cd</span> /opt/packeton
<span class="token function">docker</span> compose restart packeton</code></pre>
<p>Then go to the Packeton integrations page in the UI and click Install Integration, then Connect to complete the OAuth flow.</p>
<h2>Bonus Notes</h2>
<h3>Data</h3>
<p>All Packeton data lives in <code>/opt/packeton/data</code> on the host, mapped to <code>/data</code> inside the container. Back this directory up — it contains the database, config, and any stored artifacts.</p>
<h3>Ongoing maintenance</h3>
<p>Watchtower checks for a new <code>packeton/packeton:latest</code> image daily and recreates the container automatically. No action needed.</p>
<p>Monitor the <a href="https://github.com/vtsykun/packeton/releases">Packeton releases</a> for any updates that require manual migration steps before they land.</p>


<p><strong>Read time:</strong> 4 mins</p>
<p><strong>Tags:</strong></p>
]]></description>
		</item>
		
		
		<item>
			<title>GitButler</title>
			<link>https://www.mikestreety.co.uk/notes/gitbutler/</link>
			<pubDate>Wed, 01 Apr 2026 16:23:00 GMT</pubDate>
			<guid>https://www.mikestreety.co.uk/notes/gitbutler/</guid>
			<description><![CDATA[
<p>Interesting Git tool allowing for Parallel branches and with a strong AI integration</p>

<p><a href="https://gitbutler.com/">https://gitbutler.com/</a></p>
<p><strong>Read time:</strong> 1 mins</p>

]]></description>
		</item>
		
		
		<item>
			<title>CSS Refactoring with an AI Safety Net</title>
			<link>https://www.mikestreety.co.uk/notes/css-refactoring-with-an-ai-safety-net/</link>
			<pubDate>Thu, 26 Mar 2026 11:49:00 GMT</pubDate>
			<guid>https://www.mikestreety.co.uk/notes/css-refactoring-with-an-ai-safety-net/</guid>
			<description><![CDATA[
<p>Claude Code was used to refactor a messy CSS codebase into a clean architecture across 7 phases, with a Playwright script capturing screenshots of 9 distinct app states before and after each phase.</p>

<p><a href="https://danielabaron.me/blog/css-refactoring-with-an-ai-safety-net/">https://danielabaron.me/blog/css-refactoring-with-an-ai-safety-net/</a></p>
<p><strong>Read time:</strong> 1 mins</p>

]]></description>
		</item>
		
		
		<item>
			<title>Checking your websites with the BLAT test</title>
			<link>https://www.mikestreety.co.uk/blog/checking-your-websites-with-the-blat-test/</link>
			<pubDate>Thu, 26 Mar 2026 00:00:00 GMT</pubDate>
			<guid>https://www.mikestreety.co.uk/blog/checking-your-websites-with-the-blat-test/</guid>
			<description><![CDATA[
<p>You've been staring at the same project for weeks. Your design eye is shot and the finish line is in sight. You can't see the wood for the trees.</p>
<p>This is exactly when you need fresh eyes. At Liquid Light, that's what the BLAT test is for.</p>
<p>BLAT is a timeboxed, no-holds-barred review where everyone gets a go at one of our nearly finished websites. Each person gets an hour to click around, prod things, and use the site as a real person would. The goal is to find holes.</p>
<p>It might be a personal preference. It might be a niggle. It might be the tiniest of nitpicks. Doesn't matter. It goes on the list.</p>
<p>The project manager then reviews the list and decides what to action, postpone, or drop. It's not personal. It's priorities.</p>
<p>Things that tend to come up:</p>
<ul>
<li>Spacing between two particular elements</li>
<li>Accessibility of a link in a specific context</li>
<li>Unexpected (or missing) behaviour from an interaction</li>
<li>Image sizes affecting performance</li>
<li>Print styles nobody tested</li>
<li>Odd flows between pages</li>
<li>Future website improvements or additions</li>
</ul>
<p>Anything goes, as long as the note includes:</p>
<ul>
<li>A link</li>
<li>A description</li>
<li>A screenshot where possible</li>
</ul>
<p>Side note: No, BLAT doesn't actually stand for anything. We all know what it means (although out of all the recommendations from Claude my favourite was <strong>B</strong>rutally <strong>L</strong>ook <strong>A</strong>t <strong>T</strong>hings )</p>


<p><strong>Read time:</strong> 1 mins</p>
<p><strong>Tags:</strong></p>
]]></description>
		</item>
		
		
		<item>
			<title>Completely remove DDEV from your computer</title>
			<link>https://www.mikestreety.co.uk/blog/completely-remove-ddev-from-your-computer/</link>
			<pubDate>Fri, 06 Feb 2026 00:00:00 GMT</pubDate>
			<guid>https://www.mikestreety.co.uk/blog/completely-remove-ddev-from-your-computer/</guid>
			<description><![CDATA[
<p>I recently ran into an issue with DDEV 1.25 and was upgrading and downgrading between the two versions to test, check and verify.</p>
<p>Eventually, my DDEV got confused and started producing 404s. With mixed version images &amp; config, I wanted to remove everything and start again.</p>
<p>With the help of Claude, I created a bash script which will run through and delete every DDEV related configuration.</p>
<p><a href="https://gist.github.com/mikestreety/07d531b346ab8ce9c62ec655dd4274a4" class="button">Completely remove DDEV</a></p>
<h2>Steps</h2>
<ol>
<li>Copy the contents or download the zip</li>
<li>Make the file executeable - <code>cd path/to/file</code> and <code>chmod +x ./remove-ddev.sh</code></li>
<li>Run the script <code>./remove-ddev.sh</code> - there is <code>--help</code> and <code>--dry-run</code> flags available</li>
</ol>


<p><strong>Read time:</strong> 1 mins</p>
<p><strong>Tags:</strong></p>
]]></description>
		</item>
		
		
		<item>
			<title>Playwriter - Browser Automation MCP</title>
			<link>https://www.mikestreety.co.uk/notes/playwriter-browser-automation-mcp/</link>
			<pubDate>Tue, 06 Jan 2026 15:53:00 GMT</pubDate>
			<guid>https://www.mikestreety.co.uk/notes/playwriter-browser-automation-mcp/</guid>
			<description><![CDATA[
<p>Like Playwright MCP but via extension. 90% less context window. 10x more capable (full playwright API)</p>

<p><a href="https://github.com/remorses/playwriter">https://github.com/remorses/playwriter</a></p>
<p><strong>Read time:</strong> 1 mins</p>

]]></description>
		</item>
		
		
		<item>
			<title>2025 In Review</title>
			<link>https://www.mikestreety.co.uk/blog/2025-in-review/</link>
			<pubDate>Wed, 31 Dec 2025 00:00:00 GMT</pubDate>
			<guid>https://www.mikestreety.co.uk/blog/2025-in-review/</guid>
			<description><![CDATA[
<p>Like 2024, 2025 passed without major incident or upheaval. All in all, it was an enjoyable year - seeing some firsts for us all.</p>
<h2>Life</h2>
<p>Plenty of goings-on with the Street family this year. Ruby, our youngest, started school which meant another shift in routines and schedules. Alfie moved up to Beavers and Ruby started Squirrels, which now means I'm the only one of our family to not be currently invested in a scouting section.</p>
<p>Alfie has become a classic &quot;kid&quot; and discovered Minecraft. He'd been talking about it at school and we finally gave in and bought a copy for the XBOX. I've also enjoyed playing it, trying to actually build things and thinking about layout (although it's still enjoyable to build a tower of TNT and blog it up).</p>
<p>There were some small home improvements - I redecorated the garden office and painted the &quot;TV corner&quot; of the lounge with matching bookcases. The biggest change was the demolition of the back garden patio and building of a deck - carried out by my dad and me.</p>
<p>To finish off the year, our car decided to give us a Christmas present of breaking. We couldn't get it booked in until the 5th Jan, however my mother-in-law leant us her car for the festive period which meant Christmas was saved.</p>
<h2>Trip and Holidays</h2>
<p>For our main holiday this year we took the kids to Disneyland Paris. It was a first for me - going on the channel tunnel and driving on foreign soil in my own car. Disneyland was hectic and expensive and fun and chaos. We went with my wife's family which did mean we had the opportunity to leave the kids with each other and head off to the big rides.</p>
<p>Little trips included taking Ruby to London for the first time, a boat trip out to the <a href="https://en.wikipedia.org/wiki/Rampion_Wind_Farm">Rampion Wind Farm</a> and a visit to Brooklands Museum in Weybridge.</p>
<p>We also spent a week in a static caravan in the New Forest - it was a classic &quot;caravan park&quot; style holiday, with on-site swimming, golf and evening entertainment. We sprinkled in some day trips to Paultons Park and a miniature steam railway. We also found a pub round the corner with an <em>incredible</em> outdoor play area for the kids (while Chilly and I kicked back with a book and a beer).</p>
<p>My attendance to gigs sky-rocketed this year as I got the taste for it last year. This year saw a few shows with the kids along with seeing OneRepublic (my first trip to the 02 since it was the millennium dome), Nizlopi (a birthday present), Self Esteem and it seems I can't go through a year without seeing Bastille. The last gig of the year was with my mum and family to see Stereophonics at the 02 again.</p>
<h2>Stats Analysis</h2>
<p><a href="/stats/">Visit the stats page</a>.</p>
<p>Cycling was a big part this year - recording the highest ever number of miles done in a year since I started recording. A big part of this was the turbo trainer I purchased at the end of last year, with just 85 miles separating virtual and real-world bike rides (and I rode more virtual miles then I did on an eBike)!</p>
<p>I also hit a few big rides this year, doing the London to Brighton bike ride (for the third time), a 65 mile bike ride in August and 70 miles around the Isel of Wight in October. I was pretty happy that I was able to pull these out the bag without <em>much</em> concious training. It seems cycling to and from work combined with the turbo proved to be a pretty effective training plan. I'd like to hit <strong>4000 miles</strong> again in 2026.</p>
<p>My Geocaching suffered this year - only finding 2 at the beginning of the year. I need to get some caching days booked in in 2026 to get those numbers back up. I feel like <strong>75 Geocaches</strong> is a good target.</p>
<p>Everything else, such as blog posts, steps and music streams stayed steady.</p>


<p><strong>Read time:</strong> 3 mins</p>
<p><strong>Tags:</strong></p>
]]></description>
		</item>
		
		
		<item>
			<title>Oh My Posh</title>
			<link>https://www.mikestreety.co.uk/notes/oh-my-posh/</link>
			<pubDate>Tue, 30 Dec 2025 08:36:00 GMT</pubDate>
			<guid>https://www.mikestreety.co.uk/notes/oh-my-posh/</guid>
			<description><![CDATA[
<p>The most customizable and fastest prompt engine for any shell</p>

<p><a href="https://ohmyposh.dev/">https://ohmyposh.dev/</a></p>
<p><strong>Read time:</strong> 1 mins</p>

]]></description>
		</item>
		
		
		<item>
			<title>Makefile includes and other Makefile tips and tricks</title>
			<link>https://www.mikestreety.co.uk/blog/makefile-includes-and-other-makefile-tips-and-tricks/</link>
			<pubDate>Sun, 28 Dec 2025 00:00:00 GMT</pubDate>
			<guid>https://www.mikestreety.co.uk/blog/makefile-includes-and-other-makefile-tips-and-tricks/</guid>
			<description><![CDATA[
<p>We use Makefiles in our repositories to wrangle commands for setting up, pulling and linting our code. Nothing revolutionary, but they've become pretty essential to our workflow.</p>
<h2>Makefile includes</h2>
<p>The thing that initially had me scratching my head was wanting to share commands between sites. A lot of our sites follow the same patterns and need identical Makefile commands - seemed daft to copy-paste the same stuff everywhere when we could have one central file doing the heavy lifting.</p>
<p>I spent ages trawling through Stack Overflow and various forums, but here's the thing - because our Makefiles essentially just contain bash commands (rather than actually &quot;making&quot; anything in the traditional sense), there was loads of conflicting advice that didn't quite fit our use case. Eventually, I gave up being stubborn and asked AI, which promptly gave me exactly what I needed:</p>
<pre class="language-makefile"><code class="language-makefile"><span class="token keyword">-include</span> ./path/to/file.mk</code></pre>
<p><strong>Word of warning:</strong> That hyphen <code>-</code> before <code>include</code> is doing important work - it prevents Make from throwing a tantrum if the file happens to be missing (which can happen if it's installed via a dependency that hasn't been pulled yet).</p>
<p><code>.mk</code> is the recognised file extension for Makefiles that aren't called <code>Makefile</code> - bit of trivia for you there.</p>
<p>When you're including a file with commands, you can overwrite them in your local Makefile if you need project-specific tweaks. Make will give you a gentle warning about this, but it's just letting you know something's been overridden - nothing to worry about.</p>
<h2>Variables</h2>
<p>Your central Makefile might need some paths or other bits that vary between projects. You can handle this much like bash - define variables without a prefix, use them with one:</p>
<pre class="language-makefile"><code class="language-makefile">SITE_PATH_PRODUCTION <span class="token operator">:=</span> ~/www/current

<span class="token keyword">-include</span> ./path/to/file.mk</code></pre>
<p>Then reference that variable in your shared Makefile:</p>
<pre class="language-makefile"><code class="language-makefile"><span class="token comment">## Pull the full database from production</span>
<span class="token target symbol">db-full-pull-production</span><span class="token punctuation">:</span>
	ssh <span class="token variable">$</span><span class="token punctuation">(</span>SSH_HOST<span class="token punctuation">)</span> \
<span class="token target symbol">		"<span class="token variable">$</span>(SITE_PATH_PRODUCTION)/vendor/bin/typo3 database</span><span class="token punctuation">:</span><span class="token keyword">export</span> ...</code></pre>
<p>As a bit of a safety net, you can set defaults at the top of your shared makefile too:</p>
<pre class="language-makefile"><code class="language-makefile">SITE_PATH_PRODUCTION <span class="token operator">?=</span> ~/www/current</code></pre>
<p>That way things won't explode if someone forgets to set a variable.</p>
<h2>.PHONY Commands</h2>
<p>All our commands are basically bash scripts masquerading as Make targets. This works fine until you accidentally create a folder that matches one of your command names.</p>
<p>For example, if you've got <code>make config</code> but also have a <code>config/</code> folder hanging about, running <code>make config</code> will target the folder instead of your command. Bit annoying when you're expecting it to do something entirely different.</p>
<p>You can tell Make which commands are <code>.PHONY</code> (i.e., they don't correspond to actual files), but since <em>all</em> of ours are just bash commands in disguise, we take the sledgehammer approach and mark everything as phony:</p>
<pre class="language-makefile"><code class="language-makefile"><span class="token builtin-target builtin">.PHONY</span><span class="token punctuation">:</span> *</code></pre>
<h2>Help block</h2>
<p>Here's something that'll save you from accidentally running the wrong command: by default, running <code>make</code> with no target executes whatever's first in the file. This could be anything - a rebuild command, a deployment script, something that downloads half the internet. Not ideal.</p>
<p>We stick a help generator at the top of our Makefiles that serves double duty - it shows available commands <em>and</em> acts as a safety net:</p>
<pre class="language-makefile"><code class="language-makefile"><span class="token target symbol">help</span><span class="token punctuation">:</span>
	<span class="token operator">@</span>echo <span class="token string">"\033[0;33mAvailable targets\033[0m"</span>
	<span class="token operator">@</span>echo <span class="token string">"\033[0;33m-----------------\033[0m"</span>
	<span class="token operator">@</span>awk <span class="token string">'/^[[:alnum:]_-]+:/ { \
		helpMessage = match(lastLine, /^## (.*)/); \
		if (helpMessage) { \
			helpCommand = substr($$1, 0, index($$1, ":")-1); \
			helpMessage = substr(lastLine, RSTART + 3, RLENGTH); \
			printf "%-25s %s\n", helpCommand, helpMessage; \
		} \
	} \
	{ lastLine = $$0 }'</span> <span class="token variable">$</span><span class="token punctuation">(</span>MAKEFILE_LIST<span class="token punctuation">)</span></code></pre>
<p>Any command with a double hash comment (<code>##</code>) above it gets picked up and displayed in the help. Simple but effective - and it means accidentally running <code>make</code> just shows you what's available rather than doing something potentially destructive.</p>
<p>Certainly not the most groundbreaking setup, but it's made managing our various projects much less of a faff. If you've got a different approach or improvements to suggest, I'd love to hear about them.</p>


<p><strong>Read time:</strong> 3 mins</p>
<p><strong>Tags:</strong></p>
]]></description>
		</item>
		
		
		<item>
			<title>2025 Quiz of the Year</title>
			<link>https://www.mikestreety.co.uk/blog/2025-quiz-of-the-year/</link>
			<pubDate>Sat, 27 Dec 2025 00:00:00 GMT</pubDate>
			<guid>https://www.mikestreety.co.uk/blog/2025-quiz-of-the-year/</guid>
			<description><![CDATA[
<p>This years <a href="category/quiz/">quiz of the year</a> needs the following:</p>
<ul>
<li>The slides (linked below)</li>
<li>The info and notes below</li>
<li>Pens and paper for your teams</li>
</ul>
<p>The quiz can be played in teams or individually - I'll leave it to you to work it out. There doesn't need to be a &quot;quiz master&quot; per say, just someone who can click &quot;next slide please&quot;.</p>
<h2>Slides</h2>
<p><a href="https://docs.google.com/presentation/d/1iHsGoROP03uZz5BUKbMiLn9i4Tk7WISxWbR-ZgkYClY/edit?usp=sharing" class="button">Get the quiz slides</a></p>
<p>The slides are on Google, however if you need them in a different format, <a href="/contact/">let me know</a>.</p>
<h2>Quiz Information</h2>
<p>This quiz is 5 rounds with 7 questions in most rounds (except the picture round, which has 11).</p>
<p>Most answers are 2 points per correct answer (allowing single points to be rewarded where deserved).</p>
<p>When running the quiz I ask that phones are put away - more for politeness than fear of cheating. I also make it clear that the answers in the quiz are always right - even if they are not. This way it is fair and should hopefully avoid arguments.</p>
<h3>Round explanations</h3>
<h4>1.Battlenips (and other parts)</h4>
<p>Identify the grid reference where the specified body part of object is.</p>
<h4>2. Music</h4>
<p>Fill in the missing lines of the song. Single points can be awarded if the teams are <em>nearly</em> right.</p>
<h4>3. Film</h4>
<p>Identify the films featuring my face</p>
<h4>4. Picture</h4>
<p>I got my 7 &amp; 4 year-olds to draw things from the garden and park. What are they?</p>
<h4>5. 2025</h4>
<p>7 questions about what happened in 2025.</p>
<h2>The end</h2>
<p>Let me know if you use this quiz and how you get on - was it to easy? to hard? to complicated?</p>


<p><strong>Read time:</strong> 1 mins</p>
<p><strong>Tags:</strong></p>
]]></description>
		</item>
		
		
		<item>
			<title>Keeping RustFS clear of old assets</title>
			<link>https://www.mikestreety.co.uk/blog/keeping-rustfs-clear-of-old-assets/</link>
			<pubDate>Thu, 06 Nov 2025 00:00:00 GMT</pubDate>
			<guid>https://www.mikestreety.co.uk/blog/keeping-rustfs-clear-of-old-assets/</guid>
			<description><![CDATA[
<p>After having <a href="/blog/setting-up-rustfs-as-an-amazon-s3-replacement/">RustFS</a> running as our <a href="/blog/use-minio-to-cache-gitlab-containers-and-runners/">Gitlab CI cache</a> for a few weeks the server (as expected) filled up.</p>
<p>Since we're only using RustFS to cache build assets, we can safely bin the old ones without worry. We settled on a 14-day cut-off - bit arbitrary really, but it works. The worst that can happen is the application won't deploy without them, which means you'll have to re-run the entire pipeline if you're trying to deploy something that hasn't been built in a fortnight. Not ideal, but hardly the end of the world.</p>
<p>RustFS is comapitble with <code>mc</code> - the MinIo command so I started looking there but then ended up with a default linux command</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">find</span> /data/rustfs0/XXX <span class="token parameter variable">-mindepth</span> <span class="token number">1</span> <span class="token parameter variable">-type</span> f <span class="token parameter variable">-mtime</span> +14 <span class="token operator">|</span> <span class="token function">xargs</span> <span class="token function">rm</span></code></pre>
<div class="info"><strong>Note:</strong> Make sure you specify the path (<code>XXX</code> in the example above) to your bucket as RustFS stores configuration in <code>/data/rustfs0/.rustfs.sys</code> - I ended up deleting our user access by removing files older than 14 days in this folder</div>
<p>Once you have the command and are happy with it, add it to a crontab to run once a night.</p>
<p>To edit the crontab, run <code>crontab -e</code> and place the following at the bottom (this will run a 10pm every evening)</p>
<pre class="language-bash"><code class="language-bash"><span class="token number">0</span> <span class="token number">22</span> * * * <span class="token function">find</span> /data/rustfs0/gitlab-ci <span class="token parameter variable">-mindepth</span> <span class="token number">1</span> <span class="token parameter variable">-type</span> f <span class="token parameter variable">-mtime</span> +14 <span class="token operator">|</span> <span class="token function">xargs</span> <span class="token function">rm</span></code></pre>


<p><strong>Read time:</strong> 1 mins</p>
<p><strong>Tags:</strong></p>
]]></description>
		</item>
		
		
		<item>
			<title>Email authentication records to improve deliverability</title>
			<link>https://www.mikestreety.co.uk/blog/email-authentication-records-to-improve-deliverability/</link>
			<pubDate>Mon, 13 Oct 2025 00:00:00 GMT</pubDate>
			<guid>https://www.mikestreety.co.uk/blog/email-authentication-records-to-improve-deliverability/</guid>
			<description><![CDATA[
<p>Sending emails in this mad world of spam is a tricky business. Spoofing and phishing are all too common, and email providers try to be smart to it, although sometimes at the detriment to honest and &quot;real&quot; emails.</p>
<p>If your website is sending emails at all (even to you for contact form responses), it is worth considering spending time to verify that you own the domain and you are allowed to send emails from it. Word of warning: this isn't going to be the most thrilling post, but it's one of those things that'll save you a proper headache down the line when your carefully crafted emails end up in spam folders.</p>
<p><strong>SPF</strong>, <strong>DKIM</strong> and <strong>DMARC</strong> records all help with this and below each one is explained as to what it does and how you set it up. I'll be honest - I found these records a bit bewildering at first, but once you've set them up a few times they become second nature. Think of them as your email's passport - proving you are who you say you are.</p>
<h2>Testing Tools</h2>
<p>Before we dive in, bookmark this:</p>
<ul>
<li><a href="https://www.mail-tester.com/">mail-tester</a> - Send an email to get real-world data (proper useful, this one - gives you a score out of 10 and tells you exactly what's wrong)</li>
</ul>
<h2>SPF Record</h2>
<ul>
<li><a href="https://mailtrap.io/blog/spf-records-explained/">SPF Records explained</a></li>
<li><a href="https://mxtoolbox.com/spf.aspx">SPF Tester</a></li>
</ul>
<p>SPF (Sender Policy Framework) is basically your domain saying &quot;these are the mail servers allowed to send email on my behalf&quot;. Without it, anyone could pretend to send emails from your domain - which is as dodgy as it sounds.</p>
<p>This is how the most common SPF record looks:</p>
<pre><code>v=spf1 a mx -all
</code></pre>
<p>Breaking this down: <code>v=spf1</code> is the version, <code>a</code> and <code>mx</code> mean your domain's A record and MX records are allowed to send mail, and <code>-all</code> means &quot;reject anything else&quot;. That last bit is important - it's like saying &quot;if it's not on the list, it's not coming in&quot;.</p>
<p>As an example, if your client uses <strong>Google Workspace</strong>, you'll need to add <code>include:_spf.google.com</code>. Same goes for services like Mailchimp:</p>
<pre><code>v=spf1 a mx include:_spf.google.com include:mailchimpapp.net -all
</code></pre>
<p>Most services which send emails on your behalf will have some documentation detailing what SPF Record you need.</p>
<h2>DMARC Record</h2>
<ul>
<li><a href="https://dmarcian.com/dmarc-record-wizard/">DMARC wizard</a> (weirdly satisfying to use, this one)</li>
</ul>
<p>DMARC (Domain-based Message Authentication, Reporting and Conformance) tells receiving mail servers what to do if your SPF or DKIM checks fail. It also sends you reports so you can see if someone's trying to spoof your domain - certainly interesting to see what's being attempted in the wild.</p>
<p>A good standard is something like the following:</p>
<ul>
<li>Target: <code>_dmarc.@</code></li>
<li>Type: <code>TXT</code></li>
<li>Record:</li>
</ul>
<pre><code>v=DMARC1; p=quarantine; rua=mailto:email@example.com; aspf=r;
</code></pre>
<p>Or if you want to be a bit stricter:</p>
<pre><code>v=DMARC1; p=quarantine; pct=100; aspf=s;
</code></pre>
<p>The <code>p=quarantine</code> means &quot;if this looks dodgy, put it in spam rather than rejecting it outright&quot;. You can use <code>p=reject</code> if you're feeling confident, but I'd recommend starting with quarantine until you're sure everything's configured properly.</p>
<h2>DKIM Record</h2>
<p>This can only be configured if the service you are using emits a DKIM signature or similar. CMS's, like TYPO3, does not include a DKIM header so make sure you know before you start.</p>
<p>DKIM (DomainKeys Identified Mail) adds a digital signature to your emails - like a wax seal on a letter proving it hasn't been tampered with. Your email service provider will generate the keys for you.</p>
<p>Check with the system for instructions - each provider does it slightly differently and they'll give you the specific DNS records to add.</p>
<h2>BIMI</h2>
<p>Right, this one's a bit fancy and optional, but if you want your logo to appear next to your emails in supported clients (Gmail, Yahoo, etc.), BIMI is what you need.</p>
<ul>
<li><a href="https://bimigroup.org/bimi-generator/">BIMI Generator &amp; Inspector</a></li>
<li>Use a 512px square SVG for the image (the Favicon SVG is perfect for this)</li>
<li>We don't generally have a <strong>VMC</strong> (Verified Mark Certificate) available - these cost proper money and are only really worth it for big brands</li>
</ul>
<p>Example BIMI:</p>
<ul>
<li>Target: <code>default._bimi.@</code></li>
<li>Type: <code>TXT</code></li>
<li>Record: <code>v=BIMI1; l=https://link/to/svg;</code></li>
</ul>
<h2>Final Thoughts</h2>
<p>I should really test email deliverability more systematically on projects, but these DNS records are a good foundation. Set them up early and you'll avoid that awkward conversation later where the client asks why their contact form emails keep ending up in spam.</p>
<p>If anyone's got experience with VMC certificates for BIMI or has tips on DKIM implementation in TYPO3, I'd love to hear your thoughts. There's still a lot of nuance to email deliverability that catches me out occasionally.</p>


<p><strong>Read time:</strong> 3 mins</p>
<p><strong>Tags:</strong></p>
]]></description>
		</item>
		
		
		<item>
			<title>Setting up RustFS as an Amazon S3 replacement</title>
			<link>https://www.mikestreety.co.uk/blog/setting-up-rustfs-as-an-amazon-s3-replacement/</link>
			<pubDate>Sun, 12 Oct 2025 00:00:00 GMT</pubDate>
			<guid>https://www.mikestreety.co.uk/blog/setting-up-rustfs-as-an-amazon-s3-replacement/</guid>
			<description><![CDATA[
<p>I was at <a href="https://t3cl25.typo3.com/">TYPO3 Camp London</a> recently when Martin Helmich casually dropped that <a href="https://rustfs.com/en/">RustFS</a> was a solid MinIO replacement. That got my attention.</p>
<p>I've written about MinIO before - <a href="/blog/how-i-improved-the-speed-of-docker-builds-in-gitlab-ci/">speeding up Gitlab CI</a> and <a href="/blog/use-minio-to-cache-gitlab-containers-and-runners/">caching Gitlab assets</a> - and it's been great as a self-hosted S3 alternative. But I'm always up for trying new toys, especially when they promise improvements.</p>
<p>Turns out RustFS has been benchmarked against MinIO and is <a href="https://github.com/orgs/rustfs/discussions/598#discussion-8952907">faster across the board</a>. That was enough to convince me to give it a go.</p>
<h2>Server setup</h2>
<p>We're running RustFS on a dedicated Ubuntu server with <a href="https://www.hetzner.com/">Hetzner</a> (our go-to VPS provider). I went with an Intel CX32:</p>
<ul>
<li>4 vCPU</li>
<li>8 GB RAM</li>
<li>80 GB Disk</li>
</ul>
<p><strong>Note:</strong> You'll need an external IPv4 address - <code>rustfs.com</code> only supports IPv4 for setup.</p>
<h2>Installation</h2>
<p>Once your VPS is up, installation is pleasantly straightforward. First, make sure you've got <code>unzip</code>:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">apt</span> update <span class="token operator">&amp;&amp;</span> <span class="token function">apt</span> upgrade
<span class="token function">apt</span> <span class="token function">install</span> <span class="token function">zip</span> <span class="token function">unzip</span></code></pre>
<p>Then grab the <a href="https://rustfs.com/en/download/?platform=linux">install script</a>:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">curl</span> <span class="token parameter variable">-O</span> https://rustfs.com/install_rustfs.sh <span class="token operator">&amp;&amp;</span> <span class="token function">bash</span> install_rustfs.sh</code></pre>
<p>Follow the CLI prompts and you're sorted.</p>
<h2>Setup</h2>
<p>After installation, hit up the web interface at <code>http://[server-ip]:9000</code>. Default credentials are <code>rustfsadmin</code> for both username and password.</p>
<p>Change that immediately by editing:</p>
<pre><code>/etc/default/rustfs
</code></pre>
<p>Once logged in, you can create buckets and extra users or access keys - which is what I've been using for Gitlab CI.</p>


<p><strong>Read time:</strong> 1 mins</p>
<p><strong>Tags:</strong></p>
]]></description>
		</item>
		
		
		<item>
			<title>Shower thoughts that live in my head rent free</title>
			<link>https://www.mikestreety.co.uk/blog/shower-thoughts-that-live-in-my-head-rent-free/</link>
			<pubDate>Sat, 11 Oct 2025 00:00:00 GMT</pubDate>
			<guid>https://www.mikestreety.co.uk/blog/shower-thoughts-that-live-in-my-head-rent-free/</guid>
			<description><![CDATA[
<p>The idea of shower thoughts are things for you to ponder or wonder, these are ones that I have read on the internet (I take no credit) and often think about for no reason at all.</p>
<blockquote>
<p>Sleep is one of the few things we pretend to do to actually do it</p>
</blockquote>
<p>Close your eyes, lie down and pretend until you actually drift off</p>
<blockquote>
<p>Cleaning your teeth is the only time we clean our skeleton</p>
</blockquote>
<p>You don't see a skull with a beard, do you?</p>
<blockquote>
<p>Why do we have round lenses on cameras, but rectangle sensors and photos?</p>
</blockquote>
<p>Surely the lens is capturing more of the photo then we ever see?</p>
<blockquote>
<p>Have you ever walked in a &quot;space&quot; of the earth that no human has before?</p>
</blockquote>
<p>Doesn't matter what is actually under your feet, but has a human ever been to that lat/long before?</p>


<p><strong>Read time:</strong> 1 mins</p>
<p><strong>Tags:</strong></p>
]]></description>
		</item>
		
		
		<item>
			<title>Migrate your GitLab instance to a new domain</title>
			<link>https://www.mikestreety.co.uk/blog/migrate-your-gitlab-instance-to-a-new-domain/</link>
			<pubDate>Fri, 10 Oct 2025 00:00:00 GMT</pubDate>
			<guid>https://www.mikestreety.co.uk/blog/migrate-your-gitlab-instance-to-a-new-domain/</guid>
			<description><![CDATA[
<p>We were sunsetting a domain and wanted to migrate our GitLab instance to a different URL.</p>
<p>GitLab doesn't make this easy. There's no big &quot;Change Domain&quot; button, and if you're using package registries, you're in for a proper adventure and need to get your team on board.</p>
<h2>Steps we'll cover</h2>
<ol>
<li>Add the new domain alongside the old one (30 mins)</li>
<li>Switch the primary domain (30 mins + testing time)</li>
<li>Set up redirects (1 hour)</li>
<li>Clean up (10 mins)</li>
<li>Deal with package registry authentication (varies, but budget a day if you're unlucky)</li>
</ol>
<p>In the examples below, I'm using:</p>
<ul>
<li><code>old.gitlab-company.org</code> - the old domain</li>
<li><code>new.gitlab-instance.com</code> - where we're migrating to</li>
</ul>
<p>Word of warning: if you're using GitLab as a package registry (NPM, Docker, whatever), prepare yourself. This is where most of the pain lives.</p>
<h2>Add the second domain</h2>
<p>The first step is to allow GitLab to accept connections from the new domain. This lets you test everything works before you commit to the switch - you'll be able to access GitLab on both domains simultaneously, which is brilliant for testing.</p>
<p>Once you point the domain record to your GitLab instance, you'll find you can navigate to it and click around - GitLab doesn't try to redirect you back to the primary domain. You may, however, encounter an SSL error. This can be resolved by adding the secondary domain to Let's Encrypt and allowing GitLab to generate an SSL certificate for it.</p>
<p>Edit the GitLab config file <code>/etc/gitlab/gitlab.rb</code> and add the following:</p>
<pre class="language-ruby"><code class="language-ruby">letsencrypt<span class="token punctuation">[</span><span class="token string-literal"><span class="token string">'alt_names'</span></span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string-literal"><span class="token string">'new.gitlab-instance.com'</span></span><span class="token punctuation">,</span> <span class="token string-literal"><span class="token string">'registry.new.gitlab-instance.com'</span></span><span class="token punctuation">]</span></code></pre>
<p>Note: If you have a container registry or any other subdomains, these will need to be added too.</p>
<p>Reconfigure the instance:</p>
<pre class="language-ruby"><code class="language-ruby">gitlab<span class="token operator">-</span>ctl reconfigure</code></pre>
<p>This will generate the SSL certificates while reconfiguring and will error if there are any subdomains it can't generate certificates for.</p>
<p>I'd encourage using this new domain for a day or two (we left it longer, because paranoia, but two days is probably fine if you're braver than us). Navigate around, clone some projects, generally kick the tyres to make sure there are no basic issues.</p>
<h2>Switch the instance domain</h2>
<p>The next step is to change the instance URL. This won't force a redirect but will mean GitLab responds with the new URL for API and internal requests. You'll still be able to navigate on the old URL, clone projects, and so on.</p>
<p>Edit the GitLab config file <code>/etc/gitlab/gitlab.rb</code> and update the <code>external_url</code> and <code>letsencrypt['alt_names']</code>:</p>
<pre class="language-ruby"><code class="language-ruby">external_url <span class="token string-literal"><span class="token string">'https://new.gitlab-instance.com'</span></span>
registry_external_url <span class="token string-literal"><span class="token string">'https://registry.new.gitlab-instance.com'</span></span>
letsencrypt<span class="token punctuation">[</span><span class="token string-literal"><span class="token string">'alt_names'</span></span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string-literal"><span class="token string">'old.gitlab-company.org'</span></span><span class="token punctuation">,</span> <span class="token string-literal"><span class="token string">'registry.old.gitlab-company.org'</span></span><span class="token punctuation">]</span></code></pre>
<p>Reconfigure the GitLab instance:</p>
<pre class="language-ruby"><code class="language-ruby">gitlab<span class="token operator">-</span>ctl reconfigure</code></pre>
<p>I'd advise using this new domain with GitLab for a week or two. It won't redirect you to the new domain, but clone URLs and other requests will use the new domain.</p>
<h2>Package registries</h2>
<p>This is where you'll begin to see issues. If you use your GitLab instance as a Docker or package registry, you'll need to ensure you've authenticated with all your package managers using the new domain.</p>
<p>If you use GitLab as your NPM registry, this will be the biggest pain. Every project needs re-authenticating with the new domain, and npm will absolutely refuse to install packages until you do. It'll update your <code>package-lock.json</code> happily enough, then leave you staring at authentication errors wondering what you've done to deserve this.</p>
<p>If you use something like <a href="https://docs.renovatebot.com/">Renovate</a>, this can help with migration, but it takes a lot of planning (and a lot of head scratching). Weirdly, dealing with this across multiple projects was more time-consuming than the actual GitLab migration.</p>
<p><a href="/contact/">Get in touch</a> if you need advice with your specific setup - I spent enough time Googling obscure package registry authentication that I might actually be able to help.</p>
<h2>Redirect to the new domain</h2>
<p>With the new instance battle-tested and working, it's time to set up a redirect. You may choose to do this when you switch the instance domain above, but I left it a week or two in case we needed to fall back to the old domain (which, to be fair, we didn't, but better safe than sorry).</p>
<p>Edit the GitLab config file <code>/etc/gitlab/gitlab.rb</code> and add the option to allow custom nginx configuration:</p>
<pre class="language-ruby"><code class="language-ruby">nginx<span class="token punctuation">[</span><span class="token string-literal"><span class="token string">'custom_nginx_config'</span></span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string-literal"><span class="token string">'include /etc/gitlab/nginx-extra.conf;'</span></span></code></pre>
<p>Next, create a config file - <code>/etc/gitlab/nginx-extra.conf</code>. I chose not to redirect the registry, but if you need to, there's an example in <a href="https://robinopletal.com/posts/gitlab-on-two-domains">Running GitLab simultaneously on two domains</a>.</p>
<p>Before you reconfigure the GitLab instance, ensure the SSL certificates are in the location specified below:</p>
<pre><code># web
server {
  listen 443 ssl http2;
  server_name old.gitlab-company.org;
  server_tokens off;

  ssl_certificate /etc/gitlab/ssl/old.gitlab-company.org.crt;
  ssl_certificate_key /etc/gitlab/ssl/old.gitlab-company.org.key;
  ssl_ciphers 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256';
  ssl_protocols TLSv1.2;
  ssl_prefer_server_ciphers on;
  ssl_session_cache builtin:1000 shared:SSL:10m;
  ssl_session_timeout 5m;

  return 301 https://new.gitlab-instance.com$request_uri;
}
</code></pre>
<p>Reconfigure the GitLab instance:</p>
<pre class="language-bash"><code class="language-bash">gitlab-ctl reconfigure</code></pre>
<h2>Clean-up</h2>
<p>After some time (we left it a month, but we're cautious like that), you can tidy things up:</p>
<ul>
<li>Delete <code>/etc/gitlab/nginx-extra.conf</code></li>
<li>Remove <code>nginx['custom_nginx_config']</code> from <code>/etc/gitlab/gitlab.rb</code></li>
<li>Remove any references to the old domain in <code>/etc/gitlab/gitlab.rb</code></li>
<li>Delete any references from your browser history</li>
</ul>
<p>The whole migration took us about three weeks from start to finish, mostly because we were being cautious and dealing with the package registry nightmare. If you're not using registries heavily, you could probably knock this out in a few days.</p>
<p>There's still a lot I don't know about GitLab's internals (it's a proper beast of a system), but this process worked well for us. If you hit any snags or your setup is a bit different, <a href="/contact/">get in touch</a> - I might be able to point you in the right direction.</p>


<p><strong>Read time:</strong> 4 mins</p>
<p><strong>Tags:</strong></p>
]]></description>
		</item>
		

	</channel>
</rss>
