<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Drifting Ruby]]></title><description><![CDATA[A collection of development related articles.]]></description><link>https://blog.driftingruby.com/</link><image><url>https://blog.driftingruby.com/favicon.png</url><title>Drifting Ruby</title><link>https://blog.driftingruby.com/</link></image><generator>Ghost 5.88</generator><lastBuildDate>Thu, 04 Jun 2026 19:19:15 GMT</lastBuildDate><atom:link href="https://blog.driftingruby.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA["Choose Your Own Path"]]></title><description><![CDATA[<p>Remember those &quot;Choose Your Own Adventure&quot; books? I loved those as a kid. For those who don&apos;t know what they are, they&apos;re adventure books that are not meant to be read from front to back. Instead, you read a page or two and then</p>]]></description><link>https://blog.driftingruby.com/choose-your-own-path/</link><guid isPermaLink="false">67b69e5f2e7b780001b5ca01</guid><category><![CDATA[Rails]]></category><category><![CDATA[LLM]]></category><dc:creator><![CDATA[Dave Kimura]]></dc:creator><pubDate>Thu, 20 Feb 2025 03:53:37 GMT</pubDate><media:content url="https://blog.driftingruby.com/content/images/2025/02/image-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.driftingruby.com/content/images/2025/02/image-1.png" alt="&quot;Choose Your Own Path&quot;"><p>Remember those &quot;Choose Your Own Adventure&quot; books? I loved those as a kid. For those who don&apos;t know what they are, they&apos;re adventure books that are not meant to be read from front to back. Instead, you read a page or two and then you&apos;re left with a few choices. Depending on which option you choose, you can flip to a specific page to continue the story. </p><p>Using various LLMs, I was able to dynamically generate my own! The process is fairly simple where you pick the kind of text you want to have generated. Then, background jobs will be kicked off and the content will be generated.</p><p>Here&apos;s a basic overview of how something like this happens.</p><figure class="kg-card kg-image-card"><img src="https://blog.driftingruby.com/content/images/2025/02/CleanShot-2025-02-19-at-22.27.59.png" class="kg-image" alt="&quot;Choose Your Own Path&quot;" loading="lazy" width="704" height="329" srcset="https://blog.driftingruby.com/content/images/size/w600/2025/02/CleanShot-2025-02-19-at-22.27.59.png 600w, https://blog.driftingruby.com/content/images/2025/02/CleanShot-2025-02-19-at-22.27.59.png 704w"></figure><h3 id="prompt-chaining">Prompt Chaining</h3><p>But it&apos;s actually not so simple. The Title Generation is created off of the various prompts that the user initially selected. Then the Narrative Generation will start creating the text for the story. This is a fairly creative step for the LLM and is given a lot of room to do its thing.</p><p>However, the Image Generation step isn&apos;t so simple. You cannot simply give the Narrative to the Image Generator and expect good results. Instead, you need to have a prompt for this LLM to generate a somewhat related image. So, Another call to the generative text LLM is made where it feeds in the narrative and is expecting a prompt response that is formatted for Stable Diffusion.</p><p>The choices option is also very similar where based on the narrative, a few different options are given. The general nature of generative text LLMs is that they are very bad at following instructions. This is especially true with smaller models. In all of the text generations on this project, I&apos;m using an 8b parameter model. That&apos;s super small! The generative text is often chatty and not in a format that can easily be parsed by Ruby. So, instead of trying to design the &quot;perfect&quot; prompt that would work, I make two separate requests. The first request is more on the creative side. I feed in the narrative and with a temperature of <code>0.7</code> I ask for a list of options. I then make another request feeding in the response from the previous one and ask the LLM to format this in an array. Since its context is so small, just my request prompt plus the context of the &quot;actions&quot;, it&apos;s able to handle this fairly well. </p><p>By having multiple prompts and chaining the responses, I&apos;m able to consistently get better results even from a small LLM. Not only this, but it&apos;s a bit more universal in being able to switch models. If I want to test out a different small model, I can and most often the Choices Generation will still work.</p><h3 id="applicationtechnology-used">Application/Technology Used</h3><p>This application is a very vanilla Rails 8 application. I added one gem into the Gemfile that&apos;s outside of the provided defaults. There&apos;s only two controllers since most of the work happens in the background jobs. Since this is a fresh Rails 8 application, SolidQueue is being used for the background worker and SQLite is being used on the database side. Kamal is being used to deploy this to a VM that I&apos;m self-hosting.</p><p>That&apos;s basically it. The entire application is under 1000 lines of code. It&apos;s missing a lot of features like authentication and authorization. It&apos;s also missing some potentially cool features like Inventory tracking, but for a &quot;MVP&quot;, I was fairly happy with the results.</p><figure class="kg-card kg-image-card"><img src="https://blog.driftingruby.com/content/images/2025/02/CleanShot-2025-02-19-at-22.37.02.png" class="kg-image" alt="&quot;Choose Your Own Path&quot;" loading="lazy" width="1015" height="488" srcset="https://blog.driftingruby.com/content/images/size/w600/2025/02/CleanShot-2025-02-19-at-22.37.02.png 600w, https://blog.driftingruby.com/content/images/size/w1000/2025/02/CleanShot-2025-02-19-at-22.37.02.png 1000w, https://blog.driftingruby.com/content/images/2025/02/CleanShot-2025-02-19-at-22.37.02.png 1015w" sizes="(min-width: 720px) 720px"></figure><p>The code is also very simple. The most complicated part of the code is creating the HEREDOC for the prompts. There&apos;s several prompts that go into this as we&apos;ve already talked about. The other bits of the code is fairly simple as well. I&apos;m using the <code>ruby-openai</code> gem to communicate with the LLMs. I&apos;m hosting these locally</p><pre><code>  def generate
    client.chat(
      parameters: {
        model: Rails.configuration.default_model,
        messages: [ { role: &quot;user&quot;, content: @prompt } ],
        temperature: @temperature,
        stream: stream_proc(self)
      }
    )
  end

  private

  def client
    @client ||= OpenAI::Client.new(
      access_token: Rails.configuration.default_ollama_token,
      uri_base: Rails.configuration.default_ollama,
    )
  end</code></pre><p>The real-time streaming of the prompt happens from the <code>stream</code> option that&apos;s passed into the generation. It&apos;s actually quite simple where we basically take and update the existing narrative with the chunked content. This does create a burst of writes to the database, but it&apos;s also the most simple and direct approach. If I had to scale this, I would probably do something a bit different where I simply broadcast appending the new content and store everything in an array. Once the stream is finished then I could update the database with the final results.</p><pre><code>  def stream_proc(message)
    proc do |chunk, _bytesize|
      new_content = chunk.dig(&quot;choices&quot;, 0, &quot;delta&quot;, &quot;content&quot;)
      @scenario.update!(narrative: @scenario.narrative.to_s + new_content.to_s)
    end
  end</code></pre><p>The broadcasting, or live updates, is simply handled by ActionCable and turbo streams. No Javascript necessary. Since this is just a hobby project and the most life it&apos;ll see is the short days of this post coming out, I&apos;m not too worried about performance. But I would basically have this broadcast function in the model as is, but have another broadcast in the <code>stream_proc</code>. However, this also has some potentials of things getting out of sync. Because, if the jobs are processed out of order (which is very likely to happen), then it will essentially cause text to be inserted out of order. You could get around this by broadcasting the whole array joined together as a string and that would have a much better chance of staying together.</p><pre><code>after_save_commit { 
  broadcast_replace_to(
    adventure, 
    target: &quot;scenario_adventure_#{adventure.id}&quot;, 
    partial: &quot;adventures/scenario&quot;, 
    locals: { scenario: self } 
  )
}
</code></pre><h3 id="hardwaregpus">Hardware/GPUs</h3><p>I am using a fairly massive GPU for this task, but it&apos;s really not required. A smaller and significantly less expensive GPU with 16GB of VRAM could do this job all the same. There&apos;s also a lot of options out there if you want to use metered-based APIs or other options. I won&apos;t recommend any right now, because I typically run all of my LLMs in house.</p><figure class="kg-card kg-image-card"><img src="https://blog.driftingruby.com/content/images/2025/02/CleanShot-2025-02-19-at-22.41.21.png" class="kg-image" alt="&quot;Choose Your Own Path&quot;" loading="lazy" width="928" height="481" srcset="https://blog.driftingruby.com/content/images/size/w600/2025/02/CleanShot-2025-02-19-at-22.41.21.png 600w, https://blog.driftingruby.com/content/images/2025/02/CleanShot-2025-02-19-at-22.41.21.png 928w" sizes="(min-width: 720px) 720px"></figure><p>But, if you&apos;re able to get your hands on a RTX 4060 Ti 16GB, you can run quite a bit with the 16GB of VRAM. There&apos;s also the RTX 5070 Ti which is being released on Feb 20th, 2025 (tomorrow as of the time of this writing), but the demand for these cards will be high. I&apos;m a big fan of the consumer cards since I&apos;m able to run what I need on them and I&apos;m not doing anything critical. </p><p>There&apos;s a lot of misconception about the GPUs and what&apos;s required with them. In short, if you&apos;re running Inference (text generation, image generation, voice generation) then you do not need an insanely fast GPU or GPU PCIe lane bandwidth. Once the model is loaded into video memory, it is the bandwidth between the video memory and the graphics processor that will make the biggest difference. All of this meaning... you can use a PCIe 4.0 slot in 4x mode and still be okay. You won&apos;t be running into bottlenecks due to the PCIe lane bandwidth. This is a completely different story if you are fine tuning or training your own model. There is much more exchange that goes on between the GPU and the CPU, so running the full 16x lane becomes much more important.</p><h3 id="wrap-up">Wrap up</h3><p>Here&apos;s the link to the Choose Your Own Path if you want to play around with it. Keep in mind that I am hosting this on my local network and using my GPUs for the inference and image generation, so be kind. I likely won&apos;t keep this project online indefinitely so tinker around with it while you can!</p><p>Choose Your Own Path -&gt; <a href="https://cyop.railsapp.dev/?ref=blog.driftingruby.com" rel="noreferrer">https://cyop.railsapp.dev </a></p>]]></content:encoded></item><item><title><![CDATA[asdf 0.16.x]]></title><description><![CDATA[<p>Recently, there was an update to <a href="https://github.com/asdf-vm/asdf?ref=blog.driftingruby.com" rel="noreferrer"><code>asdf</code></a> which broke my install. I experienced this on macOS, Linux and WSL (which is basically Linux in Windows). This issue was due to <code>asdf</code> being rewritten in GoLang so a lot changed. For those who don&apos;t know what <code>asdf</code> is, you</p>]]></description><link>https://blog.driftingruby.com/asdf-0-16-x/</link><guid isPermaLink="false">67ab7d81f181770001d5426f</guid><category><![CDATA[Development]]></category><dc:creator><![CDATA[Dave Kimura]]></dc:creator><pubDate>Tue, 11 Feb 2025 17:00:05 GMT</pubDate><media:content url="https://blog.driftingruby.com/content/images/2025/02/CleanShot-2025-02-11-at-11.59.23.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.driftingruby.com/content/images/2025/02/CleanShot-2025-02-11-at-11.59.23.png" alt="asdf 0.16.x"><p>Recently, there was an update to <a href="https://github.com/asdf-vm/asdf?ref=blog.driftingruby.com" rel="noreferrer"><code>asdf</code></a> which broke my install. I experienced this on macOS, Linux and WSL (which is basically Linux in Windows). This issue was due to <code>asdf</code> being rewritten in GoLang so a lot changed. For those who don&apos;t know what <code>asdf</code> is, you can probably just ignore this article. But, basically, it is a runtime version manager, so you can manage various versions of Python, Node, Ruby, Rust, GoLang and more. I use to use <code>rvm</code> for many years, but as my needs grew, I looked for other toolings and landed with <code>asdf</code>.</p><p>You can read about the releases notes <a href="https://github.com/asdf-vm/asdf/releases?ref=blog.driftingruby.com" rel="noreferrer">here</a> and <a href="https://asdf-vm.com/guide/upgrading-to-v0-16.html?ref=blog.driftingruby.com#breaking-changes" rel="noreferrer">upgrade guide here</a>.</p><p>But, to sum up the major changes that I experienced and how I upgraded from <code>&lt; 0.16.x</code> to <code>0.16.x</code>, keep reading.</p><h2 id="fixing-my-install">Fixing my install</h2><p>On my macOS computer, I normally run <code>brew upgrade</code> and most often, it doesn&apos;t cause any problems. I like finding out these kinds of issues early on so that I can address them before I&apos;m blindsided when working on something critical. So, I make it a habit to only run updates when there is a critical vulnerability patch or if I know that there is time to troubleshoot any kind of issues. </p><p>Thankfully, this was one of those routine maintenance times because I noticed that after I upgraded to <code>asdf</code>, my Ruby Interpreter was no longer working as expected.</p><p></p><figure class="kg-card kg-image-card"><img src="https://blog.driftingruby.com/content/images/2025/02/image.png" class="kg-image" alt="asdf 0.16.x" loading="lazy" width="1364" height="638" srcset="https://blog.driftingruby.com/content/images/size/w600/2025/02/image.png 600w, https://blog.driftingruby.com/content/images/size/w1000/2025/02/image.png 1000w, https://blog.driftingruby.com/content/images/2025/02/image.png 1364w" sizes="(min-width: 720px) 720px"></figure><p>The first hint that something was wrong with <code>asdf</code> was an error message when I launched a new terminal. Side note: Typically, I do not modify my <code>.bashrc</code> or <code>.zshrc</code> files, but instead will put all of my config in a <code>.devrc</code> as you see in the image below. In my <code>.zshrc</code> file, I&apos;ll source this dot file with <code>source ~/.devrc</code>.</p><figure class="kg-card kg-image-card"><img src="https://blog.driftingruby.com/content/images/2025/02/CleanShot-2025-02-11-at-11.09.48@2x.png" class="kg-image" alt="asdf 0.16.x" loading="lazy" width="1364" height="638" srcset="https://blog.driftingruby.com/content/images/size/w600/2025/02/CleanShot-2025-02-11-at-11.09.48@2x.png 600w, https://blog.driftingruby.com/content/images/size/w1000/2025/02/CleanShot-2025-02-11-at-11.09.48@2x.png 1000w, https://blog.driftingruby.com/content/images/2025/02/CleanShot-2025-02-11-at-11.09.48@2x.png 1364w" sizes="(min-width: 720px) 720px"></figure><p>This error message clearly pointed to an issue with <code>asdf</code> which began my troubleshooting. I first went to see what version of Ruby I was using by default now. To my surprise, it was using the version that ships with macOS instead of my <code>asdf</code> version.</p><figure class="kg-card kg-image-card"><img src="https://blog.driftingruby.com/content/images/2025/02/CleanShot-2025-02-11-at-11.10.15@2x.png" class="kg-image" alt="asdf 0.16.x" loading="lazy" width="1364" height="638" srcset="https://blog.driftingruby.com/content/images/size/w600/2025/02/CleanShot-2025-02-11-at-11.10.15@2x.png 600w, https://blog.driftingruby.com/content/images/size/w1000/2025/02/CleanShot-2025-02-11-at-11.10.15@2x.png 1000w, https://blog.driftingruby.com/content/images/2025/02/CleanShot-2025-02-11-at-11.10.15@2x.png 1364w" sizes="(min-width: 720px) 720px"></figure><p>So, I did some research and found the upgrade guide and release notes that I linked above. There was a lot of information in there so I&apos;m going to give the TLDR version here.</p><p>You&apos;ll need to update your dot file (<code>.zshrc</code>, <code>.bashrc</code>, or <code>.devrc</code>) and remove the original line. Luckily the error message tells you what line this happened on so it was easy enough to spot. But, you&apos;ll then want to add the following:</p><pre><code>export ASDF_DATA_DIR=&quot;$HOME/.asdf&quot;
export PATH=&quot;$ASDF_DATA_DIR/shims:$PATH&quot;</code></pre><figure class="kg-card kg-image-card"><img src="https://blog.driftingruby.com/content/images/2025/02/CleanShot-2025-02-11-at-11.12.23@2x.png" class="kg-image" alt="asdf 0.16.x" loading="lazy" width="1276" height="550" srcset="https://blog.driftingruby.com/content/images/size/w600/2025/02/CleanShot-2025-02-11-at-11.12.23@2x.png 600w, https://blog.driftingruby.com/content/images/size/w1000/2025/02/CleanShot-2025-02-11-at-11.12.23@2x.png 1000w, https://blog.driftingruby.com/content/images/2025/02/CleanShot-2025-02-11-at-11.12.23@2x.png 1276w" sizes="(min-width: 720px) 720px"></figure><p>Once you save the file and close it. It&apos;s best to start up a new terminal. You could always run <code>source .devrc</code> but in some situations, I do not find that to always work. So, let&apos;s just assume that you launch a new terminal and then try to get the Ruby version again.</p><figure class="kg-card kg-image-card"><img src="https://blog.driftingruby.com/content/images/2025/02/CleanShot-2025-02-11-at-11.12.45@2x.png" class="kg-image" alt="asdf 0.16.x" loading="lazy" width="1364" height="638" srcset="https://blog.driftingruby.com/content/images/size/w600/2025/02/CleanShot-2025-02-11-at-11.12.45@2x.png 600w, https://blog.driftingruby.com/content/images/size/w1000/2025/02/CleanShot-2025-02-11-at-11.12.45@2x.png 1000w, https://blog.driftingruby.com/content/images/2025/02/CleanShot-2025-02-11-at-11.12.45@2x.png 1364w" sizes="(min-width: 720px) 720px"></figure><p>Luckily, there&apos;s no errors this time and the Ruby version returns the correct one that I installed via <code>asdf</code>. </p><h2 id="other-tldr-on-the-new-changes-with-asdf">Other TL;DR; on the new changes with <code>asdf</code></h2><p>There are some other things that have changed.</p><pre><code>asdf list-all</code></pre><p>has now become</p><pre><code>asdf list all</code></pre><p>So, the <code>-</code> &apos;s have been removed from the commands.</p><p>Also, I use to be able to set my global versions with a command like</p><pre><code>asdf global ruby 3.4.1</code></pre><p>but this has also changed to</p><pre><code>asdf set --home ruby 3.4.1</code></pre><h2 id="and-thats-pretty-much-it">And that&apos;s pretty much it</h2><p>Luckily this change with <code>asdf</code> didn&apos;t cause an all day problem. It was pretty quick to resolve, but it did take some investigative work. It&apos;s not often that we see a patch version have such a significant impact, but they definitely can as we have so clearly seen in this update.</p><p>I should also say that I&apos;m incredibly grateful to the development community and those who have put time and effort into these version managers. They make life so much easier. The point of this post isn&apos;t to bash on their works at all, but rather to share an experience that I had.</p>]]></content:encoded></item><item><title><![CDATA[Kamal 2.0 Issue with AWS ALB]]></title><description><![CDATA[<p>I created a Github Issue on the <a href="https://github.com/basecamp/kamal/issues/982?ref=blog.driftingruby.com">Kamal Repo</a> for visibility as this problem wasn&apos;t clear. There were definitely hints as to what the problem was, but it wasn&apos;t a straight forward solution. Well, actually, in hindsight, it did seem pretty logical. However, at the time</p>]]></description><link>https://blog.driftingruby.com/kamal-2-0-issue-with-aws-alb/</link><guid isPermaLink="false">66f6153dc724bf0001df1518</guid><category><![CDATA[infrastructure]]></category><category><![CDATA[aws]]></category><category><![CDATA[kamal]]></category><dc:creator><![CDATA[Dave Kimura]]></dc:creator><pubDate>Fri, 27 Sep 2024 02:49:45 GMT</pubDate><media:content url="https://blog.driftingruby.com/content/images/2024/09/kamal.webp" medium="image"/><content:encoded><![CDATA[<img src="https://blog.driftingruby.com/content/images/2024/09/kamal.webp" alt="Kamal 2.0 Issue with AWS ALB"><p>I created a Github Issue on the <a href="https://github.com/basecamp/kamal/issues/982?ref=blog.driftingruby.com">Kamal Repo</a> for visibility as this problem wasn&apos;t clear. There were definitely hints as to what the problem was, but it wasn&apos;t a straight forward solution. Well, actually, in hindsight, it did seem pretty logical. However, at the time when you&apos;re getting that scary image on your screen when you refresh for the first time after an upgrade.</p><figure class="kg-card kg-image-card"><img src="https://blog.driftingruby.com/content/images/2024/09/CleanShot-2024-09-26-at-22.17.36.png" class="kg-image" alt="Kamal 2.0 Issue with AWS ALB" loading="lazy" width="743" height="211" srcset="https://blog.driftingruby.com/content/images/size/w600/2024/09/CleanShot-2024-09-26-at-22.17.36.png 600w, https://blog.driftingruby.com/content/images/2024/09/CleanShot-2024-09-26-at-22.17.36.png 743w" sizes="(min-width: 720px) 720px"></figure><p>Panic can set in and logical thinking can go right out the window.</p><h1 id="upgrade-process">Upgrade Process</h1><p>My upgrade process for Kamal was fairly simple. I tested it out on a local environment to make sure that I understood the new config options and then tried deploying Kamal 2.0 on Drifting Ruby.</p><p>There is a neat feature that I either didn&apos;t realize existed or is new. You can run <code>kamal config</code> locally to see if your config will have the <em>potential</em> to deploy successfully. Basically, it just verifies that there aren&apos;t issues within the config.</p><p>With the introduction of Kamal 2.0, we are no longer using the traefik proxy and instead now using <a href="https://github.com/basecamp/kamal-proxy?ref=blog.driftingruby.com">kamal-proxy</a>. The upgrade process is fairly simple, but does require changing a few things. Once you upgrade Kamal to version 2.0... wait! Before you do this!, you should upgrade to version <code>1.9.0</code> because this version offers a &quot;downgrade&quot; feature that will remove kamal-proxy and replace it back with traefik. This can be critical if you&apos;re in a situation where you need to go backwards and get the site back up and running while you troubleshoot an issue. I know, because I ended up having to do this.</p><p>But, assuming that you&apos;ve successfully deployed <code>1.9.0</code> and you&apos;re now upgrading to <code>2.0.0</code>, the first thing that you&apos;ll need to do after updating and installing the gem is to run </p><pre><code>kamal init</code></pre><p>This will create a new file <code>.kamal/secrets</code> which is meant to replace the <code>.env</code> file. This file will be committed to your version control, so do not put your actual secrets in there. Luckily, the generated file provides a few different examples that you can use in order to find what will work best for you. In my case, since all of my apps using Kamal to deploy share the same Docker Hub account, I have this within the secrets file.</p><pre><code># Option 1: Read secrets from the environment
KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD

# Option 2: Read secrets via a command
RAILS_MASTER_KEY=$(cat config/credentials/production.key)</code></pre><p>While using Rails Credentials does have some drawbacks like rotating keys can be a bit annoying or your key gets compromised and now every secret needs to be rolled and an audit done to see if there was any breaches, it does make it a lot easier to manage from an infrastructure standpoint.</p><p>Then we need to update the <code>config/deploy.yml</code> file. I removed the references to the <code>traefik</code> as that was no longer needed. In my initial discovery on my test app, I saw that the application deployed successfully, but I was unable to access the website. This is because the <code>kamal-proxy</code> assumes that the application&apos;s port is <code>80</code> instead of <code>3000</code>. This was the biggest change that I had to made to the <code>deploy.yml</code> file outside of cleaning up the no longer used things. But remember that <code>kamal config</code> will let you know if you have some lingering obsolete configs. To tell <code>kamal-proxy</code> that you need to use port <code>3000</code> instead, you&apos;d insert a new key for the <code>proxy</code> and then use <code>app_port</code> to specify the port.</p><pre><code>proxy:
  app_port: 3000</code></pre><p>Once this is all done, you can begin your upgrade with</p><pre><code>kamal upgrade</code></pre><p>This will ask you to confirm that you&apos;ll be deleting the <code>traefik</code> proxy and use <code>kamal-proxy</code> instead. Once this upgrade happens, you should be good to go. However, if you&apos;re like me and it didn&apos;t go smoothly, keep reading on.</p><h1 id="enter-aws-alb-load-balancer">Enter AWS ALB Load Balancer</h1><p>I would like to think that my infrastructure if fairly simple. In its most simple terms, this is what the infrastructure looks like.</p><figure class="kg-card kg-image-card"><img src="https://blog.driftingruby.com/content/images/2024/09/CleanShot-2024-09-26-at-22.38.27.png" class="kg-image" alt="Kamal 2.0 Issue with AWS ALB" loading="lazy" width="811" height="723" srcset="https://blog.driftingruby.com/content/images/size/w600/2024/09/CleanShot-2024-09-26-at-22.38.27.png 600w, https://blog.driftingruby.com/content/images/2024/09/CleanShot-2024-09-26-at-22.38.27.png 811w" sizes="(min-width: 720px) 720px"></figure><p>When deploying with Kamal 2.0, there didn&apos;t seem to be any issues. It successfully deployed and I thought that everything was good. However, that&apos;s when the dreadful Cloudflare error page showed up and I paniced.</p><p>I immediately looked at the documentation and my test app&apos;s config to see if there were any things that I may have missed, but everything looked fine. Since I knew what my infrastructure looked like on the test application, it helped me narrow some things down. The big differences between the test application and Drifting Ruby&apos;s infrastructure is the AWS Load Balancer and an additional Web Server. Since the web servers were on separate hosts, I didn&apos;t think that would matter. So I looked in the logs with <code>kamal app logs -f</code> and that&apos;s when the problems started to become a bit more clear. Well, not really because I had no idea what it mean. In my error logs I saw the repeated message <code>Unsupported HTTP Method used: PRI</code> which seemed odd.</p><figure class="kg-card kg-image-card"><img src="https://blog.driftingruby.com/content/images/2024/09/error-messages.png" class="kg-image" alt="Kamal 2.0 Issue with AWS ALB" loading="lazy" width="495" height="210"></figure><p>Searching around for that error message returned multiple sites pointing to it being an <code>HTTP/2</code> problem. I knew that I was using <code>HTTP/2</code> on my test application through the Cloudflare Proxy, but the only difference in this case that was applicable was the AWS Load Balancer. So, I looked on there and saw that I had configured the Target Group for the load balancer to use <code>HTTP/2</code> as well. This really shouldn&apos;t matter since Cloudflare is doing the initial handshake with the browser and setting the protocol that will be used on the page.</p><p>Sadly, AWS doesn&apos;t just let you change the Protocol version either. I had to create a new Target Group (mimicking the settings from my original Target Group) but use <code>HTTP 1</code>instead.</p><figure class="kg-card kg-image-card"><img src="https://blog.driftingruby.com/content/images/2024/09/alb.png" class="kg-image" alt="Kamal 2.0 Issue with AWS ALB" loading="lazy" width="750" height="176" srcset="https://blog.driftingruby.com/content/images/size/w600/2024/09/alb.png 600w, https://blog.driftingruby.com/content/images/2024/09/alb.png 750w" sizes="(min-width: 720px) 720px"></figure><p>After this change, the site immediately came back up and everything was good. Since then, I&apos;ve tried deploying again and it went without any issues.</p><p>I hope that this helps you if you&apos;ve run into this issue too!</p>]]></content:encoded></item><item><title><![CDATA[Ollama Context Window]]></title><description><![CDATA[<p><a href="https://ollama.com/?ref=blog.driftingruby.com">Ollama</a> is a versatile tool that I&#x2019;ve been using for several months to handle various tasks. It&apos;s easy to install and easy to use. Initially, I applied it to smaller projects, but recently, I&#x2019;ve been leveraging it for more complex tasks that involve larger</p>]]></description><link>https://blog.driftingruby.com/ollama-context-window/</link><guid isPermaLink="false">66eddcd799117700012ea90d</guid><category><![CDATA[LLM]]></category><dc:creator><![CDATA[Dave Kimura]]></dc:creator><pubDate>Sat, 21 Sep 2024 01:25:40 GMT</pubDate><media:content url="https://blog.driftingruby.com/content/images/2024/09/CVS-receipt-toilet-paper.webp" medium="image"/><content:encoded><![CDATA[<img src="https://blog.driftingruby.com/content/images/2024/09/CVS-receipt-toilet-paper.webp" alt="Ollama Context Window"><p><a href="https://ollama.com/?ref=blog.driftingruby.com">Ollama</a> is a versatile tool that I&#x2019;ve been using for several months to handle various tasks. It&apos;s easy to install and easy to use. Initially, I applied it to smaller projects, but recently, I&#x2019;ve been leveraging it for more complex tasks that involve larger amounts of content.</p><p>One of my primary use cases involves taking receipts, performing Optical Character Recognition (OCR) on them, and then using a large language model (LLM) to extract essential information such as the date of purchase, vendor, category, and amount. Using Ollama with LLAMA3.1:8b has been a great experience most of the time. However, I&#x2019;ve encountered challenges with certain receipts that contain extensive content beyond the actual receipt details.</p><p>For example, consider a PDF receipt from a mobile phone provider. It might include one or two pages with purchase information and then 20 pages of phone log details. In such cases, the context window becomes a significant limitation. Although LLAMA3.1 supports up to 128K tokens for context&#x2014;a substantial capacity&#x2014;I&#x2019;m only able to utilize about 8K tokens by default when running:</p><pre><code>ollama run llama3.1:8b</code></pre><p>This discrepancy means that the LLM often cannot process the full document context, causing the initial instructions for data extraction to be lost. As a result, the consistency of the extraction results varies based on the document size. While small documents yield accurate results, larger ones may lead to inaccurate or incomplete data extraction.</p><p>To make matters worse, the OpenAI API integration with Ollama doesn&#x2019;t currently offer a way to modify the context window. This limitation restricts flexibility and exacerbates the inconsistency in results across different document sizes.</p><p>Addressing this challenge is crucial for maintaining the reliability of data extraction processes, especially as I scale up to handle more extensive documents. Potential solutions could involve optimizing the preprocessing steps to minimize irrelevant content or exploring alternative models with more adaptable context windows.</p><h1 id="solution">Solution</h1><p>We need to modify the default context window! Let&apos;s create a new <code>Modelfile</code> that we&apos;re going to use and this will be used to extend our context window.</p><pre><code># Modelfile
FROM llama3.1:8b
PARAMETER num_ctx 32768</code></pre><p>This is a very simple file, but let&apos;s go over it. The <code>FROM</code> is saying what model we&apos;re going to be using. In my example, I am using <code>llama3.1:8b</code> so I&apos;m going to keep this the exact same. I then provide a <code>PARAMETER</code> who&apos;s key is <code>num_ctx</code> and the value is <code>32768</code>. This will give us a context window that is <strong>4x larger</strong> than the default one.</p><p>We can now &quot;apply&quot; this to our existing model.</p><pre><code>ollama create -f Modelfile llama3.1:8b</code></pre><p>So, before, we had 8192 context size.</p><figure class="kg-card kg-image-card"><img src="https://blog.driftingruby.com/content/images/2024/09/CleanShot-2024-09-20-at-21.09.39.png" class="kg-image" alt="Ollama Context Window" loading="lazy" width="1216" height="714" srcset="https://blog.driftingruby.com/content/images/size/w600/2024/09/CleanShot-2024-09-20-at-21.09.39.png 600w, https://blog.driftingruby.com/content/images/size/w1000/2024/09/CleanShot-2024-09-20-at-21.09.39.png 1000w, https://blog.driftingruby.com/content/images/2024/09/CleanShot-2024-09-20-at-21.09.39.png 1216w" sizes="(min-width: 720px) 720px"></figure><p>Now, the context window size is showing a much larger size. I&apos;ve tested this out in reading large amounts of data and it was able to keep up with the context without losing information. Of course, if you had really large files, then you would be hitting the context limits again. </p><figure class="kg-card kg-image-card"><img src="https://blog.driftingruby.com/content/images/2024/09/CleanShot-2024-09-20-at-21.16.27.png" class="kg-image" alt="Ollama Context Window" loading="lazy" width="211" height="108"></figure><p>If you need an even larger context window, you can increase this to <code>131072</code> which is the 128k context limit that <code>llama3.1</code> has.</p><p>There&apos;s also something else important to know. In increasing the context window, I also increased the amount of VRAM that was being used. I don&apos;t have exact numbers, but for most practical purposes, I found <code>32768</code> to be sufficiently fast, low on VRAM and would fit the largest contexts that I needed.</p>]]></content:encoded></item><item><title><![CDATA[My Development Environment on macOS 15 Sequoia]]></title><description><![CDATA[<p>Each year, I attempt to refresh my system by installing the latest version of macOS, often beginning with a beta release. Nevertheless, I&apos;ve no longer update my primary computer to the latest beta versions due to certain software lagging behind and requiring updates before compatibility is ensured. Specifically,</p>]]></description><link>https://blog.driftingruby.com/my-development-environment-on-macos-15-sequoia/</link><guid isPermaLink="false">66a704e33dfabb0001ad66e8</guid><category><![CDATA[Development]]></category><dc:creator><![CDATA[Dave Kimura]]></dc:creator><pubDate>Thu, 15 Aug 2024 04:11:07 GMT</pubDate><media:content url="https://blog.driftingruby.com/content/images/2024/07/CleanShot-2024-07-28-at-23.32.53.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.driftingruby.com/content/images/2024/07/CleanShot-2024-07-28-at-23.32.53.png" alt="My Development Environment on macOS 15 Sequoia"><p>Each year, I attempt to refresh my system by installing the latest version of macOS, often beginning with a beta release. Nevertheless, I&apos;ve no longer update my primary computer to the latest beta versions due to certain software lagging behind and requiring updates before compatibility is ensured. Specifically, the software that are &quot;dealbreakers&quot; are <a href="https://www.uaudio.com/uad-plugins/apollo-twin-x.html?ref=blog.driftingruby.com" rel="noreferrer">Universal Audio Console</a> and <a href="https://rogueamoeba.com/loopback/?ref=blog.driftingruby.com" rel="noreferrer">Loopback</a>. I use these for recording and have had issues in the past with updating too soon. But, I will use a spare computer or a virtual machine to test out upcoming releases.</p><h1 id="installing-homebrew">Installing Homebrew</h1><p>We&apos;ll first start with Homebrew. This will install the Homebrew.</p><pre><code class="language-bash">/bin/bash -c &quot;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)&quot;</code></pre><p>You&apos;ll then be prompted to run some additional commands.</p><pre><code>(echo; echo &apos;eval &quot;$(/opt/homebrew/bin/brew shellenv)&quot;&apos;) &gt;&gt; /Users/$(whoami)/.zprofile
eval &quot;$(/opt/homebrew/bin/brew shellenv)&quot;</code></pre><h1 id="installing-apps">Installing Apps</h1><p>Some apps that I&apos;ll install on any new install. Some of these applications are paid. There are several others, but they&apos;re unrelated to my development experience.</p><pre><code>brew install \
  iterm2 \
  visual-studio-code \
  orbstack \
  1password \
  obsidian \
  google-chrome \
  cleanshot \
  cyberduck \
  rectangle \
  ollama \
  tableplus \
  magic-wormhole</code></pre><p>I will also install some libraries needed for other Rails development stuff. Since I use both MySQL and PostgreSQL, I&apos;ll install the appropriate packages. I don&apos;t actually install the services since I&apos;ve dockerized all of my applications and their dependencies.</p><pre><code>brew install libpq mysql-client</code></pre><h1 id="oh-my-zsh">Oh-my-zsh</h1><p>By default, macOS comes with ZSH, but there are some things that <a href="https://ohmyz.sh/?ref=blog.driftingruby.com" rel="noreferrer">oh-my-zsh</a> does out of the box that I&apos;ve grown to enjoy.</p><pre><code>sh -c &quot;$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)&quot;</code></pre><h1 id="devrc">.devrc</h1><p>I like my dot files to be separate from the main <code>.zshrc</code> file. This way, I can backup my <code>.devrc</code> file and have all of my settings intact. But, I will insert into the <code>.zshrc</code> a command to load the <code>.devrc</code></p><pre><code>echo &apos;source ~/.devrc&apos; &gt;&gt; ~/.zshrc
</code></pre><h1 id="installing-ruby">Installing Ruby</h1><p>Then we can start with ASDF which is a runtime version manager.</p><pre><code>brew install asdf
echo -e &quot;\n. $(brew --prefix asdf)/libexec/asdf.sh&quot; &gt;&gt; ${ZDOTDIR:-~}/.devrc
source ~/.zshrc
</code></pre><p>We will need to install some packages for Ruby.</p><pre><code>asdf plugin add rust
asdf install rust latest
asdf global rust latest
brew install openssl@3 readline libyaml gmp</code></pre><p>Then we can install Ruby. At the time of this article, the latest version of Ruby is 3.3.4. I will also add <code>echo &apos;export RUBY_YJIT_ENABLE=1&apos; &gt;&gt; ~/.devrc</code> since some applications I work on may not be updated to the latest version of Rails which enables YJIT by default.</p><pre><code>asdf plugin add ruby
asdf install ruby 3.3.4
asdf global ruby 3.3.4
echo &apos;export RUBY_YJIT_ENABLE=1&apos; &gt;&gt; ~/.devrc
source ~/.zshrc
gem update --system</code></pre><p>Ruby&apos;s now installed, so we can verify this and make sure that everything is working well.</p><pre><code>ruby -v
ruby 3.3.4 (2024-07-09 revision be1089c8ec) +YJIT [arm64-darwin24]</code></pre><p>Since I don&apos;t want to install documentation for gems, I will typically exclude this by updating my <code>.gemrc</code></p><pre><code>echo &quot;gem: --no-document&quot; &gt; ~/.gemrc</code></pre><p>And now we&apos;re ready to install Ruby on Rails and any other gems</p><pre><code>gem install rails foreman</code></pre><p>I also have a <code>~/.railsrc</code> file which has a bunch of defaults whenever I am creating a new application.</p><pre><code>echo &quot;--asset-pipeline propshaft&quot; &gt;&gt; ~/.railsrc
echo &quot;--javascript esbuild&quot; &gt;&gt; ~/.railsrc
echo &quot;--skip-jbuilder&quot; &gt;&gt; ~/.railsrc
echo &quot;--css bootstrap&quot; &gt;&gt; ~/.railsrc</code></pre><h1 id="installing-nodejs">Installing NodeJS</h1><p>While Rails will use importmaps as a default, I often find that importmaps is a bit more annoying to work with than other options like <code>bun</code> or <code>esbuild</code>. So, I&apos;ll use <code>asdf</code> to install NodeJS.</p><pre><code>asdf plugin add nodejs
asdf install nodejs 21.7.3
asdf global nodejs 21.7.3
npm -g install yarn</code></pre><h1 id="docker">Docker</h1><p>I enjoy keeping things clean on my development environment. I keep the runtimes for Ruby and NodeJS on the host machine because it&apos;s easier to run some scripts and create sample applications. I use to use Docker Desktop and have since switched to OrbStack.</p><pre><code>orb start</code></pre><p>For some reason, it&apos;s 2024 and we still need Rosetta for some things. When starting OrbStack, it prompts us to install Rosetta. But honestly, this isn&apos;t a horrible thing as when building x86/amd64 containers on Apple Silicon, it can get a speed boost by building the Docker image through Rosetta.</p><figure class="kg-card kg-image-card"><img src="https://blog.driftingruby.com/content/images/2024/07/image-1.png" class="kg-image" alt="My Development Environment on macOS 15 Sequoia" loading="lazy" width="1160" height="656" srcset="https://blog.driftingruby.com/content/images/size/w600/2024/07/image-1.png 600w, https://blog.driftingruby.com/content/images/size/w1000/2024/07/image-1.png 1000w, https://blog.driftingruby.com/content/images/2024/07/image-1.png 1160w" sizes="(min-width: 720px) 720px"></figure><p>Once you go through the OrbStack setup, you should be able to interact with Docker from the command line.</p><pre><code>docker run --rm hello-world</code></pre><h1 id="backing-up-data">Backing up data</h1><p>This is the basics of what I need to do in order to be productive with my development environment. There are a few things that I will backup and transfer to my new computer (or fresh install). These things are mainly to get back up and running quicker.</p><p>On my original macOS install I will back up the files/folders and transfer them to my new setup. The reason why I copy my Projects is that there are several files within these folders that would be difficult to get back. Mainly encryption keys or <code>.env</code> files. I find it easier to just copy the directories over rather than redownloading them from the version control.</p><pre><code>~/.ssh/
~/.aws/
~/Projects/ # or whatever you call them
~/.zsh_history
~/.devrc
~/.gemrc</code></pre><p>Typically, I&apos;ll use TimeMachine to back up my macOS computer, but also have another backup solution like BackBlaze in the event that my computer and TimeMachine backup fails.</p><h1 id="testing-things-out">Testing things out</h1><p>Let&apos;s create a simple Ruby on Rails application and see if things are working correctly.</p><pre><code>rails new example
cd example
bin/dev</code></pre><p>We can visit http://localhost:3000 and see if things are working.</p><figure class="kg-card kg-image-card"><img src="https://blog.driftingruby.com/content/images/2024/07/image-2.png" class="kg-image" alt="My Development Environment on macOS 15 Sequoia" loading="lazy" width="2000" height="1442" srcset="https://blog.driftingruby.com/content/images/size/w600/2024/07/image-2.png 600w, https://blog.driftingruby.com/content/images/size/w1000/2024/07/image-2.png 1000w, https://blog.driftingruby.com/content/images/size/w1600/2024/07/image-2.png 1600w, https://blog.driftingruby.com/content/images/2024/07/image-2.png 2144w" sizes="(min-width: 720px) 720px"></figure>]]></content:encoded></item><item><title><![CDATA[Rails 7.2 Upgrade Problems (of my own making)]]></title><description><![CDATA[<p>I maintain several Ruby on Rails applications. Most of these are personal projects that I&apos;ve built to make managing my daily life a bit easier. These projects range from small to much larger in scale.</p><p>I&apos;m a big fan of keeping these projects up to date</p>]]></description><link>https://blog.driftingruby.com/rails-7-2-upgrade-problems-of-my-own-making/</link><guid isPermaLink="false">66bb47e33dfabb0001ad6767</guid><category><![CDATA[Rails]]></category><category><![CDATA[Upgrade]]></category><dc:creator><![CDATA[Dave Kimura]]></dc:creator><pubDate>Tue, 13 Aug 2024 12:46:41 GMT</pubDate><media:content url="https://blog.driftingruby.com/content/images/2024/08/rails_logo-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.driftingruby.com/content/images/2024/08/rails_logo-1.png" alt="Rails 7.2 Upgrade Problems (of my own making)"><p>I maintain several Ruby on Rails applications. Most of these are personal projects that I&apos;ve built to make managing my daily life a bit easier. These projects range from small to much larger in scale.</p><p>I&apos;m a big fan of keeping these projects up to date as they prove to be a good testing ground for any kind of updates. Some of these applications have been built off of Rails 4 or 5 and some them were created with Rails 7. As time passes, I will use something like <a href="https://railsdiff.org/?ref=blog.driftingruby.com" rel="noreferrer">https://railsdiff.org</a> to see the differences between Rails versions in what is created from fresh applications. I typically apply these changes to each of my applications during the upgrade process.</p><p>My deployment strategy varies depending on the application, but there are consistencies across all of the applications. All of the applications are using Docker containers on the &quot;production&quot; environment. And, all of the applications are hosted on x86/AMD64 servers. The differences between the applications is that the newer ones are using Kamal to deploy and the older ones are deployed via Portainer which has a webhook to can be pinged to restart the container. I much prefer the Kamal route instead, but haven&apos;t changed over all of the applications yet.</p><p>When upgrading to Rails 7.2, I encountered issues early in the process. After updating everything on one application, I went to deploy. As a side note, regardless of the hosting method, I always use a <code>bin/deploy</code> script to trigger production deployments. This is helpful as I don&apos;t have to worry about which mechanism I am using to deploy. In this example, the deployment mechanism was a simple Docker build using a remote server and then making a cURL request to Portainer.</p><p><em>I did not dive into the specific changes of Rails 7.2 to see if this is a bug or not. However, based on new Rails 7.2 applications, even with <code>--database postgresql</code> as a default flag, the issue doesn&apos;t exist. I blame myself for habits that I&apos;ve created over the years with the ways I did things back then, but never changed how I do them today. So, while the error messages could have been a bit more specific, I feel that this is a &quot;past me&quot; bug that finally caught up to &quot;present me&quot;.</em></p><pre><code>Dockerfile:59
--------------------
  57 |
  58 |     # Precompiling assets for production without requiring secret RAILS_MASTER_KEY
  59 | &gt;&gt;&gt; RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
  60 |
  61 |
--------------------
ERROR: failed to solve: process &quot;/bin/sh -c SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile&quot; did not complete successfully: exit code: 1</code></pre><p>I got the error above and unfortunately, it was not very helpful in troubleshooting. Luckily, Ruby on Rails is pretty awesome and we did also get a stacktrace.</p><pre><code> =&gt; ERROR [build  9/10] RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile                                                                                               0.4s
------
 &gt; [build  9/10] RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile:
0.417 bin/rails aborted!
0.417 NoMethodError: undefined method `to_sym&apos; for nil (NoMethodError)
0.417
0.417       super(key.to_sym)
0.417                ^^^^^^^
0.418 /usr/local/bundle/ruby/3.3.0/gems/activesupport-7.2.0/lib/active_support/ordered_options.rb:42:in `[]&apos;</code></pre><p>Ok... yeah... that&apos;s not very helpful either. I spent a few hours trying to figure out what the problem was and why this application was having problems in building the Docker container image. I narrowed down the problem to my <code>config/database.yml</code> file which looks something like this:</p><pre><code>default: &amp;default
  adapter: postgresql
  encoding: unicode
  pool: &lt;%= ENV.fetch(&quot;RAILS_MAX_THREADS&quot;) { 5 } %&gt;
  url: &lt;%= ENV.fetch(&quot;DATABASE_URL&quot;) { &quot;localhost&quot; } %&gt;

development:
  &lt;&lt;: *default
  database: example_development

test:
  &lt;&lt;: *default
  database: example_test

production:
  &lt;&lt;: *default
  database: example_production
</code></pre><h1 id="solution">Solution</h1><p>Overall... it seems like it&apos;s very simple and there&apos;s nothing to really write home about. But, the issue here is actually the <code>DATABASE_URL</code> environment variable. For some reason, this is causing the problem, so I updated my <code>Dockerfile</code> to include a dummy variable.</p><pre><code>RUN DATABASE_URL=postgres://postgres SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile</code></pre><figure class="kg-card kg-image-card"><img src="https://blog.driftingruby.com/content/images/2024/08/CleanShot-2024-08-13-at-08.10.20.png" class="kg-image" alt="Rails 7.2 Upgrade Problems (of my own making)" loading="lazy" width="863" height="237" srcset="https://blog.driftingruby.com/content/images/size/w600/2024/08/CleanShot-2024-08-13-at-08.10.20.png 600w, https://blog.driftingruby.com/content/images/2024/08/CleanShot-2024-08-13-at-08.10.20.png 863w" sizes="(min-width: 720px) 720px"></figure><p>This resolved the issue!</p><h1 id="alternative-solution">Alternative Solution</h1><p>This whole issue is centered around the <code>DATABASE_URL</code>. If you were to use Rails Credentials, then this problem doesn&apos;t exist. In the <code>config/database.yml</code> you can switch from an environment variable over to using the Rails Credentials.</p><pre><code>url: &lt;%= Rails.application.credentials.database_url %&gt;</code></pre><p>And this also works!</p><figure class="kg-card kg-image-card"><img src="https://blog.driftingruby.com/content/images/2024/08/CleanShot-2024-08-13-at-08.13.09.png" class="kg-image" alt="Rails 7.2 Upgrade Problems (of my own making)" loading="lazy" width="774" height="274" srcset="https://blog.driftingruby.com/content/images/size/w600/2024/08/CleanShot-2024-08-13-at-08.13.09.png 600w, https://blog.driftingruby.com/content/images/2024/08/CleanShot-2024-08-13-at-08.13.09.png 774w" sizes="(min-width: 720px) 720px"></figure><h1 id="not-isolated-to-just-databaseurl">Not isolated to just DATABASE_URL</h1><p>Now that I got this application working and deployed, I went to update another application that was using Kamal for deployments and SQLite as the database. So, there was no <code>DATABASE_URL</code> to configure since the database was hosted locally on that container (mounted through a volume).</p><p>However, I got a similar problem on a different application that was using ActiveStorage. My configuration looked similar to</p><pre><code>amazon:
  service: S3
  access_key_id: ...
  secret_access_key: ...
  region: us-east-1
  bucket: &lt;%= ENV.fetch(&quot;S3_BUCKET&quot;) %&gt;
</code></pre><p>And another application&apos;s <code>storage.yml</code> file was using Rails Credentials</p><pre><code>amazon:
  service: S3
  access_key_id: ...
  secret_access_key: ...
  region: us-east-1
  bucket: &lt;%= Rails.application.credentials.storage.bucket %&gt;</code></pre><p>And I got a similar obscure error message but this time it was</p><pre><code> =&gt; ERROR [10/10] RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile                                                                                      4.5s
------
 &gt; [10/10] RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile:
4.404 Error retrieving instance profile credentials: Failed to open TCP connection to 169.254.169.254:80 (execution expired)
4.405 bin/rails aborted!
4.406 ArgumentError: missing required option :name (ArgumentError)
4.406 
4.406       when nil then raise ArgumentError, &quot;missing required option :name&quot;
4.406                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4.406 /app/app/models/user.rb:107:in `&lt;class:User&gt;&apos;
4.406 /app/app/models/user.rb:78:in `&lt;main&gt;&apos;
4.406 /app/config/routes.rb:167:in `block in &lt;main&gt;&apos;
4.406 /app/config/routes.rb:3:in `&lt;main&gt;&apos;</code></pre><figure class="kg-card kg-image-card"><img src="https://blog.driftingruby.com/content/images/2024/08/CleanShot-2024-08-13-at-08.29.08.png" class="kg-image" alt="Rails 7.2 Upgrade Problems (of my own making)" loading="lazy" width="1137" height="495" srcset="https://blog.driftingruby.com/content/images/size/w600/2024/08/CleanShot-2024-08-13-at-08.29.08.png 600w, https://blog.driftingruby.com/content/images/size/w1000/2024/08/CleanShot-2024-08-13-at-08.29.08.png 1000w, https://blog.driftingruby.com/content/images/2024/08/CleanShot-2024-08-13-at-08.29.08.png 1137w" sizes="(min-width: 720px) 720px"></figure><p>And I found that passing in the environment variable didn&apos;t work here. Instead, I had to update the <code>storage.yml</code> file. </p><pre><code>amazon:
  service: S3
  access_key_id: ...
  secret_access_key: ...
  region: us-east-1
  bucket: &lt;%= Rails.application.credentials.storage.bucket || &quot;my_bucket&quot; %&gt;</code></pre><p>Honestly, the issue here is that the bucket name isn&apos;t truly a &quot;secret&quot;. However, I did have different buckets depending on the environment I was working in. So, I needed a configuration that accounted for this.</p><p>In hindsight, I should have kept my bucket names similar to the convention of the default <code>storage.yml</code> example where there is a plaintext prefix of the bucket name and then the <code>Rails.env</code> is added in based on the environment.</p><pre><code># amazon:
#   service: S3
#   access_key_id: &lt;%= Rails.application.credentials.dig(:aws, :access_key_id) %&gt;
#   secret_access_key: &lt;%= Rails.application.credentials.dig(:aws, :secret_access_key) %&gt;
#   region: us-east-1
#   bucket: your_own_bucket-&lt;%= Rails.env %&gt;</code></pre><h1 id="conclusion">Conclusion</h1><p>Overall the Rails upgrade experience from Rails 7.1 to 7.2 was rather painless. Over the years, I&apos;ve developed a habit of minimizing gem dependencies and keeping configurations as close to the &apos;Rails way&apos; as possible. But, sometimes with older applications, things slip through the cracks because they &quot;worked&quot; back then and the configuration files like the <code>database.yml</code> and <code>storage.yml</code> normally aren&apos;t revisited during the upgrades.</p><p>I hope that these findings can help save you some time!</p>]]></content:encoded></item><item><title><![CDATA[Self Hosted Github Actions Runner]]></title><description><![CDATA[<p>The spark of this blog post came from a&#xA0;<a href="https://twitter.com/dhh/status/1741838676487737763?ref=blog.driftingruby.com">tweet</a>&#xA0;from DHH about the default Rails 8 having a Github Actions workflow.</p><p>Github Actions is a great tool for automating your workflows. It&apos;s built into Github and is free for public repositories. However, if you have</p>]]></description><link>https://blog.driftingruby.com/self-hosted-github-actions-runner/</link><guid isPermaLink="false">66a0613a93cad700012fa60a</guid><category><![CDATA[Development]]></category><dc:creator><![CDATA[Dave Kimura]]></dc:creator><pubDate>Wed, 24 Jul 2024 02:08:36 GMT</pubDate><content:encoded><![CDATA[<p>The spark of this blog post came from a&#xA0;<a href="https://twitter.com/dhh/status/1741838676487737763?ref=blog.driftingruby.com">tweet</a>&#xA0;from DHH about the default Rails 8 having a Github Actions workflow.</p><p>Github Actions is a great tool for automating your workflows. It&apos;s built into Github and is free for public repositories. However, if you have a private repository, you will need to pay for Github Actions. This can be a bit expensive if you have a lot of repositories. There is a way to reduce the cost of Github Actions by using self hosted runners. This allows you to run your Github Actions workflows on your own servers. This can be a great way to reduce the cost of Github Actions.</p><p>To be fair, Github Actions does have a free tier for private repositories. However, this free tier only allows for 2000 minutes of build time per month. This can be a bit limiting if you have a lot of repositories or if you have a lot of builds.</p><p>The good news is that you can set up your own self hosted runner for free. This will allow you to run your Github Actions workflows on your own servers. This can be a great way to reduce the cost of Github Actions.</p><h2 id="setting-up-a-self-hosted-runner">Setting up a Self Hosted Runner</h2><p>To set up a self hosted runner, you will need to have a server that you can SSH into. This can be a server that you already have or it can be a new server that you create. It could also be something like a Raspberry Pi or a Virtual Machine hosted in the cloud.</p><p>To start, we will use Debian 12 (Bookworm) as our base operating system. This is the latest version of Debian and it is a great choice for a server. It is lightweight and it is easy to install. Once you have a running Debian 12 server, we can SSH into it and update and install some packages.</p><pre><code>apt update &amp;&amp; apt upgrade -y &amp;&amp; \
apt-get install -y \
    curl nano git gnupg apt-transport-https ca-certificates \
    software-properties-common make gcc autoconf patch \
    build-essential rustc libssl-dev libyaml-dev libreadline6-dev \
    zlib1g-dev libgmp-dev libncurses5-dev libffi-dev libgdbm6 \
    libgdbm-dev libdb-dev uuid-dev htop libpq-dev
</code></pre>
<p>We&apos;ll then create a new user for our runner so that we don&apos;t have to run everything as root.</p><pre><code>adduser --quiet --disabled-password --shell /bin/bash --home /home/developer --gecos &quot;Developer&quot; developer
</code></pre>
<p>The runner will need to be able to run Docker containers, so we&apos;ll need to install Docker.</p><pre><code>install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo \
&quot;deb [arch=&quot;$(dpkg --print-architecture)&quot; signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
&quot;$(. /etc/os-release &amp;&amp; echo &quot;$VERSION_CODENAME&quot;)&quot; stable&quot; | \
tee /etc/apt/sources.list.d/docker.list &gt; /dev/null
apt update &amp;&amp; apt install docker-ce -y
</code></pre>
<p>And the&#xA0;<code>developer</code>&#xA0;user will need to be able to run Docker containers without&#xA0;<code>sudo</code>.</p><pre><code>usermod -aG docker developer
</code></pre>
<p>Since I typically use esbuild on my projects, It&apos;ll be best to install nodejs and yarn.</p><pre><code>curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | \
    gpg --dearmor -o /etc/apt/nodesource.gpg &amp;&amp; \
    echo &quot;deb [signed-by=/etc/apt/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main&quot; | \
    tee /etc/apt/sources.list.d/nodesource.list &amp;&amp; \
    apt update &amp;&amp; apt install nodejs -y &amp;&amp; \
    npm install --global yarn
</code></pre>
<p>We can then log in as the&#xA0;<code>developer</code>&#xA0;user to install Ruby.</p><pre><code>su - developer
</code></pre>
<p>On my development machines, I prefer to use&#xA0;<code>asdf</code>, but for a runner,&#xA0;<code>rbenv</code>&#xA0;is a great choice.</p><pre><code>git clone https://github.com/rbenv/rbenv.git ~/.rbenv
echo &apos;eval &quot;$(~/.rbenv/bin/rbenv init - bash)&quot;&apos; &gt;&gt; ~/.bashrc
git clone https://github.com/rbenv/ruby-build.git &quot;$(rbenv root)&quot;/plugins/ruby-build
git -C &quot;$(rbenv root)&quot;/plugins/ruby-build pull
echo &apos;export PATH=&quot;$HOME/.rbenv/plugins/ruby-build/bin:$PATH&quot;&apos; | tee -a ~/.bashrc
source ~/.bashrc
</code></pre>
<p>We can then install the Github Actions Runner. You can check for the latest versions on the&#xA0;<a href="https://github.com/actions/runner/releases?ref=blog.driftingruby.com">Github Actions Runner Releases</a>&#xA0;page.</p><pre><code>mkdir actions-runner &amp;&amp; cd actions-runner
export RUNNER_VERSION=&quot;2.311.0&quot;
curl -o actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz -L https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz
tar xzf ./actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz
</code></pre>
<p>You&apos;ll need to register this runner, so be sure to check out the steps below on how to do that. You&apos;ll need to run the registration script in the folder that you extracted the runner to. I keep the default settings in wizard of the script.</p><p>To register the runner, head over to one of your projects on Github and select the Actions &gt; Runners. Then click on the&#xA0;<code>New self-hosted runner</code>&#xA0;button. Select your operating system and architecture.</p><p>Under the&#xA0;<code>Configure</code>&#xA0;section, there will be a script to run that starts with&#xA0;<code>./config.sh</code>&#xA0;and ends with a token. Copy this script and run it on your server. This will register the runner with Github. This will need to be done in the folder that you extracted the runner to.</p><pre><code># ./config &lt;- run the config registration script from below here.
</code></pre>
<h2 id="installing-ruby">Installing Ruby</h2><p>We can now install our Ruby version. I&apos;ve updated all of my projects to Ruby 3.3.0, so that&apos;s the version that I&apos;ll be using. However, modify this as needed. This is also assuming that you&apos;re installing this on an amd64 machine. If you&apos;re using a Raspberry Pi or something else, you&apos;ll need to modify the path. It&apos;s also important to let the runner know that we&apos;ve installed Ruby so we will touch a file to let the runner know that this Ruby version is present.</p><pre><code>export RUBY_VERSION=&quot;3.3.0&quot;
ruby-build ${RUBY_VERSION} /home/developer/actions-runner/_work/_tool/Ruby/${RUBY_VERSION}/x64
touch /home/developer/actions-runner/_work/_tool/Ruby/${RUBY_VERSION}/x64.complete
</code></pre>
<h2 id="starting-the-runner-at-boot">Starting the Runner at boot</h2><p>We&apos;ll edit the&#xA0;<code>/etc/systemd/system/github-runner.service</code>&#xA0;file and place the following contents in it. This will allow us to start the runner at boot.</p><pre><code>[Unit]
Description=GitHub Runner
After=network.target

[Service]
User=developer
WorkingDirectory=/home/developer/actions-runner
ExecStart=/home/developer/actions-runner/run.sh
Restart=always
RestartSec=3
LimitNOFILE=4096

[Install]
WantedBy=multi-user.target
</code></pre>
<h2 id="reload-systemd-enable-and-start-the-service">Reload systemd, enable and start the service</h2><pre><code>systemctl daemon-reload
systemctl enable github-runner
systemctl start github-runner
</code></pre>
<h2 id="conclusion">Conclusion</h2><p>We now have a self hosted runner that we can use for our Github Actions workflows. This will allow us to run our workflows on our own servers. This can be a great way to reduce the cost of Github Actions. If you have any questions or comments, please feel free to reach out. I&apos;m always happy to help.</p>]]></content:encoded></item><item><title><![CDATA[Using Ansible to Update Kamal Servers]]></title><description><![CDATA[<p>I&apos;ve been using Kamal recently and it&apos;s been a great experience. It takes a lot of the headaches out of having to wait for a deployment to finish. Previously, I was hosting&#xA0;<a href="https://www.driftingruby.com/?ref=blog.driftingruby.com">Drifting Ruby</a>&#xA0;on AWS with Elastic Beanstalk. This was a great solution</p>]]></description><link>https://blog.driftingruby.com/using-ansible-to-update-kamal-servers/</link><guid isPermaLink="false">66a0600893cad700012fa5e8</guid><category><![CDATA[Development]]></category><dc:creator><![CDATA[Dave Kimura]]></dc:creator><pubDate>Sun, 29 Oct 2023 04:00:00 GMT</pubDate><content:encoded><![CDATA[<p>I&apos;ve been using Kamal recently and it&apos;s been a great experience. It takes a lot of the headaches out of having to wait for a deployment to finish. Previously, I was hosting&#xA0;<a href="https://www.driftingruby.com/?ref=blog.driftingruby.com">Drifting Ruby</a>&#xA0;on AWS with Elastic Beanstalk. This was a great solution for a long time as I didn&apos;t have to worry about the servers and it set up a lot of the networking for me. However, there were some pain points with this process and I have been wanting to switch to something new for a while. When I first heard about MRSK (now Kamal), I was excited! Ruby on Rails applications were getting a first class citizen deployment solution. The networking and infrastructure side of things hasn&apos;t ever been an issue for me since I initially came from that kind of background. However, I was initially looking for something easy to use that gave a bit of flexibility. I didn&apos;t want to go the Heroku route since pricing was a concern early on and AWS offers Reserved Instances which can save a significant amount.</p><p>One of the drawbacks with Kamal is that it doesn&apos;t have a built in way to update the servers. This is where Ansible comes in. Ansible is a tool that allows you to automate server configuration and deployment. But, in our case, we are going to use it to update our servers. This is a great solution for me since I already had some familiarity with Ansible and it&apos;s a great tool to have in your toolbelt. I need to look up some Ansible tricks a bit more to look at full server provisioning, but this will be a good start.</p><p><strong>Note</strong>&#xA0;I am performing these steps with the assumption that you have already set up your Kamal servers and have them running. You can check out a&#xA0;<a href="https://www.driftingruby.com/episodes/kamal-in-github-actions?ref=blog.driftingruby.com">recent episode on Kamal</a>&#xA0;which covers deploying with Github Actions, but I also go through the steps of setting up the servers. I also have an&#xA0;<a href="https://www.driftingruby.com/episodes/deploying-with-mrsk?ref=blog.driftingruby.com">older episode when Kamal was called MRSK</a>&#xA0;which covers setting up the servers with a managed database and load balancer.</p><p><strong>Note</strong>&#xA0;This Ansible playbook is also assuming that you&apos;ll be running this on a computer that has shell access to the servers that you&apos;re wanting to manage.</p><h2 id="setting-up-ansible">Setting up Ansible</h2><p>Since I am on an Apple computer, I&apos;m going to use Homebrew to install Ansible. If you&apos;re on a different platform, you can check out the&#xA0;<a href="https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html?ref=blog.driftingruby.com">Ansible installation documentation</a>&#xA0;for more information.</p><pre><code>brew install ansible
</code></pre>
<h2 id="understanding-ansible">Understanding Ansible</h2><p>This Ansible setup is going to have two different files. The first is the&#xA0;<code>inventory</code>&#xA0;file which will contain the list of servers that we want to manage. The second is the&#xA0;<code>playbook.yml</code>&#xA0;file which will contain the instructions for Ansible to run.</p><p>The&#xA0;<code>inventory</code>&#xA0;file will look something like this:</p><pre><code>[server_group]
app1            ansible_host=PUBLIC_IP_ADDRESS_OF_SERVER
app2            ansible_host=PUBLIC_IP_ADDRESS_OF_SERVER
</code></pre>
<p>The inventory has different groups of servers. In this case, we only have one group called&#xA0;<code>server_group</code>. This group contains two servers. The first server is called&#xA0;<code>app1</code>&#xA0;and the second server is called&#xA0;<code>app2</code>. You can add as many servers as you want to this group. You can also create multiple groups if you want to manage different servers with different playbooks. You will want to replace the&#xA0;<code>PUBLIC_IP_ADDRESS_OF_SERVER</code>&#xA0;with the public IP address of your server. This should be the same IP address that you use to SSH into your server and that you have set up in the Kamal&#xA0;<code>deploy.yml</code>&#xA0;file.</p><p>The&#xA0;<code>playbook.yml</code>&#xA0;file will look something like this:</p><pre><code>---
- name: Update and Upgrade Packages on Servers Sequentially
  hosts: all
  serial: 1
  become: yes

  tasks:
    - name: Run apt update and apt upgrade on servers
      apt:
        update_cache: yes
        upgrade: yes
        cache_valid_time: 3600
      register: apt_update

    - name: Pause for 2 minutes before next server update
      pause:
        minutes: 2
      when: apt_update.changed
</code></pre>
<p>This is a bit more complicated because we have some additional things to take into consideration. The first thing to note is that we are running the playbook on all servers in the inventory. This is because we want to update all of the servers. However, we don&apos;t want to update them all at the same time. This is where the&#xA0;<code>serial</code>&#xA0;option comes in. This will tell Ansible to run the playbook on one server at a time. This is important because we don&apos;t want to take down our application while we are updating the servers. This could happen if there was an update to Docker. If we updated Docker on all of the servers at the same time, then our application would be down until the update was complete. This is why we want to update the servers one at a time.</p><p>The&#xA0;<code>become</code>&#xA0;option tells Ansible to run the commands as the root user. This is important because we need to be able to update the packages on the server. If we didn&apos;t have this option, then we would get an error when trying to update the packages.</p><p>The&#xA0;<code>tasks</code>&#xA0;section is where we define the tasks that we want Ansible to run. The first task is to run the&#xA0;<code>apt update</code>&#xA0;and&#xA0;<code>apt upgrade</code>&#xA0;commands. This will update all of the packages on the server. The&#xA0;<code>register</code>&#xA0;option tells Ansible to store the output of the command in a variable called&#xA0;<code>apt_update</code>. This will be used in the next task.</p><p>The second task is to pause for 2 minutes before running the next server update. This is important because we want to make sure that the server is fully updated before we update the next server. The&#xA0;<code>when</code>&#xA0;option tells Ansible to only run this task if the&#xA0;<code>apt_update</code>&#xA0;variable has changed. This will only happen if the server was updated. If the server was already up to date, then this task will not run.</p><h2 id="running-the-ansible-playbook">Running the Ansible Playbook</h2><p>Now that we have our playbook and inventory files set up, we can run the playbook using the following command:</p><pre><code>ansible-playbook -i inventory playbook.yml
</code></pre>
<p>This will run the playbook on all of the servers in the inventory file. You should see something like this:</p><pre><code>PLAY [Update and Upgrade Packages on Servers Sequentially] *********

TASK [Gathering Facts] *********************************************
ok: [app1]

TASK [Run apt update and apt upgrade on servers] *******************
ok: [app1]

TASK [Pause for 2 minutes before next server update] ***************
skipping: [app1]

PLAY [Update and Upgrade Packages on Servers Sequentially] *********

TASK [Gathering Facts] *********************************************
ok: [app2]

TASK [Run apt update and apt upgrade on servers] *******************
ok: [app2]

TASK [Pause for 2 minutes before next server update] ***************
skipping: [app2]

PLAY RECAP *********************************************************
app1 : ok=2 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
app2 : ok=2 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
</code></pre>
<p>You can see that the playbook ran on both servers and that the&#xA0;<code>apt update</code>&#xA0;and&#xA0;<code>apt upgrade</code>&#xA0;commands were run. You can also see that the&#xA0;<code>Pause for 2 minutes before next server update</code>&#xA0;task was skipped. This is because the server was already up to date.</p><p>I hope that this helps close another gap in the Kamal deployment process. I&apos;m looking forward to seeing what else is in store for Kamal and I&apos;m excited to see what the future holds for Ruby on Rails deployments.</p>]]></content:encoded></item><item><title><![CDATA[Using SQLite in Production]]></title><description><![CDATA[<p>So there is a recent push on using SQLite in production, but there&apos;s also little talks around the pros and cons of doing so. This article will try to cover the pros and cons of using SQLite in production.</p><p>To start, there&apos;s some value in asking</p>]]></description><link>https://blog.driftingruby.com/using-sqlite-in-production/</link><guid isPermaLink="false">66a05e8193cad700012fa5b6</guid><category><![CDATA[Development]]></category><category><![CDATA[Meta]]></category><dc:creator><![CDATA[Dave Kimura]]></dc:creator><pubDate>Mon, 02 Oct 2023 01:57:00 GMT</pubDate><content:encoded><![CDATA[<p>So there is a recent push on using SQLite in production, but there&apos;s also little talks around the pros and cons of doing so. This article will try to cover the pros and cons of using SQLite in production.</p><p>To start, there&apos;s some value in asking ChatGTP what they think about using SQLite in production. So, the below information is from ChatGPT and it has some interesting points. However, some of these points are flawed and I don&apos;t agree with them. So, this is another example where you must take everything with a grain of salt when it comes to generative AI content.</p><p>My responses will be in italics next to the points made by ChatGPT.</p><h2 id="pros-of-using-sqlite-in-production">Pros of Using SQLite in Production:</h2><p><strong>Simplicity and Maintenance:</strong>&#xA0;SQLite requires no separate server process or system to operate. Its serverless nature makes it easy to set up, backup, and maintain.&#xA0;<em>Keep in mind that this is requiring you to install the sqlite service on the server that will be using the database. This isn&apos;t a big deal as the&#xA0;<code>sqlite3</code>&#xA0;binary is about 4MB. Though, it doesn&apos;t run as a service so it is simple to manage.</em></p><p><strong>Portability:</strong>&#xA0;All the data in an SQLite database is stored in a single file, making it extremely easy to transfer, copy, or backup.&#xA0;<em>This is true. However, you may want to be careful in situations where you could run out of disk space. Make sure that you have proper monitoring in place on the server. Also, while I&apos;ve never confirmed that this could be an issue, but this is also putting all of your eggs in one basket. If there was ever an issue with the file being corrupted then it could be a total loss. Again, I&apos;ve never seen this happen, but I also don&apos;t use SQLite enough to have experienced this.</em></p><p><strong>Low Resource Footprint:</strong>&#xA0;It&#x2019;s lightweight and requires minimal memory, making it suitable for devices with limited resources like IoT devices or mobile applications.&#xA0;<em>The IoT devices and mobile applications is where I have seen this the most. While a default Ruby on Rails application does come with SQLite as the default database engine, typically it isn&apos;t seen in a production setting.</em></p><p><strong>Consistent Performance:</strong>&#xA0;For applications with a moderate load and where the read operations far outnumber the write operations, SQLite can deliver consistent and fast performance.&#xA0;<em>There&apos;s no network hops to worry about. Optimized SQL queries are often not the slow point of the application. Even with network latency it&apos;s often a tiny amount such that you won&apos;t notice. If we&apos;re talking about a huge application that&apos;s service millions of requests per second then these network hops could add up with a traditional database.</em></p><p><strong>ACID Compliant:</strong>&#xA0;SQLite transactions are ACID compliant, ensuring data integrity even after system crashes or power failures.&#xA0;<em>Atomic, consistent, isolated, and durable (ACID). It&apos;s true that SQLite has come a long way in terms of integrity and reliability of handling the data. However, with system crashes or power failures, database corruptions can occur especially on systems where RAID systems are configured improperly. If the RAID cache is set to write-back, you get better performance but could end up in a situation where there is data loss or corruption.</em></p><p><strong>Cross-Platform:</strong>&#xA0;SQLite is cross-platform and can be used on all major operating systems, including Windows, Linux, and macOS.&#xA0;<em>In terms of deploying a Ruby on Rails application, this is kind of a moot point as we will typically not change the platform the application is hosted on.</em></p><p><strong>Free and Open-Source:</strong>&#xA0;SQLite is free to use and is released under the Public Domain license, which means it can be used for any purpose, including commercial applications.&#xA0;<em>The same could be said for MySQL (or MariaDB) and PostgreSQL. MySQL is GPL licensed which probably doesn&apos;t make a difference if you&apos;re hosting your own application and not distributing it.</em></p><p><strong>No Configuration:</strong>&#xA0;SQLite doesn&#x2019;t require any configuration or setup, making it easy to use for beginners.&#xA0;<em>Yes, properly configuring a MySQL or PostgreSQL instance can be difficult and should be done properly for optimal performance. However, using a managed service with AWS, GCP, Digital Ocean, Azure, etc. will often have a decent configuration that you don&apos;t need to worry about.</em></p><p><strong>No Dependencies:</strong>&#xA0;SQLite doesn&#x2019;t have any dependencies, which means it can be used in any programming language or framework.&#xA0;<em>While MySQL and PostgreSQL have dependencies and you&apos;ll need to install some libraries on the server, they&apos;re well documented steps and usually not a big deal.</em></p><h2 id="cons-of-using-sqlite-in-production">Cons of Using SQLite in Production:</h2><p><strong>Concurrency Issues:</strong>&#xA0;SQLite is not ideal for applications with a high level of concurrent write operations. It locks the entire database during a write operation, which can cause a bottleneck.&#xA0;<em>If we&apos;re working on a single threaded application since we&apos;re talking about smaller applications without too much traffic, this will not really be an issue. However, it does bring up a good point for any kind of background workers that occur asynchronous. Even though SQLite does a good job at handling transactions, it is easy to get ourselves into situations of Deadlocks when background jobs are trying to write to the database at the same time.</em></p><p><strong>Not Suited for Large Databases:</strong>&#xA0;While SQLite supports database files up to 140 TB, it&#x2019;s generally not the best choice for very large datasets due to potential performance issues.&#xA0;<em>Most databases will never reach 140TB. But this does not talk to file system limits. This is really a moot point since modern file systems exceed SQLite&apos;s limits, but if you&apos;re file system was improperly set (to something like Fat32), then you could easily reach the 4GB limit. Most likely, if your database was even getting to a fraction of this size, you&apos;ll have other issues to be concerned with; system resources, performance issues, etc.</em></p><p><strong>Limited Built-in Features:</strong>&#xA0;Unlike more robust RDBMS like PostgreSQL or MySQL, SQLite doesn&#x2019;t have a wide range of built-in tools or support for stored procedures, right out of the box.&#xA0;<em>It supports the important ones like JSON data types. Typically, we don&apos;t use stored procedures in Ruby on Rails applications since the ActiveRecord ORM is powerful enough in most cases.</em></p><p><strong>Limited Security:</strong>&#xA0;SQLite doesn&#x2019;t have built-in support for user management or access control, making it less secure than other RDBMS.&#xA0;<em>While this can be a critical aspect in some scenarios, the Ruby on Rails applications will likely not need this level of access control. However, it does speak to the vulnerability of the database as it will be much easier to make a copy of if the system is ever breached. In a traditional deployment, there&apos;s still nothing that could really stop the attacker if they gained access to the machine, but it does offer some levels of obfuscation if they don&apos;t know about how Rails handles credentials. But, the reality is that if a bad actor has gained access to your machine, all bets are already off and you&apos;re in a very bad situation.</em></p><p><strong>No Client-Server Model:</strong>&#xA0;SQLite doesn&#x2019;t have a client-server model, which means it can&#x2019;t be accessed by multiple users at the same time.&#xA0;<em>This is going to be a big issue if High Availability is a concern. Typically, we will have a load balancer as the SSL termination point and it will route the traffic to one of the available web servers. This is a deal breaker in many situations as using SQLite in a production setting could mean that you have downtime during deployments. You lose the ability for rolling deployments and have basically put all eggs in one basket. Depending on your deployment strategy, it could also mean that you&apos;re having to maintain a server instead of being able to manually or automatically provision and scale up new web servers.</em></p><h2 id="conclusion">Conclusion</h2><p>So, there&apos;s some pros and cons to using SQLite in production. I think that the pros are valid, but the cons must also be taken into consideration. The recent push for using SQLite in production isn&apos;t necessarily a bad direction, but shouldn&apos;t be blindly followed. There&apos;s a lot of factors that go into deciding what database engine to use. I think that SQLite is a great choice for smaller applications that don&apos;t have a lot of traffic. It is also a good choice when architecture simplicity is a concern. Just make sure that you&apos;re weighing all of your options and understand the consequences and benefits of your choices. There&apos;s no one size fits all solution in this case. Also keep in mind that a lot of platforms will have a free tier or a cheap tier for managed database engines. You can get a lot of value out of these services and it will be much easier to scale up in the future if you need to. I think that the biggest concern is the lack of High Availability This is a deal breaker for most applications that are going to be deployed in a production setting. However, for non-revenue generating applications or applications that are not mission critical, SQLite could be a good choice.</p>]]></content:encoded></item><item><title><![CDATA[Running Meilisearch]]></title><description><![CDATA[<p>MeiliSearch is a powerful, fast, and easy-to-use search engine, perfect for developers who want to implement search functionality into their applications. In this blog post, we will walk you through the process of deploying MeiliSearch with SSL encryption using Docker Compose and Nginx as a reverse proxy. This setup ensures</p>]]></description><link>https://blog.driftingruby.com/running-meilisearch/</link><guid isPermaLink="false">66a05db193cad700012fa595</guid><dc:creator><![CDATA[Dave Kimura]]></dc:creator><pubDate>Sat, 06 May 2023 04:00:00 GMT</pubDate><content:encoded><![CDATA[<p>MeiliSearch is a powerful, fast, and easy-to-use search engine, perfect for developers who want to implement search functionality into their applications. In this blog post, we will walk you through the process of deploying MeiliSearch with SSL encryption using Docker Compose and Nginx as a reverse proxy. This setup ensures a secure connection between your users and your MeiliSearch instance.</p><h2 id="prerequisites">Prerequisites</h2><p>Before you begin, make sure you have the following tools installed on your system:</p><p>Docker: Ensure Docker is installed and running on your machine. You can follow the installation guide on the official Docker website.</p><p>Docker Compose: Make sure Docker Compose is installed. You can find installation instructions on the official Docker Compose website.</p><p>OpenSSL: Required for generating SSL certificates. You can install OpenSSL through your package manager or download it from the official OpenSSL website.</p><h2 id="configuration">Configuration</h2><p>Create a docker-compose.yml file in your project directory and copy the following content:</p><pre><code>services:
  meilisearch:
    image: getmeili/meilisearch:v1.1
    restart: always
    ports:
      - &apos;7700:7700&apos;
    environment:
      - MEILI_MASTER_KEY=key
    volumes:
      - meili_data:/meili_data

  nginx:
    image: nginx:stable-alpine
    restart: always
    ports:
      - &apos;80:80&apos;
      - &apos;443:443&apos;
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - /home/ubuntu/cert.pem:/etc/nginx/certs/fullchain.pem
      - /home/ubuntu/key.pem:/etc/nginx/certs/privkey.pem
    depends_on:
      - meilisearch

volumes:
  meili_data:
</code></pre>
<p>This configuration file sets up two services: MeiliSearch and Nginx.</p><h2 id="meilisearch-service">Meilisearch service:</h2><p>You should replace the key value with a random string. This key will be used to secure your MeiliSearch instance. You can generate a random string using the following command:</p><pre><code>openssl rand -hex 16
</code></pre>
<h2 id="nginx-service">Nginx service:</h2><p>There&apos;s a bit more config to do with the Nginx service. We can create a ssl certificate using OpenSSL. We will use this certificate to enable SSL encryption for our MeiliSearch instance. You can generate a self-signed certificate using the following command:</p><pre><code>openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -subj &quot;/CN=yourdomainname&quot; -nodes
</code></pre>
<p>Then we can create a configuration file called nginx.conf and copy the following content:</p><pre><code>events {
    worker_connections 1024;
}
http {
    server {
        listen 80;
        server_name yourdomainname;
        return 301 https://$host$request_uri;
    }
    server {
        listen 443 ssl;
        server_name yourdomainname;
        ssl_certificate /etc/nginx/certs/fullchain.pem;
        ssl_certificate_key /etc/nginx/certs/privkey.pem;
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers &apos;TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384&apos;;
        location / {
            proxy_pass http://meilisearch:7700;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}
</code></pre>
<p>You&apos;ll want to update the appropriate values for your domain name and SSL certificate paths.</p><h2 id="running-the-application">Running the application</h2><p>Now that we have our configuration files ready, we can run the application using the following command:</p><pre><code>docker compose up -d
</code></pre>
<p>This will start both services in the background. You can check the status of your services using the following command:</p><pre><code>docker compose ps
</code></pre>
<p>You should see something like this:</p><pre><code>Name                Command               State           Ports
--------------------------------------------------------------------------------
meilisearch         ...                   Up              ..
nginx               ...                   Up              ..
</code></pre>
<p>The&#xA0;<code>-d</code>&#xA0;in the&#xA0;<code>docker compose up -d</code>&#xA0;command tells Docker to run the services in the background.</p><p>This will also cause the services to restart automatically if they crash or if you reboot the machine.</p><p>Thanks for reading!</p>]]></content:encoded></item><item><title><![CDATA[Ruby 3.2.1 with YJIT]]></title><description><![CDATA[<p>I&apos;ve been using Ruby 3.2.1 + YJIT for some time now and overall, I&apos;m seeing anywhere from 10-20% performance improvements over running Ruby 3.0.5. Sorry, I don&apos;t have an apple-to-apple comparison for just YJIT vs non-YJIT. However, the performance improvements are</p>]]></description><link>https://blog.driftingruby.com/ruby-3-2-1-with-yjit/</link><guid isPermaLink="false">66a05d3393cad700012fa57e</guid><category><![CDATA[Development]]></category><dc:creator><![CDATA[Dave Kimura]]></dc:creator><pubDate>Sat, 04 Mar 2023 05:00:00 GMT</pubDate><content:encoded><![CDATA[<p>I&apos;ve been using Ruby 3.2.1 + YJIT for some time now and overall, I&apos;m seeing anywhere from 10-20% performance improvements over running Ruby 3.0.5. Sorry, I don&apos;t have an apple-to-apple comparison for just YJIT vs non-YJIT. However, the performance improvements are real world scenarios on a Ruby on Rails 7.0.4 application.</p><p>So, I&apos;ve recently formatted my primary desktop which is running macOS 13.2.1 and figured that I would document my steps on my preferred way of installing Ruby as well as getting YJIT to work. It&apos;s important to note that this is also an Apple Silicon machine. These steps should work regardless of which Apple Silicon you&apos;re using.</p><p>First, I&apos;ll install homebrew. This will automatically install the Command Line Tools for Xcode.</p><pre><code>/bin/bash -c &quot;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)&quot;
</code></pre>
<p>You&apos;ll then be prompted to run some additional commands.</p><pre><code>(echo; echo &apos;eval &quot;$(/opt/homebrew/bin/brew shellenv)&quot;&apos;) &gt;&gt; /Users/$(whoami)/.zprofile
eval &quot;$(/opt/homebrew/bin/brew shellenv)&quot;
</code></pre>
<p>We can then start our install of Ruby. However, there are a few things that we should do first. Firstly, we need to select a manager for our Ruby interpreter. I normally use RVM as it&apos;s what I used for the longest time. It works well and I never had any complaints. However, with Ruby 3.2.1, we&apos;re also going to need an installation of Rust if we want to enable YJIT. So, instead of RVM, I&apos;m going to be using ASDF.</p><pre><code>brew install asdf
echo -e &quot;\n. $(brew --prefix asdf)/libexec/asdf.sh&quot; &gt;&gt; ${ZDOTDIR:-~}/.zshrc
source ~/.zshrc
</code></pre>
<p>Now that we have a manager for our Ruby and Rust, we can now proceed to install Rust.</p><pre><code>asdf plugin add rust
asdf install rust latest
asdf global rust latest
</code></pre>
<p>Now that Rust is installed, we can proceed with our Ruby installation. Normally there are some dependencies that we need to handle, but luckily, asdf will take care of that for us.</p><pre><code>asdf plugin add ruby
</code></pre>
<p>However, there is a strange thing with the mapping of the libraries so if we were to try and install Ruby right now, it would fail. So let&apos;s take care of this. This is around the libyaml package and we need to set some environment variables for this. I&apos;ve added all four of these to my ~/.zshrc so that Ruby can install properly and YJIT is enabled out of the box.</p><pre><code>brew install openssl@3 readline libyaml gmp
echo &apos;export LDFLAGS=&quot;-L$(brew --prefix libyaml)/lib&quot;&apos; &gt;&gt; ~/.zshrc
echo &apos;export CPPFLAGS=&quot;-I$(brew --prefix libyaml)/include&quot;&apos; &gt;&gt; ~/.zshrc
echo &apos;export RUBY_YJIT_ENABLE=1&apos; &gt;&gt; ~/.zshrc
echo &apos;export RUBY_CONFIGURE_OPTS=--enable-yjit&apos; &gt;&gt; ~/.zshrc
source ~/.zshrc
asdf install ruby 3.2.1
asdf global ruby 3.2.1
</code></pre>
<p>I had to restart my terminal, but once I did, everything seemed to work as expected.</p><pre><code>ruby -v
ruby 3.2.1 (2023-02-08 revision 31819e82c8) +YJIT [arm64-darwin22]
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Pushing Code to Multiple Repositories with Git]]></title><description><![CDATA[<p>Have you ever found yourself needing to publish your code to multiple repositories? Perhaps you have a static site hosted on Github Pages and want to have multiple domains point to it. Or maybe you manage multiple technology-related blogs and need to consolidate your content into a single blog, while</p>]]></description><link>https://blog.driftingruby.com/pushing-code-to-multiple-repositories-with-git/</link><guid isPermaLink="false">66a05cc293cad700012fa56c</guid><category><![CDATA[Development]]></category><dc:creator><![CDATA[Dave Kimura]]></dc:creator><pubDate>Thu, 23 Feb 2023 05:00:00 GMT</pubDate><content:encoded><![CDATA[<p>Have you ever found yourself needing to publish your code to multiple repositories? Perhaps you have a static site hosted on Github Pages and want to have multiple domains point to it. Or maybe you manage multiple technology-related blogs and need to consolidate your content into a single blog, while still updating it on multiple domain names. In this article, we will explore a solution to push code to multiple repositories with minimal effort using Git.</p><p>As mentioned earlier, there are scenarios where you need to update your code on multiple repositories, and doing so manually can be time-consuming and error-prone. For instance, you may have a blog that you want to host on multiple domains, but instead of redirecting users to a new site, you prefer to update the content on both domains simultaneously. In such cases, you can use Git to push changes to multiple repositories with just a few commands.</p><p>To push code to multiple repositories, you first need to initialize your Git repository and push the changes to one of the repositories. In our example, let&apos;s say we push the changes to the repository kobaltz/blog-kobaltz.git. Now, we want to push the same code to another repository kobaltz/blog-driftingruby.git. To achieve this, we can add another push URL to our .git/config file using the following command:</p><pre><code>git remote set-url --add --push origin git@github.com:kobaltz/blog-driftingruby.git
</code></pre>
<p>This command adds a new push URL to the origin remote and sets it to the git@github.com:kobaltz/blog-driftingruby.git repository. Now, whenever you commit your changes and push them, Git will push the changes to both repositories.</p><p>You can verify that the push URL has been added by running the following command:</p><pre><code>git remote -v
</code></pre>
<p>This command will display the two push URLs under the origin remote, as shown below:</p><pre><code>origin  git@github.com:kobaltz/blog-kimura.git (fetch)
origin  git@github.com:kobaltz/blog-kimura.git (push)
origin  git@github.com:kobaltz/blog-driftingruby.git (push)
</code></pre>
<p>Now, your code is being pushed to two repositories with minimal effort. If you ever want to remove one of the push URLs, you can do so by using the following command:</p><pre><code>git remote set-url --delete --push origin git@github.com:kobaltz/blog-driftingruby.git
</code></pre>
<p>In this article, we&apos;ve explored a simple solution to push code to multiple repositories using Git. By adding a new push URL to the origin remote, we can push changes to multiple repositories with minimal effort. This technique can be useful in scenarios where you need to update your code on multiple domains or repositories. And, remember that you can use different services as well for the push URLs; one pointing to GitHub and another pointing to GitLab.</p><p>By consolidating your code base and keeping multiple repositories up to date, you can simplify your workflow and save time. Give this approach a try and see how it works for you. Happy coding!</p>]]></content:encoded></item><item><title><![CDATA[Docker Builds are slow on M1]]></title><description><![CDATA[<p>This is a neat Docker trick for those who have an ARM development machine (Apple M1), but sometimes need to build x86/amd64 images locally to push up to a registry.</p><p>Sure, having a CI/CD platform to do this is probably ideal, but for little programs or just sometimes</p>]]></description><link>https://blog.driftingruby.com/docker-builds-are-slow-on-m1/</link><guid isPermaLink="false">66a05b8493cad700012fa557</guid><category><![CDATA[Development]]></category><dc:creator><![CDATA[Dave Kimura]]></dc:creator><pubDate>Sun, 10 Apr 2022 01:41:00 GMT</pubDate><content:encoded><![CDATA[<p>This is a neat Docker trick for those who have an ARM development machine (Apple M1), but sometimes need to build x86/amd64 images locally to push up to a registry.</p><p>Sure, having a CI/CD platform to do this is probably ideal, but for little programs or just sometimes in general, it may be handy to know this.</p><p>If you have a spare Intel machine laying around, you can turn it into a build server. Get something like&#xA0;<a href="https://docs.docker.com/engine/install/ubuntu/?ref=blog.driftingruby.com">Ubuntu installed</a>&#xA0;on there and install the latest Docker. On your development machine,&#xA0;<a href="https://www.ssh.com/academy/ssh/copy-id?ref=blog.driftingruby.com">copy your publish SSH key over to this build server.</a></p><p>So now you you have your server set up and you want to build your Docker image for an x86/amd64 platform. Typically, you would run a command like this below to target the platform.</p><pre><code>docker buildx build -f Dockerfile --platform linux/amd64 .
</code></pre>
<p>And this will work, but what you&apos;ll notice is that this is an extremely slow process. The Apple Silicon chips are amazing and are the fastest machines I&apos;ve ever used. However, when emulating the x86 instructions to build a docker image, it takes such a long time. I&apos;ve seen this take over an hour on larger and more complex projects.</p><p>Just as an example. Here we have a very simple Ruby on Rails application that has little moving parts. I&apos;m using things like&#xA0;<code>esbuild</code>&#xA0;and&#xA0;<code>css-bundling</code>, but nothing fancy. On the M1 chip, it took over 250 seconds to build the image.</p><pre><code># Apple Silicon
[+] Building 316.4s (23/23) FINISHED
</code></pre>
<p>However, on a AMD 5900X server, I have a Virtual Machine running on there which has Docker installed. Running the exact same project on there took much less time.</p><pre><code># AMD 5900X
[+] Building 62.3s (24/24) FINISHED
</code></pre>
<p>So, the main concern here is that I do not want to interrupt my normal process on how I build images or handle things. It would be a pain to push up my code, ssh into the build server, pull it down, and then build the image. This is a lot of steps, but luckily there is a MUCH easier way to do this.</p><p>Docker&apos;s buildx build command has a flag that we can specify a specific builder.</p><p>So, we can create this builder on our local machine. The nice part about this creation is that it is idempotent, so you can run this command many times without changing the result. All we have to do is to create a builder profile and in this case I have named it&#xA0;<code>amd64_builder</code>. &#xA0;Since this builder is a pool of resources, I give a name to for the VM. I&apos;m also specifying the platform that I&apos;m building against and then pass in the SSH url for my builder machine.</p><pre><code>docker buildx create \
  --name amd64_builder \
  --node linux_amd64_builder \
  --platform linux/amd64 \
  ssh://USERNAME@IP_ADDRESS_OF_BUILDER
</code></pre>
<p>Now, I can build and push the image to the registry. The magical flag that we&apos;ll use is&#xA0;<code>--builder</code>&#xA0;as we can now specify the builder VM. The rest of the buildx command is the same as we would expect.</p><pre><code>docker buildx build \
  --builder amd64_builder \
  --tag USERNAME/REPONAME:TAG \
  --push .
</code></pre>
<p>In some cases, I&apos;ve seen this go almost 10x faster than the amd64 emulation on the M1 chip. If you have a spare Intel/AMD machine laying around, this may be a worthwhile adventure.</p>]]></content:encoded></item><item><title><![CDATA[Transferring Files]]></title><description><![CDATA[<p>Sometimes we get into situations where we need to remotely transfer a file. This file could be large or it could be tiny. This file may be sensitive or could be of little importance. Regardless, sending this file over to a remote location could still have its challenges. What if</p>]]></description><link>https://blog.driftingruby.com/transferring-files/</link><guid isPermaLink="false">66a05a8293cad700012fa53c</guid><category><![CDATA[Development]]></category><dc:creator><![CDATA[Dave Kimura]]></dc:creator><pubDate>Sat, 09 Apr 2022 01:39:00 GMT</pubDate><content:encoded><![CDATA[<p>Sometimes we get into situations where we need to remotely transfer a file. This file could be large or it could be tiny. This file may be sensitive or could be of little importance. Regardless, sending this file over to a remote location could still have its challenges. What if the file is coming from a headless server? Perhaps you have some logs that you want to inspect on your local machine instead of parsing through them remotely in an ssh session. This is where&#xA0;<a href="https://magic-wormhole.readthedocs.io/?ref=blog.driftingruby.com">magic-wormhole</a>&#xA0;comes into play.</p><h2 id="installation">Installation</h2><pre><code class="language-bash">brew install magic-wormhole
</code></pre>
<h2 id="linux-debianubuntu">Linux (Debian/Ubuntu)</h2><pre><code class="language-bash">sudo apt install magic-wormhole
</code></pre>
<h2 id="usage">Usage</h2><p>To send a file, you simply use the wormhole command and specify which file you wish to send.</p><pre><code class="language-bash">&#x279C;  ~ wormhole send server.log
Sending 1.1 GB file named &apos;server.log&apos;
Wormhole code is: 5-hydraulic-snowslide
On the other computer, please run:

wormhole receive 5-hydraulic-snowslide
</code></pre>
<p>Now that we have specified which file we want to send, we&apos;ve been provided with a &quot;wormhole code&quot; that we can use on the receiving end.</p><pre><code class="language-bash">wormhole receive 5-hydraulic-snowslide
</code></pre>
<p>And that&apos;s all we have to do! On the receiving computer, we&apos;ve downloaded the requested file! In the past, I would have used&#xA0;<code>scp</code>&#xA0;or&#xA0;<code>ftp</code>&#xA0;but remembering the syntax, while isn&apos;t too difficult, can prove to be challenging at times especially if nonstandard ports are used or if ports aren&apos;t opened up on the firewall to make the connection.</p><pre><code class="language-bash">&#x279C;  ~ wormhole receive 5-hydraulic-snowslide
Receiving file (1.1 GB) into: server.log
ok? (y/N): y
Receiving (-&gt;tcp:some-external-ip:65250)..
 80%|&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x258F;               | 859M/1.07G [00:04&lt;00:01, 206MB/s]
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Let's Encrypt Wildcard Cert]]></title><description><![CDATA[<p>I&apos;m writing this more as documentation for myself as I have several domains which I often use as playgrounds for certain things. This could be a kubernetes environment or a docker cluster where I have several applications deployed, but don&apos;t want to set up an nginx</p>]]></description><link>https://blog.driftingruby.com/lets-encrypt-wildcard-cert/</link><guid isPermaLink="false">66a059f593cad700012fa529</guid><category><![CDATA[Development]]></category><dc:creator><![CDATA[Dave Kimura]]></dc:creator><pubDate>Wed, 10 Feb 2021 05:00:00 GMT</pubDate><content:encoded><![CDATA[<p>I&apos;m writing this more as documentation for myself as I have several domains which I often use as playgrounds for certain things. This could be a kubernetes environment or a docker cluster where I have several applications deployed, but don&apos;t want to set up an nginx site for each one of the apps that I deploy.</p><p>Recently, I found out about Portainer for managing a docker environment. Think of this as a kubernetes-lite. This isn&apos;t good for any kind of production use case, but does have some benefits for at home management of a local docker instance.</p><figure class="kg-card kg-image-card"><img src="https://blog.driftingruby.com/content/images/2024/07/PNG-image-90C9A493911C-1.png" class="kg-image" alt loading="lazy" width="1000" height="613" srcset="https://blog.driftingruby.com/content/images/size/w600/2024/07/PNG-image-90C9A493911C-1.png 600w, https://blog.driftingruby.com/content/images/2024/07/PNG-image-90C9A493911C-1.png 1000w" sizes="(min-width: 720px) 720px"></figure><p>So in this illustration, we have a request coming in on 443. This request could be using the domain name&#xA0;<code>c1.mydomain.com</code>&#xA0;and there could be another request coming in from&#xA0;<code>c2.mydomain.com</code>.</p><p>Previously, you would have a certificate for both subdomains and would need to set up the appropriate port forwarding for each subdomain to the appropriate server. This is a huge pain, so, instead, this is where a wildcard certificate can come in handy.</p><p>In this particular scenario, I have an nginx proxy which will be used to route all of the traffic coming in to my network. This is handy because I can use this reverse proxy for SSL termination and also routing the traffic to various servers based on the domain name. This doesn&apos;t have to be a powerful server. You could even use a Raspberry Pi to route this traffic. Again, this is for a home server and not really production grade.</p><p>You will need some prerequisites. And I also use CloudFlare&apos;s DNS for handling these wildcard domains. This is nice because certbot and cloudflare play pretty nicely to automatically verify the challenges via their API.</p><pre><code class="language-bash">apt install certbot letsencrypt python3-certbot-dns-cloudflare
</code></pre>
<p>If you already have certbot and the necessary extensions installed, you can simply run this script to get a wildcard certificate.</p><pre><code class="language-bash">sudo certbot certonly \
     --cert-name mydomain.com \
     --dns-cloudflare \
     --dns-cloudflare-credentials /etc/letsencrypt/cloudflareapi.cfg \
     --server https://acme-v02.api.letsencrypt.org/directory \
     -d &quot;*.mydomain.com&quot; \
     -d mydomain.com
</code></pre>
<p>So, now we can configure our nginx proxy to take any request from this domain and forward it over to my docker host.</p><pre><code class="language-bash">server {
  server_name *.mydomain.com;
  server_name ~^(?&lt;subdomain&gt;.+)\.mydomain\.com$;
  add_header Strict-Transport-Security &quot;max-age=31536000; includeSubDomains&quot; always;
  # proxy_next_upstream error timeout http_502;
  location / {
    proxy_pass http://DOCKER_HOST_IP_ADDRESS;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_buffering off;
  }
  listen 443 ssl;
  ssl_certificate /etc/letsencrypt/live/mydomain.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/mydomain.com/privkey.pem;
  include /etc/letsencrypt/options-ssl-nginx.conf;
  ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}

server {
  return 301 https://$host$request_uri;
  listen 80 ;
  listen [::]:80 ;
  server_name *.mydomain.com;
  server_name ~^(?&lt;subdomain&gt;.+)\.mydomain\.com$;
}
</code></pre>
<p>In another blog article, I&apos;ll describe how I set up the Docker host to take in these requests from the nginx server.</p>]]></content:encoded></item></channel></rss>