<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Mokhtar</title>
    <description>The latest articles on Forem by Mokhtar (@m5r).</description>
    <link>https://forem.com/m5r</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F335910%2F5e43ef12-0595-4094-893b-2b4c98e0eb04.png</url>
      <title>Forem: Mokhtar</title>
      <link>https://forem.com/m5r</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/m5r"/>
    <language>en</language>
    <item>
      <title>Self-hosting Quirrel</title>
      <dc:creator>Mokhtar</dc:creator>
      <pubDate>Sun, 20 Feb 2022 23:11:40 +0000</pubDate>
      <link>https://forem.com/remixtape/self-hosting-quirrel-5af7</link>
      <guid>https://forem.com/remixtape/self-hosting-quirrel-5af7</guid>
      <description>&lt;p&gt;Quirrel has recently been &lt;a href="https://dev.to/quirrel/quirrel-is-acquired-and-i-am-joining-netlify-dha"&gt;acquired by Netlify&lt;/a&gt; and this is exciting news for Simon &lt;a class="mentioned-user" href="https://dev.to/skn0tt"&gt;@skn0tt&lt;/a&gt;, Quirrel's author, who joined their team!&lt;/p&gt;

&lt;p&gt;The hosted service &lt;a href="https://quirrel.dev" rel="noopener noreferrer"&gt;quirrel.dev&lt;/a&gt; is being deprecated later this year and has stopped taking new sign-ups. This leaves a hole in the job queuing as a service niche but thankfully, Quirrel is and will stay open-source which makes it possible to switch to self-hosted instances.&lt;/p&gt;

&lt;p&gt;I've been running my own self-hosted Quirrel instance for both &lt;a href="https://www.shellphone.app" rel="noopener noreferrer"&gt;Shellphone&lt;/a&gt; and &lt;a href="https://www.remixtape.dev" rel="noopener noreferrer"&gt;Remixtape&lt;/a&gt; on &lt;a href="https://www.fly.io" rel="noopener noreferrer"&gt;Fly.io&lt;/a&gt; and this blog post is here to help you with this.&lt;br&gt;
In this guide, I won't go over setting up &lt;a href="https://fly.io/docs/flyctl/" rel="noopener noreferrer"&gt;flyctl&lt;/a&gt; nor &lt;a href="https://docs.quirrel.dev/api/queue" rel="noopener noreferrer"&gt;using Quirrel&lt;/a&gt; as their respective documentations would do a much better job than I at that.&lt;/p&gt;



&lt;p&gt;TL;DR:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deploy Redis&lt;/li&gt;
&lt;li&gt;Deploy Quirrel&lt;/li&gt;
&lt;li&gt;Generate your app's Quirrel token&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Deployment files and instructions can be found in &lt;a href="https://github.com/capsule-corp/selfhosted-quirrel" rel="noopener noreferrer"&gt;this repository&lt;/a&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  Deploying Redis
&lt;/h2&gt;

&lt;p&gt;First, set up the Fly app to be deployed with the following Dockerfile and shell script named &lt;code&gt;start-redis-server.sh&lt;/code&gt;. The script starts Redis with persistent storage which is needed for Quirrel.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; redis:alpine&lt;/span&gt;

&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="s"&gt; start-redis-server.sh /usr/bin/&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /usr/bin/start-redis-server.sh

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["start-redis-server.sh"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;
sysctl vm.overcommit_memory&lt;span class="o"&gt;=&lt;/span&gt;1
sysctl net.core.somaxconn&lt;span class="o"&gt;=&lt;/span&gt;1024
redis-server &lt;span class="nt"&gt;--requirepass&lt;/span&gt; &lt;span class="nv"&gt;$REDIS_PASSWORD&lt;/span&gt; &lt;span class="nt"&gt;--dir&lt;/span&gt; /data/ &lt;span class="nt"&gt;--appendonly&lt;/span&gt; &lt;span class="nb"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, initialize the Fly app that will host your Redis instance with the following &lt;code&gt;fly.toml&lt;/code&gt; file. It references the &lt;code&gt;redis_data&lt;/code&gt; Fly storage volume we haven't created yet which is needed for persistent storage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"quirrel-redis"&lt;/span&gt;

&lt;span class="nn"&gt;[[mounts]]&lt;/span&gt;
  &lt;span class="py"&gt;destination&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/data"&lt;/span&gt;
  &lt;span class="py"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"redis_data"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flyctl launch &lt;span class="nt"&gt;--name&lt;/span&gt; quirrel-redis &lt;span class="nt"&gt;--no-deploy&lt;/span&gt; &lt;span class="nt"&gt;--copy-config&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that Fly knows about your Redis app, let's deploy the storage volume. I recommend deploying it in the same region as your Redis instance. In my case, it's &lt;code&gt;CDG&lt;/code&gt; Paris, France.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flyctl volumes create redis_data &lt;span class="nt"&gt;--region&lt;/span&gt; cdg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Secure Redis with a strong random password. I usually use &lt;code&gt;openssl&lt;/code&gt; to generate this kind of password. Keep it somewhere safe, we will need it to deploy Quirrel later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 16 &lt;span class="c"&gt;# copy its output&lt;/span&gt;
&lt;span class="c"&gt;# you can use this alternative below if you can't use openssl&lt;/span&gt;
&lt;span class="c"&gt;# node -e "console.log(crypto.randomBytes(16).toString('hex'))"&lt;/span&gt;
flyctl secrets &lt;span class="nb"&gt;set &lt;/span&gt;&lt;span class="nv"&gt;REDIS_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;paste_redis_password_here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can deploy Redis.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flyctl deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deploying Quirrel
&lt;/h2&gt;

&lt;p&gt;The next step is to deploy Quirrel with the following &lt;code&gt;fly.toml&lt;/code&gt; file. It uses the Quirrel Docker image published to &lt;a href="https://github.com/quirrel-dev/quirrel/pkgs/container/quirrel/" rel="noopener noreferrer"&gt;Quirrel's GitHub container registry&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"quirrel"&lt;/span&gt;

&lt;span class="nn"&gt;[build]&lt;/span&gt;
  &lt;span class="py"&gt;image&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"ghcr.io/quirrel-dev/quirrel:main"&lt;/span&gt;

&lt;span class="nn"&gt;[[services]]&lt;/span&gt;
  &lt;span class="py"&gt;internal_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9181&lt;/span&gt;
  &lt;span class="py"&gt;protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"tcp"&lt;/span&gt;

  &lt;span class="nn"&gt;[services.concurrency]&lt;/span&gt;
    &lt;span class="py"&gt;hard_limit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;
    &lt;span class="py"&gt;soft_limit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
    &lt;span class="py"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"connections"&lt;/span&gt;

  &lt;span class="nn"&gt;[[services.http_checks]]&lt;/span&gt;
    &lt;span class="py"&gt;interval&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"10s"&lt;/span&gt;
    &lt;span class="py"&gt;method&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"get"&lt;/span&gt;
    &lt;span class="py"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/health"&lt;/span&gt;
    &lt;span class="py"&gt;protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"http"&lt;/span&gt;
    &lt;span class="py"&gt;timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2s"&lt;/span&gt;

  &lt;span class="nn"&gt;[[services.ports]]&lt;/span&gt;
    &lt;span class="py"&gt;handlers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="py"&gt;port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;

  &lt;span class="nn"&gt;[[services.ports]]&lt;/span&gt;
    &lt;span class="py"&gt;handlers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"tls"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="py"&gt;port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flyctl launch &lt;span class="nt"&gt;--name&lt;/span&gt; quirrel &lt;span class="nt"&gt;--no-deploy&lt;/span&gt; &lt;span class="nt"&gt;--copy-config&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that Fly knows about your Quirrel app, it's time to set up its environment variables, starting with Quirrel's secret passphrase. Like Redis' password, I'm using &lt;code&gt;openssl&lt;/code&gt; to generate it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 16 &lt;span class="c"&gt;# copy its output&lt;/span&gt;
&lt;span class="c"&gt;# you can use this alternative below if you can't use openssl&lt;/span&gt;
&lt;span class="c"&gt;# node -e "console.log(crypto.randomBytes(16).toString('hex'))"&lt;/span&gt;
flyctl secrets &lt;span class="nb"&gt;set &lt;/span&gt;&lt;span class="nv"&gt;PASSPHRASES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;paste_quirrel_passphrase_here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we need to tell Quirrel how to connect to our Redis instance, it's time to use that Redis password saved earlier! We're using our Fly app's &lt;a href="https://fly.io/docs/reference/private-networking/#fly-internal-addresses" rel="noopener noreferrer"&gt;.internal address&lt;/a&gt; which is formatted like this &lt;code&gt;{region}.{appName}.internal&lt;/code&gt;. Fly servers use IPv6 only so make sure to append &lt;code&gt;?family=6&lt;/code&gt; to connect to Redis over IPv6.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flyctl secrets &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="s2"&gt;"REDIS_URL=redis://:paste_redis_password_here@cdg.quirrel-redis.internal:6379?family=6"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now, we can deploy Quirrel.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flyctl deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Connect your app to your Quirrel instance
&lt;/h2&gt;

&lt;p&gt;The last step is to connect your app to your freshly deployed Quirrel instance. First, let's acquire a Quirrel token from your instance. Note you can retrieve your instance's public URL from your &lt;a href="https://fly.io/apps" rel="noopener noreferrer"&gt;Fly dashboard&lt;/a&gt;. Save the token returned from this command for your app, we're going to need it for the next step.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--user&lt;/span&gt; ignored:paste_quirrel_passphrase_here &lt;span class="nt"&gt;-X&lt;/span&gt; PUT https://quirrel.fly.dev/tokens/exampleapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, configure and deploy your application with the following environment variables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;QUIRREL_API_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://quirrel.fly.dev &lt;span class="c"&gt;# your Quirrel instance's public URL&lt;/span&gt;
&lt;span class="nv"&gt;QUIRREL_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;paste_quirrel_token_here &lt;span class="c"&gt;# your Quirrel token previously generated&lt;/span&gt;
&lt;span class="nv"&gt;QUIRREL_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;www.exampleapp.com &lt;span class="c"&gt;# your app's URL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bonus: using &lt;a href="https://ui.quirrel.dev" rel="noopener noreferrer"&gt;ui.quirrel.dev&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Quirrel provides a development UI that can connect to any Quirrel instance and allow you to monitor queued jobs. A public version is hosted at &lt;a href="https://ui.quirrel.dev" rel="noopener noreferrer"&gt;ui.quirrel.dev&lt;/a&gt; but your Quirrel instance's public URL also hosts this development UI!&lt;/p&gt;

&lt;p&gt;To connect to your self-hosted Quirrel instance, click on the dropdown menu next to the Quirrel logo in the header to open a connection modal.&lt;br&gt;
Fill it out with your instance's public URL, your app's token, and the Quirrel passphrase you generated earlier. The modal should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fapg52u9vv7b36l418ltk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fapg52u9vv7b36l418ltk.png" alt="Quirrel connection modal filled out"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to play around, queue a job, and see it appear in real-time in this development UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing notes
&lt;/h2&gt;

&lt;p&gt;Congrats, you did it! You've deployed a Redis instance, a Quirrel instance, and configured your production app to use your self-hosted Quirrel instance.&lt;br&gt;
Shout-out to Simon &lt;a class="mentioned-user" href="https://dev.to/skn0tt"&gt;@skn0tt&lt;/a&gt; for building this cool piece of software! I hope this helps Quirrel users transition to self-hosting.&lt;/p&gt;

&lt;p&gt;Want to get a headstart to build your next SaaS? I'm working on &lt;a href="https://www.remixtape.dev/devto" rel="noopener noreferrer"&gt;Remixtape&lt;/a&gt;, the modern Remix💿 boilerplate that includes everything you need to build better websites. Skip implementing standard functionality like background jobs, authentication, account management, sessions, subscription payments, teams, transactional emails... 😮‍💨 It gives you the solid foundation you need to build great web apps today and scale tomorrow.&lt;/p&gt;

</description>
      <category>quirrel</category>
      <category>selfhost</category>
      <category>fly</category>
    </item>
  </channel>
</rss>
