<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xml" href="https://monkeyguild.org/feed.xslt.xml"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://monkeyguild.org/feed.xml" rel="self" type="application/atom+xml" /><link href="https://monkeyguild.org/" rel="alternate" type="text/html" hreflang="en" /><updated>2026-07-23T19:49:48-04:00</updated><id>https://monkeyguild.org/feed.xml</id><title type="html">The Monkey Guild</title><subtitle>A little technology, a little food, a lot of love.</subtitle><entry><title type="html">Chocolate Chip Graham Cracker Cookies</title><link href="https://monkeyguild.org/posts/chocolate-chip-graham-cracker-cookies/" rel="alternate" type="text/html" title="Chocolate Chip Graham Cracker Cookies" /><published>2026-07-23T00:00:00-04:00</published><updated>2026-07-23T00:00:00-04:00</updated><id>https://monkeyguild.org/posts/chocolate-chip-graham-cracker-cookies</id><content type="html" xml:base="https://monkeyguild.org/posts/chocolate-chip-graham-cracker-cookies/"><![CDATA[<p><strong>Cookie Ingredients</strong></p>
<ul>
  <li>1 stick salted butter, room temperature</li>
  <li>1/2 cup granulated sugar</li>
  <li>1/2 cup dark brown sugar</li>
  <li>1/2 tablespoon vanilla extract</li>
  <li>1 large egg</li>
  <li>1 1/2 cups graham cracker crumbs</li>
  <li>3/4 cup all-purpose flour</li>
  <li>1/2 teaspoon baking soda</li>
  <li>1 cup semi-sweet chocolate chips</li>
</ul>

<p><strong>Preparation</strong></p>
<ol>
  <li>Preheat oven to 350 degrees Fahrenheit.</li>
  <li>Run 2 sleeves of graham crackers through the food processor until finely powdered (I’d recommend pulling out any large bits left in the powder but it’s not required if you’re in a rush).</li>
  <li>Line two cookie trays with parchment paper.</li>
  <li>Cream the butter and both sugars together until well combined.</li>
  <li>Add the egg and continue mixing until it lightens slightly, then add in the vanilla extract.</li>
  <li>Add all the graham cracker crumbs and mix until combined.</li>
  <li>Add the flour and baking soda and mix until combined.</li>
  <li>Stir in the chocolate chips.</li>
  <li>Use a cookie scoop, tablespoon, whatever works for you, and scoop them out onto the parchment-paper-lined cookie sheets (looking at mine, I’d say they’re around 2 tablespoon scoops).</li>
  <li>Bake for 10 minutes and then check them to see how they’re looking. You want them to start to darken a bit before you pull them out of the oven (for my oven, this takes 12 minutes).</li>
</ol>

<p>Let them cool, then move them to a plate or a wire cooling rack to further firm up…or just tuck in and start eating, I’m not your supervisor.</p>

<p><strong>Notes</strong>
You’re probably tempted to add mini-marshmallows to these aren’t you? Don’t do it! Baking with marshmallows doesn’t go the way you’d hope it would and it tends to make a mess and ruin your cookies. If you’re unwavering in your desire for marshmallows in these you have a few options. At the 10-minute mark when you’re checking the cookies out, pull them out of the oven and sprinkle some mini marshmallows onto the cookies, then put them back in for the last bit of baking. Alternately if you want to order some freeze-dried or dehydrated marshmallows, those work great in baking and will likely give you what you’re looking for.</p>]]></content><author><name>Noah Krohn</name></author><category term="Blog" /><category term="Recipe" /><category term="dessert" /><category term="cookie" /><category term="baking" /><summary type="html"><![CDATA[Perhaps the finest tasting chocolate chip cookie I've ever had.]]></summary></entry><entry><title type="html">Backup of Nextcloud to Remote Borg Repository</title><link href="https://monkeyguild.org/posts/nextcloud-borg-backup/" rel="alternate" type="text/html" title="Backup of Nextcloud to Remote Borg Repository" /><published>2026-03-22T00:00:00-04:00</published><updated>2026-03-22T00:00:00-04:00</updated><id>https://monkeyguild.org/posts/nextcloud-borg-backup</id><content type="html" xml:base="https://monkeyguild.org/posts/nextcloud-borg-backup/"><![CDATA[<p>The steps to get Nextcloud AIO setup are straightforward and, at least in my experience, accurate. I ran into problems when attempting to get backup jobs setup with the included Borg Backup container and I wanted to capture what I did while it was fresh in my mind.</p>

<p>Let’s assume you’re working with two servers. Your primary Nextcloud server has an IP address of 10.200.10.10 and your remote server that you’re using to capture your backups is 10.200.10.20. For ease of use we’ll have both running Ubuntu but the steps to install the Borg binary is accurate in the docs, so this assumption is largely just so you’re working with complete instructions.</p>

<p>On your remote system, install Borg, create a user for borg backups (root is not recommended), and generate an SSH key.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
</pre></td><td class="rouge-code"><pre><span class="nb">sudo </span>apt-get update <span class="o">&amp;&amp;</span> <span class="nb">sudo </span>apt-get <span class="nb">install </span>borgbackup
<span class="nb">sudo </span>useradd <span class="nt">-c</span> <span class="s2">"Borg Backups"</span> <span class="nt">-m</span> <span class="nt">-s</span> /bin/bash borg
<span class="nb">sudo </span>su borg -
ssh-keygen <span class="nt">-o</span> <span class="nt">-a</span> 100 <span class="nt">-t</span> ed25519
<span class="nb">cat</span> ~/.ssh/id_ed25519.pub <span class="o">&gt;&gt;</span> ~/.ssh/authorized_keys
<span class="nb">chmod </span>0600 ~/.ssh/authorized_keys
</pre></td></tr></tbody></table></code></pre></div></div>

<p>On your local system you likely already have an SSH key but if you don’t, you’ll need to create one. Copy the contents of the public key into the <code class="language-plaintext highlighter-rouge">authorized_keys</code> file you created above. Once that’s done, you’re going to install Borg Backup on your local machine and initialize the repository on the remote host. Let’s assume your backup directory for your files is in /mnt/backup/borg on your remote.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="nb">sudo </span>apt-get update <span class="o">&amp;&amp;</span> <span class="nb">sudo </span>apt-get <span class="nb">install </span>borgbackup
<span class="nb">export </span><span class="nv">BORG_REPO</span><span class="o">=</span>ssh://borg@10.200.10.20/mnt/backup/borg
<span class="nb">export </span><span class="nv">BORG_PASSPHRASE</span><span class="o">=</span>a complex password of some kind or see below <span class="k">for </span>other options
borg init <span class="nt">-e</span> repokey-blake2
borg key <span class="nb">export </span>borg@10.200.10.20/mnt/backup/borg encrypted-key-backup
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Now, connect to your Nextcloud management interface at https://10.200.10.10:8080/containers and stop all the containers. Set the backup target to ssh://borg@10.200.10.20/mnt/backup/borg and start the backup job. This first job will fail, which believe it or not is OK. The UI will display a public key that you need to add to the remote so add a new line to the <code class="language-plaintext highlighter-rouge">authorized_keys</code> file on your remote server and paste the public key. It will also show a password to use for encryption which I ended up using as the password for my Borg Backups but I’m honestly not sure if this is required or not. Either way, while you’re on the remote server you’re also going to need to grab a copy of the Borg config file</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="nb">cat</span> /mnt/backup/borg
</pre></td></tr></tbody></table></code></pre></div></div>
<p>Take the contenets of the above text and copy it into a new file on your Nextcloud server located at <code class="language-plaintext highlighter-rouge">/var/lib/docker/volumes/nextcloud_aio_mastercontainer/_data/data/borg.config</code>. Lastly you’ll want to set the privs on that file appropriately:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="nb">chown </span>www-data:www-data /var/lib/docker/volumes/nextcloud_aio_mastercontainer/_data/data/borg.config
<span class="nb">chmod </span>0770 /var/lib/docker/volumes/nextcloud_aio_mastercontainer/_data/data/borg.config
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Now that this is all done, the backups should work and you’re all good to go. You only have to do this for the first backup job and afterwards the scheduled cron jobs should take care of things for you. I hope this helps someone else as I banged my head against this for ages.</p>]]></content><author><name>Noah Krohn</name></author><category term="Blog" /><category term="Technology" /><category term="technology" /><category term="nextcloud" /><category term="backups" /><summary type="html"><![CDATA[There are missing steps to getting Borg Backup to work on a new Nextcloud install]]></summary></entry><entry><title type="html">Vegan Vanilla Coffee Creamer</title><link href="https://monkeyguild.org/posts/vegan-coffee-creamer/" rel="alternate" type="text/html" title="Vegan Vanilla Coffee Creamer" /><published>2025-09-13T00:00:00-04:00</published><updated>2025-09-13T00:00:00-04:00</updated><id>https://monkeyguild.org/posts/vegan-coffee-creamer</id><content type="html" xml:base="https://monkeyguild.org/posts/vegan-coffee-creamer/"><![CDATA[<p>I’m a supertaster, which is honestly not as much fun as you might think. When I’m eating something delicious it’s pretty great being able to taste so much subtlety and nuance. The rest of the time though it’s a pain. When I was vegan, it was definitely a problem for me as so much of the coffee creamer space was soy (which tastes like paste to me) or had some kind of non-sugar sweetener in it, like agave or erythritol (which tastes bitter and artificial to me). I still don’t understand why so much of that food space relies so heavily on agave and other such sweeteners instead of just using plain old sugar which is still vegan and I would think would probably be cheaper.</p>

<p>I was gifted a Vitamix by my parents which is such a huge boon if you’re making homemade dairy alternatives and I wanted to share my coffee creamer recipe.</p>

<ul>
  <li>500 ml cold, filtered water, divided</li>
  <li>200 grams raw, unsalted cashews</li>
  <li>1 teaspoon vanilla bean paste or 1 vanilla bean</li>
  <li>25 grams white sugar</li>
  <li>1 pinch of salt</li>
  <li>1 pinch of baking soda / bicarbonate of soda</li>
</ul>

<ol>
  <li>If you’re using a Vitamix, skip to step 2. If not then put the cashews in a container, fill it with water (with enough room for some expansion), and allow cashews to soak overnight.</li>
  <li>Drain the cashews if you’re using soaked cashews. Add 250 ml of water and the raw cashews to the blender along with the sugar and pinch of salt.</li>
  <li>If you’re using a vanilla bean, slice the vanilla bean pod lengthwise and scrape the seeds with the edge of your knife and then transfer them to the blender. Otherwise just add the vanilla bean paste at this stage.</li>
  <li>Blend on high for 8-10 minutes in a normal blender or about 5 minutes in a Vitamix. The goal is to have a completely smooth and creamy mixture.</li>
  <li>Turn the blender to low and slowly add in another 250 ml of water. Blend until combined.</li>
  <li>Check the consistency of your creamer to see if it’s to your liking. If you’re looking for the consistency of storebought cashew coffee creamer, you’ll want to add an additional 250 ml of water for a grand total of 750 ml of water. My personal preference is 500 ml total of water which makes for a rich coffee creamer that’s closer to a half-and-half if you’re comparing this to dairy.</li>
</ol>

<p>If you use a Vitamix to make this you may want to wait until your mixture has sat overnight in the refrigerator before deciding if you want more water, more sugar, etc. 5 minutes in a Vitamix is enough friction that it will start to heat a little and it’ll taste different warm than it will when it’s cooled down.</p>

<p>Store the creamer in airtight jars in the fridge and shake it well before using as the vanilla beans tend to settle to the bottom. You’ll want to use this up in around 10 days, give or take, as there’s no preservatives in this so it’ll eventually turn to the dark side (as in it will mold and you’ll have an awful-tasting cup of coffee on your hands and no one needs that kind of negativity in their life).</p>]]></content><author><name>Noah Krohn</name></author><category term="Blog" /><category term="Recipe" /><category term="vegan" /><category term="beverages" /><summary type="html"><![CDATA[Tasty cashew cream coffee creamer]]></summary></entry><entry><title type="html">Mac and Cheese</title><link href="https://monkeyguild.org/posts/mac-and-cheese/" rel="alternate" type="text/html" title="Mac and Cheese" /><published>2025-05-02T00:00:00-04:00</published><updated>2025-05-02T00:00:00-04:00</updated><id>https://monkeyguild.org/posts/mac-and-cheese</id><content type="html" xml:base="https://monkeyguild.org/posts/mac-and-cheese/"><![CDATA[<p>This is not some grand invention of mine or some closely guarded family secret. Unsurprisingly, French chefs figured this one out a long time ago, specifically the Parisian restaurant Le Grand Véfour, where “Sauce Mornay” was introduced to the public in its current form. Mornay is a béchamel sauce with grated cheese added and it’s exactly what you’re looking for to create an amazing cheese sauce for your pasta.</p>

<p><strong>Cheese Sauce</strong></p>
<ul>
  <li>8 ounces (227 grams) extra sharp white cheddar (buy a brick of cheddar and shred it yourself, trust me on this one)</li>
  <li>1/2 stick (57 grams) unsalted butter</li>
  <li>1/4 cup (32 grams) All-Purpose flour</li>
  <li>2 cups (475 ml) milk (I use low-fat milk but anything other than skim is fine)</li>
  <li>Salt and pepper to taste</li>
</ul>

<ol>
  <li>Start your pasta of choice cooking while you work on the sauce, or if you’re not a speedy cook you’ll want to get the sauce prepped first so you can get the freshly cooked pasta into the sauce as soon as it’s ready. I like the classics so I generally use good quality elbow macaroni, but really anything that holds the sauce well will do nicely. If you’re looking for suggestions - shells, penne, and rigatoni are all excellent options. You’ll need a pound (453 grams) of pasta for this recipe.</li>
  <li>Melt the butter over medium heat, don’t allow it to brown</li>
  <li>As soon as the butter is melted, add the flour while whisking to avoid making lumps. You’re looking for a smooth, pale roux.</li>
  <li>Let the roux cook for 2-3 minutes, you’re not looking for any color, you’re just looking to not have raw flour in your sauce.</li>
  <li>Pour the milk in all at once while whisking vigorously. I know, a lot of folks will have you warm it first, drizzle it, etc. It’s not needed, I promise, just whisk it while you pour and you’re good to go.</li>
  <li>Let this cook for 4-5 minutes on medium heat until it starts to bubble and thicken, whisking it frequently.</li>
  <li>Once it is thickened slightly, add all of the shredded cheese at once and cook the sauce until the cheese is fully melted and incorporated</li>
  <li>If you have enough room in your saucepan, add the pasta to the pan with your cheese sauce and stir just until everything is well coated. If not, add the pasta to a large bowl and pour the sauce over it and do your stirring there.</li>
  <li>Enjoy! Let me know what you think or what spices/cheeses/flavors you add to yours to make it your perfect mac and cheese.</li>
</ol>

<p><strong>Notes</strong></p>
<ol>
  <li>If you end up with lumps in either the béchamel or the mornay, don’t panic. You can generally fix this by using an immersion blender. If you don’t have one, a regular blender will work just fine, just work in batches since the liquid will be hot.</li>
  <li>Other cheese work great here, especially parmesan and gruyere. You can also combine multiple types of cheeses for your own flavor profile.</li>
  <li>This will make a fairly thick cheese sauce which not everyone likes, so you’ll want to add more or less milk to the cheese sauce to get it to the consistency you like. Just don’t make it too thin or it won’t really adhere to your pasta.</li>
  <li>Too much fat and too little fat from your dairy are going to lead to not great results. You can definitely do this with whole milk or evaporated milk if you want a very rich sauce. Skim milk tends to make the sauce watery and the mouth feel is off. Conversely, half and half or heavy cream would give you an unholy marriage of a pudding and a cheese sauce that will be way too fatty for anyone to really enjoy. Low-fat milk is my family’s preference but you do you my friend, it’s your sauce now.</li>
  <li>You can make this vegan and it’s pretty fantastic. I recommend using rice milk for the béchamel, along with vegan margarine. If you go this route, you’ll want to increase the margarine and flour to around 6 tablespoons of each as rice milk has very little, if any, fat content. Other milks can work but their flavors might take over and rice milk is neutral which is why I use it. For the cheese substitute, you’ll want to use something that melts like Daiya shreds. Just bear in mind that this sauce is much more likely to break on you and frequently requires a blender to make it smooth so I’d recommend having one on hand if you’re going the vegan route.</li>
  <li>For storing the leftovers (if there are any) I’d recommend thinning the sauce slightly with a bit of milk before putting it away. It just makes separating it later a lot easier. To reheat it, put a little milk in a sauce pan along with the leftover noodles and gently swirl it around in the milk over medium heat. It’ll start to break apart your brick of noodles and melt back into a sauce. Because you’re diluting it with milk you might need to add a little more cheese and a little more salt to get it back to the same flavor profile again but the consistency is almost perfect. Microwaves and dairy tend to not mix well unless you’re super careful so I tend to do this on the stove but again, you do you.</li>
  <li>You definitely <em>can</em> use pre-shredded cheese but there’s the possibility it will mess with the consistency of the sauce. Bagged cheese comes coated with a starch to keep it from clumping which also acts as a thickener when it’s added to a sauce. If you do decide to use pre-shredded cheese, just add a little more milk to compensate for the additional starch.</li>
</ol>]]></content><author><name>Noah Krohn</name></author><category term="Blog" /><category term="Recipe" /><category term="dinner" /><category term="pasta" /><category term="cheese" /><summary type="html"><![CDATA[The best mac and cheese you'll ever eat]]></summary></entry><entry><title type="html">What’s on Your Mind?</title><link href="https://monkeyguild.org/posts/what-is-on-your-mind/" rel="alternate" type="text/html" title="What’s on Your Mind?" /><published>2025-04-18T00:00:00-04:00</published><updated>2025-04-18T00:00:00-04:00</updated><id>https://monkeyguild.org/posts/what-is-on-your-mind</id><content type="html" xml:base="https://monkeyguild.org/posts/what-is-on-your-mind/"><![CDATA[<p>I’ve been staring at the text box on Mastodon for about a week now. “What’s on your mind?” it says to me. I honestly don’t know what to say that anymore. I recognize that the goal of everything happening right now is to erode any ability to fight back and to flood you with so many moving targets that it’s impossible to concentrate. What angers me the most is it feels like it’s working.</p>

<p>During the 2016 - 2020 presidency, I made it about halfway through before I had to put a moratorium on checking the News daily. Reading the News became exhausting. That was when there was little progression of these terrible ideologies because of the rampant incompetence of the president and the incompetency of those with whom he chose to surround himself. Now it’s seeming like bigotry has united these forces and while they’re still incompetent, they’re managing to succeed and people I care about are losing their right to live as they choose.</p>

<p>So here we are on a Friday. It’s been 88 days, 10 hours, 44 minutes (at the time I am writing this) since we started round two of the American dumpster fire and I’m already exhausted by all of this. I’m terrified that November of last year represents the last time my vote was counted in the United States. I’m terrified that I might have to move. I’m terrified that by the time I decide it’s gone too far that I won’t be able to move and that my family and I will be stuck trying to survive in an increasingly hostile environment.</p>

<p>I say all that recognizing that I’m not even among the first targets. I’m white, cisgender, middle-aged, reasonably well off, reasonably well paid, etc. My team at work though, I’m terrified I’m going to lose them. Only one of them has a green card and the rest are here on H1B visas. I’m terrified they’ll go home to renew their work visa and will not be allowed back into the US. I’m terrified that as progressive as my company is that they’re still a business and that if there’s substantial money to be made by following the hateful ideologies of the current presidency that they’ll forget how progressive they are and cave to those demands. They’ve shown no sign of that, they say the right things, and above all they have a history of supporting progressive initiatives well before they existed as buzzwords and talking points.</p>

<p>I try to remain hopeful and focus on the wins. But we’re not even a hundred days in and I’m exhausted. I hate this. I will continue to fight and resist until I can’t any more. I will likely try to make myself feel better by playing mindless video games, eating food that’s horrible for me, and looking at photos of adorable baby animals until I feel like the world is going to be OK and that we’re going to come through the other side of this and things will improve.</p>

<p>But I’m still terrified.</p>

<p>That’s what’s on my mind.</p>]]></content><author><name>Noah Krohn</name></author><category term="Blog" /><category term="Politics" /><category term="politics" /><category term="fascism" /><category term="united states" /><summary type="html"><![CDATA[Thoughts about everything going on in the US]]></summary></entry><entry><title type="html">GitLab Publishing Pipeline for Dreamhost, Part Two</title><link href="https://monkeyguild.org/posts/gitlab-pipeline-for-dreamhost-part-two/" rel="alternate" type="text/html" title="GitLab Publishing Pipeline for Dreamhost, Part Two" /><published>2025-04-16T00:00:00-04:00</published><updated>2025-04-16T00:00:00-04:00</updated><id>https://monkeyguild.org/posts/gitlab-pipeline-for-dreamhost-part-two</id><content type="html" xml:base="https://monkeyguild.org/posts/gitlab-pipeline-for-dreamhost-part-two/"><![CDATA[<p>Welcome back! In <a href="https://monkeyguild.org/posts/gitlab-pipeline-for-dreamhost/">Part One</a> we looked at a pretty simple deployment pipeline that sacrificed speed and efficiency for ease of reading and understanding. In this article we’re going to look at optimizing this for speed and efficiency.</p>

<h1 id="oooo-are-we-using-alpine-linux">Oooo! Are We Using Alpine Linux?!</h1>
<p>Sorry my friend, but I generally don’t use Alpine Linux and it largely boils down to <code class="language-plaintext highlighter-rouge">musl</code>. Alpine implemented its own C standard library called <code class="language-plaintext highlighter-rouge">musl</code> which is lighter weight than <code class="language-plaintext highlighter-rouge">glibc</code> (which is what most of the Linux OS that have been around longer are using). I’ve run into numerous issues with code throwing segfaults on Alpine but compiles perfectly fine on Ubuntu, Debian, RHEL, etc. I’m not here to poo-poo anyone’s OS choices, and Alpine is a really great lightweight OS. All that said, it’s been the source of enough problems for me personally though that I stopped using it and went back to using Debian-derivatives.</p>

<h1 id="so-whats-the-plan-then">So what’s the plan then?</h1>
<p>I thought I might walk you through how to build your own Docker container that you can then leverage to build your static Jekyll site. If you want to skip straight to the code you can head over to my <a href="https://gitlab.com/noah.krohn/jekyll-builder">GitLab Repository</a> to poke around.</p>

<h1 id="building-the-docker-image">Building the Docker image</h1>
<p>If you strip everything else out of my code repo that isn’t documentation-related, you’re left with just a Dockerfile and a GitLab pipeline. If you want a good intro on writing a Dockerfile, the Docker Docs has a great <a href="https://docs.docker.com/get-started/docker-concepts/building-images/writing-a-dockerfile/">intro</a>. But let’s pick apart mine:</p>
<div class="language-dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="rouge-code"><pre><span class="k">FROM</span><span class="w"> </span><span class="s">debian:bookworm</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="s">base</span>
<span class="k">RUN </span><span class="nb">echo</span> <span class="s1">'APT::Install-Suggests "0";'</span> <span class="o">&gt;&gt;</span> /etc/apt/apt.conf.d/00-jekyll-builder
<span class="k">RUN </span><span class="nb">echo</span> <span class="s1">'APT::Install-Recommends "0";'</span> <span class="o">&gt;&gt;</span> /etc/apt/apt.conf.d/00-jekyll-builder
<span class="k">RUN </span><span class="nv">DEBIAN_FRONTEND</span><span class="o">=</span>noninteractive apt-get update
<span class="k">RUN </span><span class="nv">DEBIAN_FRONTEND</span><span class="o">=</span>noninteractive apt-get upgrade <span class="nt">-y</span>
<span class="k">RUN </span><span class="nv">DEBIAN_FRONTEND</span><span class="o">=</span>noninteractive apt-get <span class="nb">install</span> <span class="nt">-y</span> ruby-full build-essential curl rsync openssh-client git
<span class="k">RUN </span>curl <span class="nt">-fsSL</span> https://deb.nodesource.com/setup_22.x <span class="nt">-o</span> nodesource_setup.sh
<span class="k">RUN </span>bash nodesource_setup.sh
<span class="k">RUN </span><span class="nv">DEBIAN_FRONTEND</span><span class="o">=</span>noninteractive apt-get <span class="nb">install</span> <span class="nt">-y</span> nodejs
<span class="k">RUN </span>gem <span class="nb">install </span>rubygems-update
<span class="k">RUN </span>update_rubygems
<span class="k">RUN </span>gem <span class="nb">install </span>jekyll bundler
</pre></td></tr></tbody></table></code></pre></div></div>
<p>Line by line I’m doing the following:</p>
<ol>
  <li>Telling Docker that I want to use the current (at the time of this article) version of Debian, which is Debian Bookworm as my base image. This pins me to that version of Debian but will continue to pull newer versions of Bookworm as that tag is updated</li>
  <li>Setting APT to not install suggested packages as we’re trying to keep things as small as possible.</li>
  <li>Setting APT to not install recommended packages as we’re trying to keep things as small as possible.</li>
  <li>Running apt update but telling it that this is a non-interactive shell (since I’m running in a pipeline).</li>
  <li>Running apt upgrade but telling it that this is a non-interactive shell (since I’m running in a pipeline).</li>
  <li>Install the needed apt packages for Jekyll.</li>
  <li>Download the current (at the time of this article) repo setup script for the LTS version of NodeJS.</li>
  <li>Run the NodeJS repo setup script.</li>
  <li>Install NodeJS.</li>
  <li>Install the rubygems-update package so I can pull any critical updates to gems more easily.</li>
  <li>Install Jekyll and Bundler</li>
</ol>

<h1 id="running-the-gitlab-pipeline">Running the GitLab Pipeline</h1>
<p>The pipeline now takes the Dockerfile, builds it on a runner, and then pushes it to Docker Hub</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
</pre></td><td class="rouge-code"><pre><span class="nn">---</span>
<span class="na">stages</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">build</span>

<span class="na">default</span><span class="pi">:</span>
  <span class="na">image</span><span class="pi">:</span> <span class="s">docker:28</span>
  <span class="na">services</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">docker:28-dind</span>
      <span class="na">variables</span><span class="pi">:</span>
        <span class="na">HEALTHCHECK_TCP_PORT</span><span class="pi">:</span> <span class="s2">"</span><span class="s">2375"</span>
  <span class="na">before_script</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">docker info</span>

<span class="na">variables</span><span class="pi">:</span>
  <span class="na">DOCKER_HOST</span><span class="pi">:</span> <span class="s">tcp://docker:2375</span>
  <span class="na">DOCKER_TLS_CERTDIR</span><span class="pi">:</span> <span class="s2">"</span><span class="s">"</span>

<span class="na">build</span><span class="pi">:</span>
  <span class="na">stage</span><span class="pi">:</span> <span class="s">build</span>
  <span class="na">tags</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">shared</span>
  <span class="na">allow_failure</span><span class="pi">:</span> <span class="kc">false</span>
  <span class="na">rules</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">if</span><span class="pi">:</span> <span class="s">$CI_COMMIT_BRANCH == 'main'</span>
  <span class="na">before_script</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">echo ${DOCKER_PAT} | docker login --username ${DOCKER_USER} --password-stdin</span>
    <span class="pi">-</span> <span class="s">docker info</span>
  <span class="na">script</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">docker build -t "${DOCKER_USER}/${DOCKER_PROJECT}" .</span>
    <span class="pi">-</span> <span class="s">docker push "${DOCKER_USER}/${DOCKER_PROJECT}"</span> 
</pre></td></tr></tbody></table></code></pre></div></div>
<p>In this case since I am using a runner that is installed in a Kubernetes cluster, I need to use both the docker image as well is the “Docker In Docker (DIND)” image as well. So the front matter sets the port the system can use to verify a Docker Engine is available and then the rest is the build pipeline. This says if you push to the main branch, login to the Docker Hub using a PAT, pull Docker Info to record the version information as spew, and then move on to building. The build is all of two commands - build the container, push it to the Hub. This setup will keep a “latest” version constantly moving forwards, however best practices should have you tagging your images with version information. Because this was a simple tool I was building just for my own use (and to write this article) I did not do so, but there are many articles out there on tagging taxonomy for your Docker images that I strongly advise you follow if you plan to implement anything like this in a production capacity.</p>]]></content><author><name>Noah Krohn</name></author><category term="Blog" /><category term="Technology" /><category term="gitlab" /><category term="dreamhost" /><category term="iac" /><category term="docker" /><summary type="html"><![CDATA[Build a custom Docker image to speed up the deployment of your Jekyll site]]></summary></entry><entry><title type="html">GitLab Publishing Pipeline for Dreamhost, Part One</title><link href="https://monkeyguild.org/posts/gitlab-pipeline-for-dreamhost/" rel="alternate" type="text/html" title="GitLab Publishing Pipeline for Dreamhost, Part One" /><published>2025-03-27T00:00:00-04:00</published><updated>2025-03-27T00:00:00-04:00</updated><id>https://monkeyguild.org/posts/gitlab-pipeline-for-dreamhost</id><content type="html" xml:base="https://monkeyguild.org/posts/gitlab-pipeline-for-dreamhost/"><![CDATA[<p>I’ve been leveraging <a href="https://www.dreamhost.com/">Dreamhost</a> to host my sites for a really long time now and they’ve never done wrong by me so I’ve no intention of moving to anything else, even though I get free hosting solutions with my current SRE gig. But, I did want to take advantage of an IAC pipeline to publish updates to my site and that’s a pretty easy process if you’re leveraging Dreamhost for hosting and is possible even with the free version of GitLab.</p>

<h1 id="prerequisites">Prerequisites</h1>
<p>First, you’ll need a user that can SSH into your hosting directory:</p>
<ol>
  <li>Login to your <a href="https://panel.dreamhost.com">Dreamhost Panel</a></li>
  <li>Click Websites -&gt; Manage Websites -&gt; “Manage” (next to your site URL) -&gt; Content -&gt; Login Info</li>
  <li>Make sure “Secure Shell Access” is enabled for your user (change the password if you’ve forgotten the login info or never logged in this way before)</li>
  <li>Login as the user and create an SSH key you’re going to use for your GitLab pipeline
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
</pre></td><td class="rouge-code"><pre><span class="nb">mkdir</span> <span class="nt">-p</span> ~/.ssh
ssh-keygen <span class="nt">-q</span> <span class="nt">-t</span> ed25519 <span class="nt">-C</span> <span class="s2">"GitLab Dreamhost Pipeline"</span> <span class="nt">-f</span> ~/.ssh/id_ed25519 <span class="nt">-N</span> <span class="s1">''</span>
<span class="nb">cat</span> ~/.ssh/id_ed25519.pub <span class="o">&gt;&gt;</span> ~/.ssh/authorized_keys
<span class="nb">chown </span>0600 ~/.ssh/authorized_keys
<span class="nb">cat</span> ~/.ssh/id_ed25519
<span class="nb">cat</span> ~/.ssh/id_ed25519.pub
</pre></td></tr></tbody></table></code></pre></div>    </div>
  </li>
  <li>Now you’ll want to set these up as files in your GitLab pipeline so head over to your GitLab project and click Settings -&gt; CI/CD</li>
  <li>Expand “Variables” and create the following variables of type “File” with the following contents
    <ul>
      <li>Key:ssh_key_private, Value: Results of <code class="language-plaintext highlighter-rouge">cat ~/.ssh/id_ed25519</code></li>
      <li>Key:ssh_key_public, Value: Results of <code class="language-plaintext highlighter-rouge">cat ~/.ssh/id_ed25519.pub</code></li>
      <li>Key:ssh_user, Value: Your Dreamhost SSH user you used to access the system and run those commands</li>
    </ul>
  </li>
</ol>

<h1 id="the-pipeline">The Pipeline</h1>
<p>The pipeline itself is pretty simple. At this point you’re just trying to rsync data from your runner to your Dreamhost server. Let’s say you’re using this to push your static Jekyll website onto the server (since that’s what I’m using it for). There are a lot of ways you can make the below pipeline faster and more elegant but I tried to write this in a way that someone new to this might understand, so I’m sacrificing speed and efficiency for ease of both reading and understanding. Let’s just cover the actual pushing of the data itself, though for best practices you’d want to have other stages to your pipeline that perform checks and have this run when merging your data back into your ‘main’ branch. I’ve added echo commands into the pipleine so you’ll understand what each piece is doing but just shoot me an email or look me up on Mastodon if you have questions, I’m always happy to help folks.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
</pre></td><td class="rouge-code"><pre><span class="nn">---</span>
<span class="na">stages</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">deploy</span>

<span class="na">variables</span><span class="pi">:</span>
  <span class="na">SERVER_NAME</span><span class="pi">:</span> <span class="s2">"</span><span class="s">&lt;replace</span><span class="nv"> </span><span class="s">with</span><span class="nv"> </span><span class="s">your</span><span class="nv"> </span><span class="s">server</span><span class="nv"> </span><span class="s">name&gt;"</span>
  <span class="na">RSYNC_USER</span><span class="pi">:</span> <span class="s2">"</span><span class="s">${ssh_user}@${SERVER_NAME}"</span>

<span class="na">deploy-site</span><span class="pi">:</span>
  <span class="na">stage</span><span class="pi">:</span> <span class="s">deploy</span>
  <span class="na">image</span><span class="pi">:</span> <span class="s">ubuntu</span>
  <span class="na">allow_failure</span><span class="pi">:</span> <span class="kc">false</span>
  <span class="na">rules</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">if</span><span class="pi">:</span> <span class="s">$CI_COMMIT_BRANCH == 'main'</span>
  <span class="na">before_script</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">echo "First running apt to update inside the container and then install a few essentials needed for the data..."</span>
    <span class="pi">-</span> <span class="s">apt-get update</span>
    <span class="pi">-</span> <span class="s">apt-get upgrade -y</span>
    <span class="pi">-</span> <span class="s">apt-get install -y curl git-all build-essential gcc jq python3.12-venv python3.12 openssh-client rsync</span>
    <span class="pi">-</span> <span class="s">echo "Using homebrew as an easily understandable method to install programs into user context..."</span>
    <span class="pi">-</span> <span class="s">/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"</span>
    <span class="pi">-</span> <span class="s">echo &gt;&gt; /root/.bashrc</span>
    <span class="pi">-</span> <span class="s">echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' &gt;&gt; /root/.bashrc</span>
    <span class="pi">-</span> <span class="s">eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"</span>
    <span class="pi">-</span> <span class="s">echo "Installing gcc, NodeJS, and Ruby using Homebrew..."</span>
    <span class="pi">-</span> <span class="s">brew install gcc ruby node</span>
    <span class="pi">-</span> <span class="s">echo "Now we're mirroring what your SSH directory likely looks like on your server and we're copying files to where they would normally live..."</span>
    <span class="pi">-</span> <span class="s">mkdir -p ~/.ssh</span>
    <span class="pi">-</span> <span class="s">chmod 700 ~/.ssh</span>
    <span class="pi">-</span> <span class="s">cp ${ssh_key_private} ~/.ssh/id_25519</span>
    <span class="pi">-</span> <span class="s">cp ${ssh_key_public} ~/.ssh/id_25519.pub</span>
    <span class="pi">-</span> <span class="s">echo "Disabling host key checking so we don't have to worry about popups related to trusting a new connection..."</span>
    <span class="pi">-</span> <span class="s">echo -e "Host *\\n\\tStrictHostKeyChecking no\\n\\n" &gt; ~/.ssh/config</span>
    <span class="pi">-</span> <span class="s">echo "Fixing the permissions on the SSH files so your SSH Agent doesn't kvetch about it..."</span>
    <span class="pi">-</span> <span class="s">chmod 0644 ~/.ssh/id_25519.pub</span>
    <span class="pi">-</span> <span class="s">chmod 0400 ~/.ssh/id_25519</span>
    <span class="pi">-</span> <span class="s">chmod 0600 ~/.ssh/config</span>
    <span class="pi">-</span> <span class="s">echo "Lastly we're adding the keys to the SSH Agent and telling it to test the connection to your Dreamhost server"</span> 
    <span class="pi">-</span> <span class="s">eval $(ssh-agent -s)</span>
    <span class="pi">-</span> <span class="s">ssh-add ~/.ssh/id_25519</span>
    <span class="pi">-</span> <span class="s">ssh-keyscan ${SERVER_NAME}</span>
  <span class="na">script</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">echo "We're in the home stretch now, just setting the paths for gems to install..."</span>
    <span class="pi">-</span> <span class="s">export GEM_HOME="$HOME/gems"</span>
    <span class="pi">-</span> <span class="s">export PATH="$HOME/.local/share/gem/ruby/3.4.0/bin:$PATH"</span>
    <span class="pi">-</span> <span class="s">echo "Installing Jekyll and Bundler as we need those for the Jekyll build"</span>
    <span class="pi">-</span> <span class="s">gem install --user-install jekyll bundler</span>
    <span class="pi">-</span> <span class="s">echo "Some Jekyll themes also require you build your NodeJS files as well so this is needed on mine but might not be on yours..."</span>
    <span class="pi">-</span> <span class="s">npm install</span>
    <span class="pi">-</span> <span class="s">npm run build</span>
    <span class="pi">-</span> <span class="s">echo "Almost there, just building the site with Jekyll..."</span>
    <span class="pi">-</span> <span class="s">bundle install</span>
    <span class="pi">-</span> <span class="s">jekyll build</span>
    <span class="pi">-</span> <span class="s">echo "And last but not least, we push the contents of your _site folder to your Dreamhost server using rsync..."</span>
    <span class="pi">-</span> <span class="s">rsync -ave ssh ./_site/ ${RSYNC_USER}:/home/${ssh_user}/${SERVER_NAME}</span>
    <span class="pi">-</span> <span class="s">echo "We're done!"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h1 id="next-steps">Next Steps</h1>
<p>This is pretty bare bones and like I said you would probably want to have other stages to this that run for your unprotected branches to test your code and make sure you’re not doing anything potentially unsafe with it. There are also <strong>tons</strong> of ways to make this faster and more efficient like using the Alpine or Jekyll images that are provided by GitLab but like I said, I wanted folks who are new to this process to understand it and Ubuntu is what a lot of folks are using when they get started in their Linux journey. I hope this helps a few folks but like I said above, if you have any questions then feel free to shoot me a message and I’m happy to answer whatever questions I can.</p>

<h1 id="i-feel-the-needthe-need-for-speed">I feel the need…the need for speed!</h1>
<p>It’s pretty slow right now isn’t it? Like I said, I sacrificed a lot of speed and efficiency to make the nuts and bolts of this easier to understand and I find Ubuntu seems to be where folks new to Linux tend to land. But let’s head over to <a href="https://monkeyguild.org/posts/gitlab-pipeline-for-dreamhost-part-two/">Part Two</a> and see what we can do about that.</p>]]></content><author><name>Noah Krohn</name></author><category term="Blog" /><category term="Technology" /><category term="gitlab" /><category term="dreamhost" /><category term="iac" /><summary type="html"><![CDATA[An easy to follow CI/CD example on how to deploy a static website to Dreamhost using GitLab]]></summary></entry><entry><title type="html">And We’re Back!</title><link href="https://monkeyguild.org/posts/and-we-are-back/" rel="alternate" type="text/html" title="And We’re Back!" /><published>2025-03-25T00:00:00-04:00</published><updated>2025-03-25T00:00:00-04:00</updated><id>https://monkeyguild.org/posts/and-we-are%20back</id><content type="html" xml:base="https://monkeyguild.org/posts/and-we-are-back/"><![CDATA[<h1 id="its-alive-and-its-static">It’s alive and it’s static.</h1>
<p>That’s right, I’ve joined the cool kids club and rebuilt the site using Jekyll. I’d been wanting to redo this as a static site for a long time now but hadn’t taken the time to do so. But I ripped the bandaid off and tore it all apart and here we are, back and good as new. I’ll be slowly pulling in the posts from my old site over the next few weeks but the look and feel of the site will largely remain like this.</p>]]></content><author><name>Noah Krohn</name></author><category term="Blog" /><category term="Introduction" /><category term="intro" /><summary type="html"><![CDATA[My website is finally back up and running with its shiny new Jekyll theme.]]></summary></entry><entry><title type="html">Everything Bagels</title><link href="https://monkeyguild.org/posts/everything-bagels/" rel="alternate" type="text/html" title="Everything Bagels" /><published>2012-10-23T00:00:00-04:00</published><updated>2012-10-23T00:00:00-04:00</updated><id>https://monkeyguild.org/posts/everything-bagels</id><content type="html" xml:base="https://monkeyguild.org/posts/everything-bagels/"><![CDATA[<p><strong>Everything Bagel Topping</strong>
2 teaspoons poppy seeds<br />
2 teaspoons sesame seeds<br />
2 teaspoons dried garlic flakes<br />
2 teaspoons dried onion flakes<br />
1 teaspoon kosher salt</p>

<p><strong>Bagel Recipe</strong>
2 teaspoons of active dry yeast<br />
1 ½ tablespoons of granulated sugar<br />
1 ¼ cups of warm water<br />
4 cups bread flour<br />
1 ½ teaspoons of salt</p>

<ol>
  <li>Add the sugar to the water, stir to dissolve then add the yeast. Let it sit for five minutes until the yeast has activated.</li>
  <li>Add everything else. Beat in a stand mixer with a dough hook attachment for 10 minutes</li>
  <li>Remove the dough from the bowl, oil it lightly, then return it to the bowl and let it rise for one hour</li>
  <li>Cut the dough into 8 equal pieces. Shape them into rounds, then use your finger to make a whole in the center and stretch it until you have a roughly bagel shaped piece of dough</li>
  <li>While doing this, start a pot of water boiling. Let the dough rounds stand for 20 minutes covered in a warm place</li>
  <li>Preheat the oven to 425ºF. Boil each of the bagels for two minutes on each side (as many as you can fit in the pot comfortably)</li>
  <li>As soon as you remove them from the water, sprinkle them with the seasoning mixture. Do it quickly as they dry out fast and you need the moisture to make it stick.</li>
  <li>Bake them for 20 minutes</li>
</ol>]]></content><author><name>Noah Krohn</name></author><category term="Blog" /><category term="Recipe" /><category term="vegan" /><category term="baking" /><summary type="html"><![CDATA[A simple, easy-to-follow, everything bagel recipe.]]></summary></entry><entry><title type="html">Vanilla Bean Cake</title><link href="https://monkeyguild.org/posts/vanilla-bean-cake/" rel="alternate" type="text/html" title="Vanilla Bean Cake" /><published>2012-07-20T00:00:00-04:00</published><updated>2012-07-20T00:00:00-04:00</updated><id>https://monkeyguild.org/posts/vanilla-bean-cake</id><content type="html" xml:base="https://monkeyguild.org/posts/vanilla-bean-cake/"><![CDATA[<p>I did some baking tonight. The completed cake ended up delicious but was outrageously messy to decorate. Apparently there is such a thing as too much filling where cake is concerned. I was however incredibly pleased with how the yellow cake itself came out. This will give you two perfect sized thin-ish layers, but if you’re going for a typical height cake you’ll want to increase the amounts here by 50% and you’ll need a third cake tin. I filled mine with a blueberry caramel…at least I think that’s what I’d call it. I made a thin caramel and then added blueberries and vanilla bean. Then I frosted it with a lemon and vanilla buttercream. The cake is super sweet though so I think all the frosting and filling was overkill (seriously, my pancreas hurts) – If you top it maybe with some balsamic macerated strawberries (lightly sweetened) that would have been ideal. Overall though, I’m really the most satisfied with the cake portion of it. I’ve been monkeying with recipes for years to get a light, but moist yellow cake and I finally got it.</p>

<ol>
  <li>Mix 1 tsp baking soda with 1 cup sour cream and set aside</li>
  <li>Blend 1 stick of butter (softened to room temperature) with 1 cup of white sugar until creamy</li>
  <li>Add 2 eggs, one at a time, to the bowl while the beater is running</li>
  <li>Add the seeds from 3 vanilla beans, and 1 tsp. vanilla and blend well</li>
  <li>Into the wet mixture sift 1 1/2 cups cake flour and 1 1/2 tsp baking powder and fold in gently just to start it combining. I used five or six folding stirs to get this going</li>
  <li>Fold in the sour cream mixture, trying to leave as much air in the mixture as possible</li>
  <li>Bake in two 8-9 inch round cake tins, that have been buttered and floured, at 350 degrees for 20-25 minutes</li>
</ol>]]></content><author><name>Noah Krohn</name></author><category term="Blog" /><category term="Recipe" /><category term="baking" /><category term="dessert" /><summary type="html"><![CDATA[Recipe for a nice dense, vanilla bean cake.]]></summary></entry></feed>