<?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"><channel><title><![CDATA[Alpsify]]></title><description><![CDATA[Alpsify]]></description><link>https://blog.alpsify.com</link><generator>RSS for Node</generator><lastBuildDate>Wed, 07 Oct 2026 22:38:06 GMT</lastBuildDate><atom:link href="https://blog.alpsify.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Deploy Cal.com with Dokploy]]></title><description><![CDATA[Be aware that Cal.com does not officially support deployment through Docker, as they mention in the documentation:

The Docker configuration for Cal is an effort powered by people within the community. Cal.com, Inc. does not provide official support ...]]></description><link>https://blog.alpsify.com/deploy-calcom-on-vps-with-dokploy</link><guid isPermaLink="true">https://blog.alpsify.com/deploy-calcom-on-vps-with-dokploy</guid><category><![CDATA[dokploy]]></category><category><![CDATA[Cal.com]]></category><category><![CDATA[Docker]]></category><dc:creator><![CDATA[Nathan De Pachtere]]></dc:creator><pubDate>Tue, 15 Oct 2024 07:43:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1728978159815/d200c16a-4e27-46bf-b6ad-84b99c8dab77.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Be aware that Cal.com does not officially support deployment through Docker, as they mention in the documentation:</p>
<blockquote>
<p>The Docker configuration for Cal is an effort powered by people within the community. <a target="_blank" href="http://Cal.com">Cal.com</a>, Inc. does not provide official support for Docker, but we will accept fixes and documentation. Use at your own risk.</p>
</blockquote>
<p><strong>Requirements</strong></p>
<ol>
<li><p>Have a VPS: check this page <a target="_blank" href="https://docs.dokploy.com/en/docs/core/get-started/installation">https://docs.dokploy.com/en/docs/core/get-started/installation</a>. If it’s your first time using Dokploy, go with Hostinger as they will install Dokploy for you on your VPS.</p>
</li>
<li><p>Have a domain: we are going to deploy under cal.yourdomain.com</p>
</li>
</ol>
<p><strong>Disclaimer</strong></p>
<p>We are not using the template provided by Dokploy to self-host Cal.com because it is outdated.</p>
<h2 id="heading-add-a-project-to-your-dokploy-instance">Add a project to your Dokploy instance</h2>
<p>It's simple: go to the project section and click on Create Project.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728975731982/b1070911-7600-44dc-9899-6d7f5daaf6b4.png" alt class="image--center mx-auto" /></p>
<p>Then, add a name and a description, click "Create," and enter the project by clicking on it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728975841343/50567d3b-4e91-4d87-923e-8f3665485847.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-create-and-configure-a-service">Create and configure a service</h2>
<p>Inside your project, you can create a service by clicking "Create Service" in the top right corner.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728975957742/e43403ad-667d-4814-9ae3-f7be4b97a28b.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-docker-compose">Docker-compose</h3>
<p>In the General tab, under Provider, click on Raw and then paste the code inside.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">services:</span>
  <span class="hljs-attr">database:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">postgres:16</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">dokploy-network</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">calcom-data:/var/lib/postgresql/data</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_USER=${POSTGRES_USER}</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_PASSWORD=${POSTGRES_PASSWORD}</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_DB=${POSTGRES_DB}</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DATABASE_URL=${DATABASE_URL}</span>

  <span class="hljs-attr">calcom:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">calcom/cal.com:latest</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">database</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">LICENSE=agree</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">NEXTAUTH_SECRET=${NEXTAUTH_SECRET}</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">CALENDSO_ENCRYPTION_KEY=${CALENDSO_ENCRYPTION_KEY}</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DATABASE_URL=${DATABASE_URL}</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DATABASE_DIRECT_URL=${DATABASE_URL}</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">NEXT_PUBLIC_WEBAPP_URL=${NEXT_PUBLIC_WEBAPP_URL}</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">NEXT_PUBLIC_API_V2_URL=${NEXT_PUBLIC_API_V2_URL}</span>
      <span class="hljs-comment">#- GOOGLE_LOGIN_ENABLED=${GOOGLE_LOGIN_ENABLED}</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">GOOGLE_API_CREDENTIALS=${GOOGLE_API_CREDENTIALS}</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">EMAIL_FROM=${EMAIL_FROM}</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">NODE_ENV=production</span>

<span class="hljs-attr">volumes:</span>
  <span class="hljs-attr">calcom-data:</span>
</code></pre>
<p>Your service interface should look like this. 🔽</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728976112327/3fa3eeed-c1f8-4c08-b172-c42cb1f4913d.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-environment">Environment</h3>
<p>Go to the Environment tab in your service and edit the environment variables to add this:</p>
<pre><code class="lang-bash">NEXTAUTH_SECRET=gRUQErxuNskUjVhR8TTerrzgnmm4VPx78e6PBaLCFQM= <span class="hljs-comment">#generate it</span>
CALENDSO_ENCRYPTION_KEY=acu16cfdadc1996h9c6df109ddc1e67c <span class="hljs-comment">#generate it by running 'dd if=/dev/urandom bs=1K count=1 | md5sum' </span>

CALCOM_HOST=cal.yourdomain.com
NEXT_PUBLIC_WEBAPP_URL=https://<span class="hljs-variable">${CALCOM_HOST}</span>

POSTGRES_USER=user <span class="hljs-comment">#change it</span>
POSTGRES_PASSWORD=password <span class="hljs-comment">#change it</span>
POSTGRES_DB=calendso <span class="hljs-comment">#change it</span>
DATABASE_HOST=database:5432
DATABASE_URL=postgresql://<span class="hljs-variable">${POSTGRES_USER}</span>:<span class="hljs-variable">${POSTGRES_PASSWORD}</span>@<span class="hljs-variable">${DATABASE_HOST}</span>/<span class="hljs-variable">${POSTGRES_DB}</span>

GOOGLE_API_CREDENTIALS={}
GOOGLE_LOGIN_ENABLED=<span class="hljs-literal">false</span>

EMAIL_FROM=notifications@yourdomain.com
EMAIL_FROM_NAME=yourname

EMAIL_SERVER_HOST=smtp-relay.gmail.com <span class="hljs-comment">#change it</span>
EMAIL_SERVER_PORT=587
</code></pre>
<p>Hit save, and you're all set for this section. It should look like this.🔽</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728976295165/4203aeca-3e34-4c16-9f4a-9d03811ae720.png" alt class="image--center mx-auto" /></p>
<p>The <code>CALENDSO_ENCRYPTION_KEY</code> needs to be this size to enable 2FA for administrator access in the Cal app. You will see an orange banner at the top when connecting for the first time.</p>
<p><code>GOOGLE_API_CREDENTIALS</code> and <code>GOOGLE_LOGIN_ENABLED</code> are variables used if you want to connect your Google Calendar to your Cal app. Just follow this tutorial to do it: <a target="_blank" href="https://cal.com/docs/self-hosting/apps/install-apps/google">https://cal.com/docs/self-hosting/apps/install-apps/google</a></p>
<h3 id="heading-domain">Domain</h3>
<p>Go to the Domains tab and add your domain that matches the CALCOM_HOST in the environment.</p>
<p>The configuration is:</p>
<ul>
<li><p>Service Name : calcom</p>
</li>
<li><p>Host : cal.yourdomain.com</p>
</li>
<li><p>Path : /</p>
</li>
<li><p>Container Port : 3000</p>
</li>
<li><p>HTTPS : ✅</p>
</li>
<li><p>Certificate : Letsencrypt</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728976625596/eeb7e4ed-595a-4a3f-a433-1aee0f22d9fa.png" alt class="image--center mx-auto" /></p>
<p>Then delete the default Traefik domain, as it's not needed.</p>
<p>Now, go to the DNS settings of your domain and add a record for your subdomain:<br />A — cal — &lt;IP_of_your_VPS&gt;</p>
<h2 id="heading-deploy">Deploy</h2>
<p>Just go back to the General section inside your service and click Deploy. It might take some time for the first deployment because it downloads the Cal.com Docker image, which is 4GB in size. So, please be patient. Once it's deployed, go to cal.yourdomain.com and complete the setup.</p>
<p>Cheers ❤️</p>
]]></content:encoded></item><item><title><![CDATA[How to use Mitt.js + Vue.js 3 + composition API + TypeScript]]></title><description><![CDATA[When you build an application with Vue.js, there comes a time when you start feeling overwhelmed and tired of the event system Vue.js offers you. I mean, it's super powerful and useful, but you can enhance your app, reuse code, organize your code, an...]]></description><link>https://blog.alpsify.com/how-to-use-mittjs-vuejs-3-composition-api-typescript</link><guid isPermaLink="true">https://blog.alpsify.com/how-to-use-mittjs-vuejs-3-composition-api-typescript</guid><category><![CDATA[alpsify]]></category><category><![CDATA[Vue.js]]></category><category><![CDATA[mitt]]></category><category><![CDATA[vue]]></category><category><![CDATA[vuejs]]></category><category><![CDATA[event listener ]]></category><dc:creator><![CDATA[Nathan De Pachtere]]></dc:creator><pubDate>Tue, 23 Jan 2024 18:16:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1706033759164/92f915d1-03a8-4b15-8b7f-b33f9cdc8946.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When you build an application with Vue.js, there comes a time when you start feeling overwhelmed and tired of the event system Vue.js offers you. I mean, it's super powerful and useful, but you can enhance your app, reuse code, organize your code, and improve maintainability by combining it with a Global Event Bus.</p>
<h2 id="heading-create-a-composable-for-your-event-bus">Create a composable for your event bus</h2>
<p>How to create a composable to serve as a Global Event Bus in your Vue.js application ? Handling events efficiently becomes essential as your Vue.js project evolves. With this composable, you'll establish a centralized event management system that simplifies event handling, enhances code organization, and boosts maintainability.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// project/useEventBus.ts</span>
<span class="hljs-keyword">import</span> mitt, { EventType, Handler } <span class="hljs-keyword">from</span> <span class="hljs-string">'mitt'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> GlobalEvents <span class="hljs-keyword">extends</span> Record&lt;EventType, unknown&gt; {}

<span class="hljs-keyword">const</span> emitter = mitt&lt;GlobalEvents&gt;();

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useEventBus</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> emitter
}
</code></pre>
<h2 id="heading-choose-a-name-for-your-event">Choose a name for your event</h2>
<p>Don't use simple strings. For consistency and usefulness, wrap them inside a constant. Whatever the shape, just wrap it!</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// project/ResourceEvent.ts</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> EVENT_Resource = <span class="hljs-string">'resource:post'</span>;
<span class="hljs-comment">// OR</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> EVENT_Resource = {
    Post: <span class="hljs-string">'resource:post'</span>,
    Patch: <span class="hljs-string">'resource:patch'</span>,
} <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>;

<span class="hljs-keyword">declare</span> <span class="hljs-keyword">module</span> './useEventBus' {
    <span class="hljs-keyword">interface</span> GlobalEvents {
        [EVENT_Resource.Post]: YourAwesomeType;
        [EVENT_Resource.Patch]: YourAwesomeType;
        <span class="hljs-comment">//...</span>
    }
}
</code></pre>
<p>In order to use that event, you have to import it wherever you need it, like so:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { EVENT_Resource } <span class="hljs-keyword">from</span> <span class="hljs-string">'project/ResourceEvent'</span>;
<span class="hljs-keyword">import</span> { useEventBus } <span class="hljs-keyword">from</span> <span class="hljs-string">'project/useEventBus'</span>;

<span class="hljs-keyword">const</span> { on, emit, off } = useEventBus();

on(EVENT_Resource.Post, <span class="hljs-function">() =&gt;</span> { ... })
emit(EVENT_Resource.Post, { ... })
off(EVENT_Resource.Post, <span class="hljs-function">() =&gt;</span> { ... })
</code></pre>
<h2 id="heading-create-the-composable-you-need-for-your-business-logic"><strong>Create the composable you need for your business logic</strong></h2>
<pre><code class="lang-typescript"><span class="hljs-comment">// project/useResourceHandler</span>
<span class="hljs-keyword">import</span> { EVENT_Resource } <span class="hljs-keyword">from</span> <span class="hljs-string">'project/ResourceEvent'</span>;
<span class="hljs-keyword">import</span> { useEventBus } <span class="hljs-keyword">from</span> <span class="hljs-string">'project/useEventBus'</span>;
<span class="hljs-keyword">import</span> { ref } <span class="hljs-keyword">from</span> <span class="hljs-string">'vue'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useResourceHandler</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> whatever = ref&lt;YourAwesomeType&gt;();

    <span class="hljs-keyword">const</span> { on, emit } = useEventBus();

    on(EVENT_Resource.Post, <span class="hljs-function">(<span class="hljs-params">payload</span>) =&gt;</span> {
        whatever.value = payload;
    });

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">triggerEvent</span>(<span class="hljs-params">obj: YourAwesomeType</span>) </span>{
        emit(EVENT_Resource.Post, obj);
    }

    <span class="hljs-keyword">return</span> {
        whatever,
    }
}
</code></pre>
<p>Now you have your business logic wrapped into a composable, so you can use it inside a component:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// project/MyAwesomeComponent.vue</span>

&lt;template&gt;
 &lt;button <span class="hljs-meta">@click</span>=<span class="hljs-string">"triggerEvent({name: 'fake'})"</span>&gt;Fake button&lt;/button&gt;
&lt;/template&gt;

&lt;script setup lang=<span class="hljs-string">"ts"</span>&gt;
<span class="hljs-keyword">import</span> { useResourceHandler } <span class="hljs-keyword">from</span> <span class="hljs-string">'project/useResourceHandler'</span>

<span class="hljs-keyword">const</span> { triggerEvent, whatever } = useResourceHandler();
&lt;/script&gt;
</code></pre>
<p>This is only for example purposes, but you can do way more and organize your code in another way that fits your needs.</p>
<p>So now you have everything set up, but if you leave it like that, you are going to have multiple handlers registered in the event bus for this event as the user goes back and forth in your interface. Each time you use your composable, you are asking to register the handler. So let's see what we need to do next.</p>
<h2 id="heading-dont-forget-to-unsubscribe">Don't forget to unsubscribe</h2>
<p>In Vue.js, you have the context of a Vue.js app, and the composition API makes things mount and unmount (component or composable). As mentioned in this section of the documentation (<a target="_blank" href="https://vuejs.org/guide/reusability/composables#side-effects">https://vuejs.org/guide/reusability/composables#side-effects</a>): WE NEED TO CLEAN UP SIDE EFFECTS. In our case, subscribing to an event is a “side effect” of our logic, and so we need to clean it up.</p>
<p>Change your composable as below:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// project/useResourceHandler</span>
<span class="hljs-keyword">import</span> { EVENT_Resource } <span class="hljs-keyword">from</span> <span class="hljs-string">'project/ResourceEvent'</span>;
<span class="hljs-keyword">import</span> { useEventBus } <span class="hljs-keyword">from</span> <span class="hljs-string">'project/useEventBus'</span>;
<span class="hljs-keyword">import</span> { ref } <span class="hljs-keyword">from</span> <span class="hljs-string">'vue'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useResourceHandler</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> whatever = ref&lt;YourAwesomeType&gt;();

    <span class="hljs-keyword">const</span> { on, emit, off } = useEventBus();

    on(EVENT_Resource.Post, <span class="hljs-function">(<span class="hljs-params">payload</span>) =&gt;</span> {
        whatever.value = payload;
    });

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">triggerEvent</span>(<span class="hljs-params">obj: YourAwesomeType</span>) </span>{
        emit(EVENT_Resource.Post, obj);
    }

    onBeforeUnmount(<span class="hljs-function">() =&gt;</span> {
        off(EVENT_Resource.post);
    });

    <span class="hljs-keyword">return</span> {
        whatever,
    }
}
</code></pre>
<p>Now you realize it's kind of boring and repetitive to unsubscribe every time you subscribe to an event somewhere in your app. So to improve that, check the next extra step.</p>
<h2 id="heading-generalize-everything-and-do-not-repeat-yourself">Generalize everything and DO NOT REPEAT YOURSELF</h2>
<p>As your app grows, you are going to have hundreds of events to manage and use, and therefore, hundreds of lines of code to subscribe and then unsubscribe. It's kind of a pain to do that everywhere. So let's refactor our code a bit.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// project/useEventBus.ts</span>
<span class="hljs-keyword">import</span> mitt, { EventType, Handler } <span class="hljs-keyword">from</span> <span class="hljs-string">'mitt'</span>;
<span class="hljs-keyword">import</span> { onBeforeUnmount } <span class="hljs-keyword">from</span> <span class="hljs-string">'vue'</span>;

<span class="hljs-keyword">const</span> emitter = mitt&lt;&gt;();

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> GlobalEvents <span class="hljs-keyword">extends</span> Record&lt;EventType, unknown&gt; {}

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useEventBus</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">extendedOn</span>&lt;<span class="hljs-title">Key</span> <span class="hljs-title">extends</span> <span class="hljs-title">keyof</span> <span class="hljs-title">GlobalEvents</span>&gt;(<span class="hljs-params">
        <span class="hljs-keyword">type</span>: Key,
        handler: Handler&lt;GlobalEvents[Key]&gt;,
    </span>): <span class="hljs-title">void</span> </span>{
        emitter.on(<span class="hljs-keyword">type</span>, handler);
        onBeforeUnmount(<span class="hljs-keyword">async</span> () =&gt; {
            emitter.off(<span class="hljs-keyword">type</span>, handler);
        });
    }

    <span class="hljs-keyword">return</span> { ...emitter, extendedOn };
}
</code></pre>
<p>You can improve that function by passing an <code>options</code> parameter to that function in order to be able to deactivate this extra behaviour if needed.</p>
<p>So now, to use it, the good thing is that we don't need to touch our components. Just go to your composable and change the function used to subscribe to an event.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// project/useResourceHandler</span>
<span class="hljs-keyword">import</span> { EVENT_Resource } <span class="hljs-keyword">from</span> <span class="hljs-string">'project/ResourceEvent'</span>;
<span class="hljs-keyword">import</span> { useEventBus } <span class="hljs-keyword">from</span> <span class="hljs-string">'project/useEventBus'</span>;
<span class="hljs-keyword">import</span> { ref } <span class="hljs-keyword">from</span> <span class="hljs-string">'vue'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useResourceHandler</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> whatever = ref&lt;YourAwesomeType&gt;();

    <span class="hljs-keyword">const</span> { extendedOn, emit, off } = useEventBus();

    extendedOn(EVENT_Resource.Post, <span class="hljs-function">(<span class="hljs-params">payload</span>) =&gt;</span> {
        whatever.value = payload;
    });

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">triggerEvent</span>(<span class="hljs-params">obj: YourAwesomeType</span>) </span>{
        emit(EVENT_Resource.Post, obj);
    }

    <span class="hljs-comment">/* REMOVE THAT
    onBeforeUnmount(() =&gt; {
        off(EVENT_Resource.post);
    });*/</span>

    <span class="hljs-keyword">return</span> {
        whatever,
    }
}
</code></pre>
<p>And voilà! You're all set up to build an app with an amazing global Event Bus that allows you to reuse logic everywhere!</p>
]]></content:encoded></item><item><title><![CDATA[GitHub Runner as container (Docker) using Moonrepo task as launcher]]></title><description><![CDATA[Prerequisites

Full understanding of Moonrepo tasks system (and a Moon project) (moonrepo.dev)
Full understanding of GitHub Actions (And a repository, with the Moon project inside; obvious? Just saying 😘)
Full understanding of Docker (and Docker ins...]]></description><link>https://blog.alpsify.com/github-runner-as-container-docker-using-moonrepo-task-as-launcher</link><guid isPermaLink="true">https://blog.alpsify.com/github-runner-as-container-docker-using-moonrepo-task-as-launcher</guid><category><![CDATA[GitHub Actions]]></category><category><![CDATA[Docker]]></category><category><![CDATA[monorepo]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[Moonrepo]]></category><dc:creator><![CDATA[Nathan De Pachtere]]></dc:creator><pubDate>Sat, 14 Oct 2023 17:43:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1697303525270/cd57a490-459a-42cd-a80e-7b79313976d8.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li>Full understanding of Moonrepo tasks system (and a Moon project) (<a target="_blank" href="https://moonrepo.dev/">moonrepo.dev</a>)</li>
<li>Full understanding of GitHub Actions (And a repository, with the Moon project inside; obvious? Just saying 😘)</li>
<li>Full understanding of Docker (and Docker installed; did you guess it?)</li>
<li>Basic knowledge of bash/sh scripting</li>
</ul>
<h2 id="heading-what-is-the-goal">What is the goal?</h2>
<p>The goal is to be able to launch a GitHub Runner as a container from any development computer (running Docker). Additionally, integrate it inside a Moonrepo monorepo repository (🤕) and have a task to launch it (or them) locally.</p>
<p>And because it's fun to start playing with monorepos and see how helpful those tools are when running in CI.</p>
<p>At Alpsify, we work with a monorepo powered by Moonrepo. We launch self-hosted GitHub Runners on local machines because we can't afford big Kubernetes clusters to auto-scale a fleet of runners. So, we have a fleet of approximately 5 runners across our computers. It's like free CPU time, as our computers never hit the limit and are always on during working time. It's just our way of doing it to save costs.</p>
<p><strong>Note:</strong> This approach is not a perfect fit for everyone. We're just sharing something that works for us and may work for others.</p>
<h3 id="heading-github-runner-as-a-container">GitHub Runner as a Container</h3>
<p>Create a folder inside your Moonrepo repository (😮‍💨) and name it as you wish. I'll refer to it as <code>&lt;project-name&gt;</code> afterwards.</p>
<p>Register it in your <code>.moon/workspace.yml</code> if needed (depends on your config).</p>
<blockquote>
<p>This part is inspired by a discussion on GitHub (<a target="_blank" href="https://github.com/actions/runner/issues/367#issuecomment-1507331413">https://github.com/actions/runner/issues/367#issuecomment-1507331413</a>)</p>
</blockquote>
<p>Create a Dockerfile inside <code>&lt;project-name&gt;</code></p>
<pre><code class="lang-dockerfile"><span class="hljs-comment"># &lt;project-name&gt;/Dockerfile</span>
<span class="hljs-keyword">FROM</span> ghcr.io/actions/actions-runner:latest
<span class="hljs-keyword">RUN</span><span class="bash"> sudo apt update -y &amp;&amp; sudo apt install build-essential git curl -y <span class="hljs-comment">#(whatever you need)</span></span>

<span class="hljs-keyword">COPY</span><span class="bash"> ./runner.sh .</span>
<span class="hljs-keyword">RUN</span><span class="bash"> sudo chmod +x runner.sh</span>

<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"./runner.sh"</span>]</span>
</code></pre>
<p>Create the <code>runner.sh</code> file inside </p>
<pre><code class="lang-bash"><span class="hljs-comment"># &lt;project-name&gt;/runner.sh</span>
<span class="hljs-comment">#!/bin/sh</span>

./config.sh --url https://github.com/&lt;repo-name&gt; --token <span class="hljs-variable">$TOKEN</span> --name <span class="hljs-variable">$NAME</span> --unattended
./run.sh
</code></pre>
<p>At this step, you can actually run your first GitHub Runner:</p>
<p>Inside your <code>&lt;project-name&gt;</code> folder, run <code>docker build --no-cache -t docker/github-runner</code> . to build your image.
Then run <code>docker run docker/github-runner</code>.</p>
<p>Don't forget to replace <code>$TOKEN</code> or <code>$NAME</code> with the actual values. You can find your <code>$TOKEN</code> at this link: https://github.com//settings/actions/runners/new. Don't replace it inside the <code>runner.sh</code> as it's going to be versioned. And believe me, you don't want your private secret <code>$TOKEN</code> to be versioned.</p>
<h3 id="heading-moonrepo-task-configuration">Moonrepo Task Configuration</h3>
<p>Create a <code>moon.yml</code> file inside <code>&lt;project-name&gt;</code> folder</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># &lt;project-name&gt;/moon.yml</span>
<span class="hljs-string">...</span>

<span class="hljs-attr">project:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">'GitHub Runner in Docker'</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">'GitHub Runner (as container) launcher'</span>
    <span class="hljs-string">...</span>

<span class="hljs-attr">tasks:</span>
    <span class="hljs-attr">build:</span>
        <span class="hljs-attr">command:</span> <span class="hljs-string">'docker build --no-cache -t docker/github-runner .'</span>
        <span class="hljs-attr">platform:</span> <span class="hljs-string">'system'</span>
        <span class="hljs-attr">local:</span> <span class="hljs-literal">true</span>

  <span class="hljs-attr">run:</span>
    <span class="hljs-comment">#-- -n -t</span>
    <span class="hljs-attr">deps:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">'build'</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">'bash ./launch-container.sh'</span>
    <span class="hljs-attr">platform:</span> <span class="hljs-string">'system'</span>
    <span class="hljs-attr">local:</span> <span class="hljs-literal">true</span>
</code></pre>
<p>Create the <code>launch-container.sh</code> file</p>
<pre><code class="lang-bash"><span class="hljs-comment"># &lt;project-name&gt;/launch-container.sh</span>
<span class="hljs-comment">#!/bin/bash</span>
<span class="hljs-keyword">while</span> <span class="hljs-built_in">getopts</span> n:t: flag
<span class="hljs-keyword">do</span>
    <span class="hljs-keyword">case</span> <span class="hljs-string">"<span class="hljs-variable">${flag}</span>"</span> <span class="hljs-keyword">in</span>
        n) name=<span class="hljs-variable">${OPTARG}</span>;;
        t) token=<span class="hljs-variable">${OPTARG}</span>;;
    <span class="hljs-keyword">esac</span>
<span class="hljs-keyword">done</span>

docker run -d -e NAME=<span class="hljs-variable">$name</span> -e TOKEN=<span class="hljs-variable">$token</span> --name <span class="hljs-variable">$name</span> docker/github-runner
</code></pre>
<p>The <code>--name $name</code> is to quickly identify which container it is (compared to GitHub Runner registration name). Sometimes you get errors, and you need to go deep into the details, so auto-generated names are not that useful in this kind of situation. 🥹</p>
<h3 id="heading-enjoy">Enjoy</h3>
<p>Run the command <code>moon &lt;project-name&gt;:run -- -t $TOKEN -n my-awesome-runner-1</code> (still need $TOKEN to be replaced)</p>
<p>Run it again with another name <code>-n my-lovely-runner-1</code> ! Voilà! You got another one up and running, ready to take care of your CI jobs.</p>
<p>Don't forget to add <code>runs-on: 'self-hosted'</code> in your <code>.github/workflows/&lt;whatever&gt;.yml</code> file for your CI to use it.</p>
<p>Look at the page <a target="_blank" href="https://github.com/alpsify/alpsify/settings/actions/runners">https://github.com/&lt;your-repo-name&gt;/settings/actions/runners</a> and enjoy your new GitHub Runners! 🥳</p>
]]></content:encoded></item></channel></rss>