<?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[Blog - Alejandro Akbal]]></title><description><![CDATA[Curated articles about anything and everything.]]></description><link>https://blog.akbal.dev</link><generator>RSS for Node</generator><lastBuildDate>Sun, 12 Apr 2026 06:18:04 GMT</lastBuildDate><atom:link href="https://blog.akbal.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How to mention everyone in WhatsApp Web]]></title><description><![CDATA[Introduction
I run a large WhatsApp group and I often need to mention everyone in the group. But there are no real solutions for mentioning all members.
WhatsApp does not natively do it, and there was a Firefox browser extension, but it was abandoned...]]></description><link>https://blog.akbal.dev/how-to-mention-everyone-in-whatsapp-web</link><guid isPermaLink="true">https://blog.akbal.dev/how-to-mention-everyone-in-whatsapp-web</guid><category><![CDATA[Tutorial]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[whatsapp]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Alejandro Akbal]]></dc:creator><pubDate>Tue, 24 Jan 2023 11:10:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1674558568682/f77825fd-3454-4077-b233-e5b3f1187cac.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<p>I run a large WhatsApp group and I often need to mention everyone in the group. But there are no real solutions for mentioning all members.</p>
<p>WhatsApp does not natively do it, and there was a Firefox browser extension, but it was abandoned.</p>
<p>That is why I decided to make my solution.</p>
<p>I made a UserScript that does exactly this: mentions everyone in WhatsApp Web when you write <code>@@</code> in the chat.</p>
<blockquote>
<p>Note: an UserScript is a program for modifying web pages to augment browsing.</p>
</blockquote>
<p>Any browser that can install an <a target="_blank" href="https://greasyfork.org/en/help/installing-user-scripts#installing-user-scripts-in-a-browser">UserScript manager</a> can run this solution.</p>
<p>Google Chrome, Firefox, Brave, Opera, Microsoft Edge, Safari, and more are supported. Just continue reading. 😉</p>
<hr />
<h2 id="heading-setup">Setup</h2>
<p>First, you need to install a UserScript manager. I recommend <a target="_blank" href="https://www.tampermonkey.net/">Tampermonkey</a> as it is the most popular and supports all browsers.</p>
<p>Then, install the <a target="_blank" href="https://github.com/AlejandroAkbal/WhatsApp-Web-Mention-Everyone-Userscript">WhatsApp Web Mention Everyone</a> UserScript.</p>
<hr />
<h2 id="heading-mention-everyone">Mention Everyone</h2>
<p>The fun part! Go to <a target="_blank" href="https://web.whatsapp.com/">web.whatsapp.com</a> and open a Group chat.</p>
<p><strong>All you need to do is write</strong> <code>@@</code> in the chat to see the magic happen.</p>
<p><img src="https://github.com/AlejandroAkbal/WhatsApp-Web-Mention-Everyone-Userscript/raw/main/misc/example.jpg" alt class="image--center mx-auto" /></p>
<hr />
<h2 id="heading-end">End</h2>
<p>That was it. Easy right?</p>
<h3 id="heading-self-promotion">Self-promotion</h3>
<p>If you have found this useful, then you should follow me, I will be posting more interesting content! 🥰</p>
<ul>
<li><p><a target="_blank" href="https://redirect.akbal.dev/github">GitHub</a></p>
</li>
<li><p><a target="_blank" href="https://redirect.akbal.dev/twitter">Twitter</a></p>
</li>
<li><p><a target="_blank" href="https://redirect.akbal.dev/dev.to">Dev.to</a></p>
</li>
</ul>
<h3 id="heading-conclusion">Conclusion</h3>
<p>Congratulations, today you have learned how to mention everyone on WhatsApp Web. Regardless of your Browser or OS.</p>
<p><strong>Let me know if the tutorial was useful to you in the comments!</strong></p>
]]></content:encoded></item><item><title><![CDATA[How to preload images for canvas in JavaScript]]></title><description><![CDATA[Introduction
This past week I was toying around with the HTML5 canvas element, trying to join two images together.
At first, it seemed fine, but when I tried to reload the website, it was a mess. One image would load, but the other wouldn't.
Investig...]]></description><link>https://blog.akbal.dev/how-to-preload-images-for-canvas-in-javascript</link><guid isPermaLink="true">https://blog.akbal.dev/how-to-preload-images-for-canvas-in-javascript</guid><category><![CDATA[Tutorial]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[canvas]]></category><category><![CDATA[images]]></category><dc:creator><![CDATA[Alejandro Akbal]]></dc:creator><pubDate>Tue, 17 Jan 2023 11:21:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1673954397235/7a68d46a-0993-4d53-a630-9d4ad3c32855.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[
<h1 id="heading-introduction">Introduction</h1>
<p>This past week I was toying around with the HTML5 canvas element, trying to join two images together.
At first, it seemed fine, but when I tried to reload the website, it was a mess. One image would load, but the other wouldn't.</p>
<p>Investigating I found out that the images were being loaded asynchronously.
But the JavaScript code was running anyway, without waiting for the images, ending up with a messed up canvas.</p>
<p>That is why I decided to write this tutorial, to help you <strong>preload images for canvas in modern JavaScript</strong>.</p>
<hr />
<h2 id="heading-preload-images">Preload images</h2>
<p>With the help of the <code>Promise.all()</code> function, we can devise a solution to preload images.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
 * <span class="hljs-doctag">@param <span class="hljs-type">{string[]}</span> <span class="hljs-variable">urls</span></span> - Array of Image URLs
 * <span class="hljs-doctag">@returns <span class="hljs-type">{Promise&lt;HTMLImageElement[]&gt;}</span> </span>- Promise that resolves when all images are loaded, or rejects if any image fails to load
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">preloadImages</span>(<span class="hljs-params">urls</span>) </span>{
  <span class="hljs-keyword">const</span> promises = urls.map(<span class="hljs-function">(<span class="hljs-params">url</span>) =&gt;</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> image = <span class="hljs-keyword">new</span> Image();

      image.src = url;

      image.onload = <span class="hljs-function">() =&gt;</span> resolve(image);
      image.onerror = <span class="hljs-function">() =&gt;</span> reject(<span class="hljs-string">`Image failed to load: <span class="hljs-subst">${url}</span>`</span>);
    });
  });

  <span class="hljs-keyword">return</span> <span class="hljs-built_in">Promise</span>.all(promises);
}
</code></pre>
<p>You can then use it like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> urls = [
  <span class="hljs-string">'https://example.com/image1.png'</span>,
  <span class="hljs-string">'https://example.com/image2.png'</span>,
];

<span class="hljs-comment">// Important to use `await` here</span>
<span class="hljs-keyword">const</span> images = <span class="hljs-keyword">await</span> preloadImages(urls);

<span class="hljs-comment">// Can also be destructured</span>
<span class="hljs-keyword">const</span> [image1, image2] = <span class="hljs-keyword">await</span> preloadImages(urls);

<span class="hljs-comment">// For example:</span>
<span class="hljs-keyword">const</span> canvas = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'canvas'</span>);
<span class="hljs-keyword">const</span> context = canvas.getContext(<span class="hljs-string">'2d'</span>);

context.drawImage(images[<span class="hljs-number">0</span>], <span class="hljs-number">0</span>, <span class="hljs-number">0</span>);
context.drawImage(images[<span class="hljs-number">1</span>], <span class="hljs-number">0</span>, <span class="hljs-number">0</span>);
</code></pre>
<hr />
<h2 id="heading-end">End</h2>
<p>That was it.
It's a small one-time setup on your project, that works like a charm.</p>

<h3 id="heading-self-promotion">Self-promotion</h3>
<p>If you have found this useful, then you should follow me, I will be posting more interesting content! 🥰</p>
<ul>
<li><a target="_blank" href="https://redirect.akbal.dev/github">GitHub</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/twitter">Twitter</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/dev.to">Dev.to</a></li>
</ul>

<h3 id="heading-conclusion">Conclusion</h3>
<p>Congratulations, today you have learned how to preload images for canvas in modern JavaScript. Without needing to complicate your code or use libraries.</p>
<p><strong>Let me know if the tutorial was useful to you in the comments!</strong></p>
]]></content:encoded></item><item><title><![CDATA[How to create a test database with Laravel Sail]]></title><description><![CDATA[Introduction
I have worked on projects that required the production and testing database to be the same.
This could be because some features work in MariaDB, but not in SQLite. Or some bugs appear in MySQL, but not in PostgreSQL.
When you're working ...]]></description><link>https://blog.akbal.dev/how-to-create-a-test-database-with-laravel-sail</link><guid isPermaLink="true">https://blog.akbal.dev/how-to-create-a-test-database-with-laravel-sail</guid><category><![CDATA[Tutorial]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[Laravel]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Databases]]></category><dc:creator><![CDATA[Alejandro Akbal]]></dc:creator><pubDate>Sun, 21 Aug 2022 17:07:05 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1661101382057/l09M6sN2L.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[
<h1 id="heading-introduction">Introduction</h1>
<p>I have worked on projects that required the production and testing database to be the same.</p>
<p>This could be because some features work in MariaDB, but not in SQLite. Or some bugs appear in MySQL, but not in PostgreSQL.</p>
<p>When you're working in a large project, <strong>you need to know that the testing database works exactly like the one in production</strong>.
You can't allow a bug to appear in production because the test database was different.</p>
<blockquote>
<p>Note: If the project is small, chances are you are fine using SQLite for testing.</p>
</blockquote>
<p>Today, I'm going to guide you through setting up a testing database in your current database server, thanks to Laravel Sail.</p>
<hr />
<h2 id="heading-before-we-start">Before we start</h2>
<p>This tutorial uses PostgreSQL as the database.
But the idea is the same for any other database: <strong>create a separate database in your current database server</strong>.</p>
<h3 id="heading-preface">Preface</h3>
<h3 id="heading-requirements">Requirements</h3>
<ul>
<li>Laravel 9</li>
<li>Laravel Sail</li>
</ul>
<hr />
<h2 id="heading-database-setup">Database setup</h2>
<p>We will use the database server declared in the <code>docker-compose.yml</code> file.
In this case, PostgreSQL.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">services:</span>
  <span class="hljs-attr">pgsql:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">'postgres:14'</span>
</code></pre>
<p>First, enter PostgreSQL's CLI</p>
<pre><code class="lang-bash">sail psql
</code></pre>
<blockquote>
<p>Note: If you are using MySQL, you can use <code>sail mysql</code> instead.</p>
</blockquote>
<p>Now, create a new database</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">DATABASE</span> testing;
</code></pre>
<p>This database is separated from the default database that your application uses.</p>
<hr />
<h2 id="heading-phpunit-configuration">PHPUnit configuration</h2>
<p>Now, modify the <code>phpunit.xml</code> file to use the new database and database server.
Should end up similar to this:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">php</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">env</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"DB_CONNECTION"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"pgsql"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">env</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"DB_DATABASE"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"testing"</span>/&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">php</span>&gt;</span>
</code></pre>
<hr />
<h2 id="heading-run-the-tests">Run the tests</h2>
<p>Now, you can run the tests and see if they work.</p>
<pre><code class="lang-bash">sail <span class="hljs-built_in">test</span> --parallel
</code></pre>
<p>It should look similar to this:</p>
<pre><code class="lang-bash">   PASS  Tests\Unit\ExampleTest
  ✓ example

  Tests:  1 passed
  Time:   0.7s
</code></pre>
<hr />
<h2 id="heading-end">End</h2>
<p>That was it.
It's a one-time setup, that works like a charm.</p>

<h3 id="heading-self-promotion">Self-promotion</h3>
<p>If you have found this useful, then you should follow me, I will be posting more interesting content! 🥰</p>
<ul>
<li><a target="_blank" href="https://redirect.akbal.dev/github">GitHub</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/twitter">Twitter</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/dev.to">Dev.to</a></li>
</ul>

<h3 id="heading-conclusion">Conclusion</h3>
<p>Congratulations, today you have learned how to create a test database in Laravel Sail.
Without needing to modify the <code>docker-compose.yml</code> file or creating more services.</p>
<p><strong>Let me know if the tutorial was useful to you in the comments!</strong></p>
]]></content:encoded></item><item><title><![CDATA[How to build a free plan in Laravel Spark]]></title><description><![CDATA[Introduction
I'm going to explain how to build a free plan for your Laravel Spark Next application.
I will be using Paddle as the payment gateway, but the steps are almost identical with Stripe.
Best of all? No credit card required for the free plan....]]></description><link>https://blog.akbal.dev/how-to-build-a-free-plan-in-laravel-spark</link><guid isPermaLink="true">https://blog.akbal.dev/how-to-build-a-free-plan-in-laravel-spark</guid><category><![CDATA[Tutorial]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[Laravel]]></category><category><![CDATA[spark]]></category><category><![CDATA[stripe]]></category><dc:creator><![CDATA[Alejandro Akbal]]></dc:creator><pubDate>Sun, 10 Oct 2021 11:10:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1633864163377/2Bomnvglg.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[
<h1 id="introduction">Introduction</h1>
<p>I'm going to explain how to build a free plan for your Laravel Spark Next application.</p>
<p>I will be using Paddle as the payment gateway, but the steps are almost identical with Stripe.</p>
<p>Best of all? No credit card required for the free plan.</p>
<hr />
<h2 id="before-we-start">Before we start</h2>
<h3 id="preface">Preface</h3>
<p>You should know that Laravel Spark is built on top of Laravel Cashier, so know that the process is very similar with both.</p>
<h3 id="requirements">Requirements</h3>
<ul>
<li>Laravel 8 with Spark Next</li>
</ul>
<hr />
<h2 id="laravel-spark-installation">Laravel Spark installation</h2>
<p>Before starting the tutorial, you should have followed the <a target="_blank" href="https://spark.laravel.com/docs/1.x/installation.html">official installation guide</a>.</p>
<hr />
<h2 id="laravel-spark-plan-configuration">Laravel Spark plan configuration</h2>
<p>Let's imagine that we are building a new application which offers a free plan and a paid plan.</p>
<ul>
<li>The <strong>"Free" plan</strong> will have a limit of <strong>1 project</strong>.</li>
<li>The <strong>"Paid" plan</strong> will have a limit of <strong>5 projects</strong>.</li>
</ul>
<p>We need to configure both plans in the <code>config/spark.php</code> file, like so:</p>
<pre><code class="lang-php">
<span class="hljs-comment">// ...</span>

  <span class="hljs-string">'plans'</span> =&gt; [
      [
          <span class="hljs-string">'name'</span> =&gt; <span class="hljs-string">'Free'</span>,
          <span class="hljs-string">'short_description'</span> =&gt; <span class="hljs-string">'This is the free plan'</span>,
          <span class="hljs-comment">// Random _(not used by other plans)_ ID</span>
          <span class="hljs-string">'monthly_id'</span> =&gt; <span class="hljs-number">1000</span>,
          <span class="hljs-string">'features'</span> =&gt; [
              <span class="hljs-string">'1 Project'</span>,
          ],
          <span class="hljs-string">'options'</span> =&gt; [
              <span class="hljs-string">'projects'</span> =&gt; <span class="hljs-number">1</span>,
          ],
          <span class="hljs-comment">// IMPORTANT</span>
          <span class="hljs-string">'archived'</span> =&gt; <span class="hljs-literal">true</span>,
      ],
      [
          <span class="hljs-string">'name'</span> =&gt; <span class="hljs-string">'Paid'</span>,
          <span class="hljs-string">'short_description'</span> =&gt; <span class="hljs-string">'This is the paid plan'</span>,
          <span class="hljs-string">'monthly_id'</span> =&gt; <span class="hljs-number">999990</span>,
          <span class="hljs-string">'yearly_id'</span> =&gt; <span class="hljs-number">999991</span>,
          <span class="hljs-string">'yearly_incentive'</span> =&gt; <span class="hljs-string">'Save 20%!'</span>,
          <span class="hljs-string">'features'</span> =&gt; [
              <span class="hljs-string">'5 Projects'</span>,
          ],
          <span class="hljs-string">'options'</span> =&gt; [
              <span class="hljs-string">'projects'</span> =&gt; <span class="hljs-number">5</span>,
          ],
          <span class="hljs-string">'archived'</span> =&gt; <span class="hljs-literal">false</span>,
      ],
  ]

<span class="hljs-comment">// ...</span>
</code></pre>
<p>As you can see, the limits are actually set in the <code>options</code> array.</p>
<p>It's important that the "Free" plan is set to <code>archived</code> to prevent users from selecting that plan on the billing page.</p>
<p>The "Free" plan has to have a random <code>monthly_id</code>, so the plan can be used internally.
<em>It doesn't need to be a working product ID, nor do you have to create it on Paddle/Stripe</em>.</p>
<hr />
<h2 id="modify-user-model">Modify User model</h2>
<p>Now that we have created a "Free" plan, we need to modify the <code>User</code> model to add a <code>getPlan</code> method.</p>
<p>This method will get the current users plan, <strong>or the "Free" plan if the user has no active plan</strong>.</p>
<pre><code class="lang-php">
<span class="hljs-comment">// ...</span>

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getPlan</span>(<span class="hljs-params"></span>)
    </span>{
        $plan = <span class="hljs-keyword">$this</span>-&gt;sparkPlan();

        <span class="hljs-keyword">if</span> ($plan !== <span class="hljs-literal">null</span>) {
            <span class="hljs-keyword">return</span> $plan;
        }

        <span class="hljs-comment">// Fallback to "Free" plan</span>
        $plan = Spark::plans(<span class="hljs-string">'user'</span>)-&gt;firstWhere(<span class="hljs-string">'name'</span>, <span class="hljs-string">'='</span>, <span class="hljs-string">'Free'</span>);

        <span class="hljs-keyword">return</span> $plan;
    }

<span class="hljs-comment">// ...</span>
</code></pre>
<p>Note: As mentioned in the steps before, you need to add a <code>monthly_id</code> to the "Free" plan, otherwise it will be skipped by the <code>Spark::plans('user')</code> call and will not be found.
The ID can be random, don't worry.</p>
<hr />
<h2 id="getting-the-users-plan">Getting the User's plan</h2>
<p>Through your application you can access a user plan and limits like so:</p>
<pre><code class="lang-php">$user = User::find(<span class="hljs-number">1</span>);

$plan = $user-&gt;getPlan();

$plan-&gt;name; <span class="hljs-comment">// "Free"</span>

$plan-&gt;options[<span class="hljs-string">'projects'</span>]; <span class="hljs-comment">// 1</span>
</code></pre>
<hr />
<h2 id="end">End</h2>
<p>As you can see, what we actually do is fallback to the "Free" plan if the user has no active plan.</p>
<p>And this plan doesn't actually go through the payment process, so you don't need to ask the users with a credit card.</p>

<h3 id="self-promotion">Self-promotion</h3>
<p>If you have found this useful, then you should follow me, I will be posting more interesting content! 🥰</p>
<ul>
<li><a target="_blank" href="https://redirect.akbal.dev/github">GitHub</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/twitter">Twitter</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/dev.to">Dev.to</a></li>
</ul>
<p>Or support me financially. 💸</p>
<ul>
<li><a target="_blank" href="https://redirect.akbal.dev/github/sponsor">GitHub Sponsors</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/liberapay">LiberaPay</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/paypal">PayPal</a></li>
</ul>

<h3 id="conclusion">Conclusion</h3>
<p>Congratulations, today you have learned how to build a free plan in Laravel Spark.</p>
<p><strong>Let me know if the tutorial was useful to you in the comments!</strong></p>
]]></content:encoded></item><item><title><![CDATA[How to roll back a Dokku deployment]]></title><description><![CDATA[Introduction
Sometimes you end up deploying an application to Dokku and then realize that you want to revert the changes you made.
In this tutorial we'll go over how to roll back a Dokku deployment.

Before we start
Preface
Keep in mind that rolling ...]]></description><link>https://blog.akbal.dev/how-to-roll-back-a-dokku-deployment</link><guid isPermaLink="true">https://blog.akbal.dev/how-to-roll-back-a-dokku-deployment</guid><category><![CDATA[Tutorial]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Git]]></category><category><![CDATA[deployment]]></category><dc:creator><![CDATA[Alejandro Akbal]]></dc:creator><pubDate>Sun, 25 Jul 2021 08:44:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1627202635182/mu_97DB-8.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[
<h1 id="introduction">Introduction</h1>
<p>Sometimes you end up deploying an application to Dokku and then realize that you want to revert the changes you made.</p>
<p>In this tutorial we'll go over how to roll back a Dokku deployment.</p>
<hr />
<h2 id="before-we-start">Before we start</h2>
<h3 id="preface">Preface</h3>
<p>Keep in mind that rolling back a deployment is a dangerous operation, proceed with caution.</p>
<h3 id="requirements">Requirements</h3>
<ul>
<li>Dokku server</li>
</ul>
<p>Don't have a Dokku server?
Check out my <a target="_blank" href="https://blog.akbal.dev/create-your-own-heroku-with-dokku-on-digitalocean">Dokku tutorial</a>.</p>
<hr />
<h3 id="get-the-commit-hash">Get the commit hash</h3>
<p>First well need to get the hash of the commit that we want to roll back to.</p>
<p>To accomplish that, list out the last 10 commits that have been made to the repository.</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">log</span> --pretty=format:<span class="hljs-string">"%h - %s"</span> -10
</code></pre>
<p>You will get an output similar to this:</p>
<pre><code class="lang-bash">3cacb03 - Revert <span class="hljs-string">"build: broaden possible purged files"</span>
25e3b2b - chore: move node dependency to dev dependencies
6a42416 - Revert <span class="hljs-string">"ci: run npm "</span>build<span class="hljs-string">" script in predeploy stage"</span>
0b53fdd - ci: execute php buildpack first
2d27d60 - ci: run npm <span class="hljs-string">"build"</span> script <span class="hljs-keyword">in</span> predeploy stage
1bc1276 - build: broaden possible purged files
1bed300 - style: lint
5ab255c - Revert <span class="hljs-string">"build: only run tailwind JIT mode on local"</span>
23b0c4b - build: fix data-tables styles getting purged
52ca32e - ci: move scripts back to app.json
</code></pre>
<p>Now copy the hash of the commit that you want to roll back to.
For example <code>2d27d60</code>.</p>
<hr />
<h2 id="how-to-rollback">How to rollback</h2>
<p>Now that we have the commit hash, we can roll back to it.</p>
<p>Just force push to Dokku with the commit hash, instead of the local branch.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># git push --force &lt;remote&gt; &lt;local branch&gt;:&lt;remote branch&gt;</span>
git push --force dokku de7fc85:master
</code></pre>
<p><strong>That is it!</strong>
Now Dokku will build the application from that commit.
Effectively rolling back to that commit.</p>
<hr />
<h2 id="end">End</h2>
<p>That was easy, wasn't it?</p>

<h3 id="self-promotion">Self-promotion</h3>
<p>If you have found this useful, then you should follow me, I will be posting more interesting content! 🥰</p>
<ul>
<li><a target="_blank" href="https://redirect.akbal.dev/github">GitHub</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/twitter">Twitter</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/dev.to">Dev.to</a></li>
</ul>
<p>Or support me financially. 💸</p>
<ul>
<li><a target="_blank" href="https://redirect.akbal.dev/github/sponsor">GitHub Sponsors</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/liberapay">LiberaPay</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/paypal">PayPal</a></li>
</ul>

<h3 id="conclusion">Conclusion</h3>
<p>Congratulations, today you have learned how to roll back a Dokku deployment.</p>
<p><strong>Let me know if the tutorial was useful to you in the comments!</strong></p>
]]></content:encoded></item><item><title><![CDATA[How to deploy git submodules to CapRover]]></title><description><![CDATA[Introduction
In this tutorial I will explain how to get git submodules to deploy correctly to CapRover using the CapRover CLI.

Before we start
Preface
Having some knowledge about CapRover, Docker and Git will help you understand how this solution wo...]]></description><link>https://blog.akbal.dev/how-to-deploy-git-submodules-to-caprover</link><guid isPermaLink="true">https://blog.akbal.dev/how-to-deploy-git-submodules-to-caprover</guid><category><![CDATA[Tutorial]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[Docker]]></category><dc:creator><![CDATA[Alejandro Akbal]]></dc:creator><pubDate>Mon, 14 Jun 2021 14:50:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1623681938269/KWmfShAfY.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[
<h1 id="introduction">Introduction</h1>
<p>In this tutorial I will explain how to get <code>git submodules</code> to deploy correctly to <code>CapRover</code> using the <code>CapRover CLI</code>.</p>
<hr />
<h2 id="before-we-start">Before we start</h2>
<h3 id="preface">Preface</h3>
<p>Having some knowledge about <code>CapRover</code>, <code>Docker</code> and <code>Git</code> will help you understand how this solution works.</p>
<hr />
<h2 id="the-problem">The problem</h2>
<p>When you use <code>caprover deploy</code>, what happens underneath is that the CLI uses <code>git archive</code> to make a compressed <code>tar</code> of your repository. It then sends and deploys that file to your CapRover server.</p>
<p>But there are some problems with <code>git archive</code>:
<strong>It does NOT include the <code>.git</code> directory in the <code>tar</code>.</strong></p>
<p><em>So what you end up deploying is not really a git repository...</em></p>
<p>And if you were using <code>git submodules</code> in your repository, they are not downloaded, since the <code>.git</code> directory is missing.</p>
<p>To solve that issue, I have found a solution that is separated into three steps.</p>
<hr />
<h2 id="first-step-create-a-dockerfile">First step: Create a Dockerfile</h2>
<p>The first step to use <code>git submodules</code> in CapRover is to create a <code>Dockerfile</code> and download the <code>git submodules</code> as a build step.</p>
<p>You will need to create a <code>captain-definition</code> file and point it to a <code>Dockerfile</code>.</p>
<p>For example:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"schemaVersion"</span>: <span class="hljs-number">2</span>,
  <span class="hljs-attr">"dockerfilePath"</span>: <span class="hljs-string">"./Dockerfile"</span>
}
</code></pre>
<p>Then, you will need to create a <code>Dockerfile</code> that contains the following build step.</p>
<pre><code class="lang-dockerfile">RUN git submodule update --init --recursive
</code></pre>
<p>For example:</p>
<pre><code class="lang-dockerfile">FROM node:15-alpine

COPY . .

RUN apk --no-cache add git

# IMPORTANT: Download git submodules
RUN git submodule update --init --recursive

# ...

RUN npm ci

CMD ["node", "src/main"]
</code></pre>
<hr />
<h2 id="second-step-include-git-directory-in-the-tar">Second step: Include .git directory in the tar</h2>
<p>The second step is to improve what <code>caprover deploy</code> does.
Create a <code>tar</code> file of your repository, while adding the <code>.git</code> directory.</p>
<p>For that, you can use the following commands:</p>
<pre><code class="lang-sh"><span class="hljs-comment"># Archive git repository</span>
git archive HEAD &gt; deploy.tar

<span class="hljs-comment"># Add `.git` directory to `tar`</span>
tar -rf deploy.tar .git
</code></pre>
<hr />
<h2 id="third-step-deploy-the-tar">Third step: Deploy the tar</h2>
<p>Now that you have both the <code>tar</code> with the <code>.git</code> directory, and a <code>Dockerfile</code> that downloads the <code>git submodules</code>, you are ready to deploy.</p>
<pre><code class="lang-sh"><span class="hljs-comment"># Deploy the `tar` to your CapRover server</span>
npx caprover deploy -t ./deploy.tar

<span class="hljs-comment"># Remove the tar</span>
rm ./deploy.tar
</code></pre>
<hr />
<h2 id="end">End</h2>
<p>That was it, I hope you had luck and your deployment was successful!</p>
<p>Feel free to use the following script to perform all of these steps automatically.</p>
<pre><code class="lang-sh"><span class="hljs-meta">#!/bin/bash</span>

<span class="hljs-comment"># Archive git repository</span>
git archive HEAD &gt; deploy.tar

<span class="hljs-comment"># Add `.git` directory to `tar`</span>
tar -rf deploy.tar .git

<span class="hljs-comment"># Deploy the `tar` to your CapRover server</span>
npx caprover deploy -t ./deploy.tar

<span class="hljs-comment"># Remove the tar</span>
rm ./deploy.tar
</code></pre>

<h3 id="self-promotion">Self-promotion</h3>
<p>If you have found this useful, then you should follow me, I will be posting more interesting content! 🥰</p>
<ul>
<li><a target="_blank" href="https://redirect.akbal.dev/github">GitHub</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/twitter">Twitter</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/dev.to">Dev.to</a></li>
</ul>
<p>Or support me financially. 💸</p>
<ul>
<li><a target="_blank" href="https://redirect.akbal.dev/github/sponsor">GitHub Sponsors</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/liberapay">LiberaPay</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/paypal">PayPal</a></li>
</ul>

<h3 id="conclusion">Conclusion</h3>
<p>Congratulations, today you have learned how to deploy <code>git submodules</code> to your <code>CapRover</code> server.</p>
<p><strong>Let me know if the tutorial was useful to you in the comments!</strong></p>
]]></content:encoded></item><item><title><![CDATA[Earn money with the AdGuard Affiliate Program]]></title><description><![CDATA[Introduction
Chances are that you want to monetize your audience, and you have learned about the 50% lifetime commissions on the AdGuard Affiliate Program.
So let's learn everything about the affiliate / referral program.

How it works
When you becom...]]></description><link>https://blog.akbal.dev/earn-money-with-the-adguard-affiliate-program</link><guid isPermaLink="true">https://blog.akbal.dev/earn-money-with-the-adguard-affiliate-program</guid><category><![CDATA[Tutorial]]></category><category><![CDATA[guide]]></category><dc:creator><![CDATA[Alejandro Akbal]]></dc:creator><pubDate>Fri, 30 Apr 2021 14:07:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1619790939291/yzYJ-yq9Q.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[
<h1 id="introduction">Introduction</h1>
<p>Chances are that you want to monetize your audience, and you have learned about the <strong><a target="_blank" href="https://aff.adguard.com/?ref=AlejandroAkbal">50% lifetime commissions on the AdGuard Affiliate Program</a></strong>.</p>
<p><strong>So let's learn everything about the affiliate / referral program.</strong></p>
<hr />
<h2 id="how-it-works">How it works</h2>
<p>When you become an AdGuard affiliate, you will earn 50% of the purchases that your referred users make.</p>
<p>The process looks like this:</p>
<ul>
<li>You share your referral link to your audience</li>
<li>A user installs AdGuard using your link</li>
<li>The user makes a purchase and activates the program with it</li>
<li>At the moment of activation, you will earn 50% of the revenue</li>
<li>You will also earn 50% from the following renewals</li>
</ul>
<p>It does not matter when and how a user purchases the license key.
<strong>The important thing is that a user has installed AdGuard with your link.</strong></p>
<hr />
<h2 id="register">Register</h2>
<p>To start the process you will have to go to the <a target="_blank" href="https://aff.adguard.com/?ref=AlejandroAkbal">AdGuard Affiliate Program website</a> and create an account.</p>
<p>It is very easy, just fill the form with your details.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fnlzm7sftswv3ci3zycr.png" alt="Registration form" /></p>
<hr />
<h2 id="set-a-payment-method">Set a payment method</h2>
<p>Now that you have an account you will have to go to the <a target="_blank" href="https://aff.adguard.com/en/member/profile.html">Profile page</a> and set a payment method to receive the money.</p>
<p>You can choose between PayPal, Visa, MasterCard, Webmoney and QIWI.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6gz83q0afr1c29cy0nkm.png" alt="User profile" /></p>
<h2 id="share">Share</h2>
<p>To start sharing, you will have to get your affiliate link.</p>
<p>Go to the <a target="_blank" href="https://aff.adguard.com/en/member/promo.html">Marketing tools page</a>, click on “Your website links” and copy the link.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gh1apiod71btljcj220u.png" alt="AdGuard Affiliate link" /></p>
<p>Now comes the “hard work”, sharing the link with your user base.</p>
<p>How you choose to share it, is entirely up to you.</p>
<p>For example, you could share it to your <strong>Twitter followers, create a review of the AdGuard App, or mention it in your YouTube videos</strong>.</p>
<p>There are many ways to do it, good luck!</p>
<hr />
<h2 id="get-paid">Get paid</h2>
<p>Now that you have hopefully made some money... It's time to receive it!</p>
<p>To cash out it's as easy as going to the <a target="_blank" href="https://aff.adguard.com/en/member/payments.html">Payouts page</a> and <strong>ordering a payout</strong>.</p>
<p><em>Remember that the minimum balance for cashing out is $20.</em></p>
<hr />
<h2 id="end">End</h2>
<p>That was it, now do your magic and earn that affiliate money!</p>

<h3 id="self-promotion">Self-promotion</h3>
<p>If you have found this useful, then you should follow me, I will be posting more interesting content! 🥰</p>
<ul>
<li><a target="_blank" href="https://redirect.akbal.dev/github">GitHub</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/twitter">Twitter</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/dev.to">Dev.to</a></li>
</ul>
<p>Or support me financially. 💸</p>
<ul>
<li><a target="_blank" href="https://redirect.akbal.dev/github/sponsor">GitHub Sponsors</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/liberapay">LiberaPay</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/paypal">PayPal</a></li>
</ul>

<h3 id="conclusion">Conclusion</h3>
<p>Congratulations, today you have learned how to join the AdGuard Affiliate Program and hopefully make some easy money.</p>
<p><strong>Let me know if this guide was useful to you in the comments!</strong></p>
]]></content:encoded></item><item><title><![CDATA[How to detect and update to the latest version with Nuxt PWA]]></title><description><![CDATA[Introduction
I was working on one of my Nuxt projects and noticed that some users were using old versions, which was causing some errors to pop up.
I investigated and learned that sometimes PWAs don't update if the user doesn't manually refresh the w...]]></description><link>https://blog.akbal.dev/how-to-detect-and-update-to-the-latest-version-with-nuxt-pwa</link><guid isPermaLink="true">https://blog.akbal.dev/how-to-detect-and-update-to-the-latest-version-with-nuxt-pwa</guid><category><![CDATA[Tutorial]]></category><category><![CDATA[Vue.js]]></category><category><![CDATA[Nuxt]]></category><category><![CDATA[Programming Blogs]]></category><dc:creator><![CDATA[Alejandro Akbal]]></dc:creator><pubDate>Thu, 11 Mar 2021 16:01:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1615478432075/we9thLfrP.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[
<h1 id="introduction">Introduction</h1>
<p>I was working on one of my Nuxt projects and noticed that some users were using old versions, which was causing some errors to pop up.</p>
<p>I investigated and learned that sometimes PWAs don't update if the user doesn't manually refresh the website. So...</p>
<p><strong>Let's learn how to automatically update to the latest PWA version.</strong></p>
<hr />
<h2 id="before-we-start">Before we start</h2>
<p>This is a simple tutorial for projects with Nuxt and the PWA module, nothing else is required.</p>
<h3 id="requirements">Requirements</h3>
<ul>
<li>Nuxt</li>
<li>Nuxt PWA module</li>
</ul>
<hr />
<h2 id="create-a-new-plugin">Create a new plugin</h2>
<p>To start, you will need to go to your plugins directory and create a new JavaScript file. I will name it <code>pwa-update.js</code> but feel free to use whatever you want to.</p>
<pre><code class="lang-js"><span class="hljs-comment">// pwa-update.js</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">async</span> (context) =&gt; {
  <span class="hljs-keyword">const</span> workbox = <span class="hljs-keyword">await</span> <span class="hljs-built_in">window</span>.$workbox;

  <span class="hljs-keyword">if</span> (!workbox) {
    <span class="hljs-built_in">console</span>.debug(<span class="hljs-string">"Workbox couldn't be loaded."</span>);
    <span class="hljs-keyword">return</span>;
  }

  workbox.addEventListener(<span class="hljs-string">'installed'</span>, <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (!event.isUpdate) {
      <span class="hljs-built_in">console</span>.debug(<span class="hljs-string">'The PWA is on the latest version.'</span>);
      <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-built_in">console</span>.debug(<span class="hljs-string">'There is an update for the PWA, reloading...'</span>);
    <span class="hljs-built_in">window</span>.location.reload();
  });
};
</code></pre>
<hr />
<h2 id="add-the-plugin-to-the-nuxt-config">Add the plugin to the Nuxt config</h2>
<p>Then we have to add it to the plugins array on <code>nuxt.config.js</code>.</p>
<pre><code class="lang-js"><span class="hljs-comment">// nuxt.config.js</span>

<span class="hljs-comment">// ...</span>

  <span class="hljs-attr">plugins</span>: [
    { <span class="hljs-attr">src</span>: <span class="hljs-string">'~/plugins/pwa-update.js'</span>, <span class="hljs-attr">mode</span>: <span class="hljs-string">'client'</span> },
  ],

<span class="hljs-comment">// ...</span>
</code></pre>
<hr />
<h2 id="end">End</h2>
<p>And that was it. Easy right?</p>

<h3 id="self-promotion">Self-promotion</h3>
<p>If you have found this useful, then you should follow me, I will be posting more interesting content! 🥰</p>
<ul>
<li><a target="_blank" href="https://redirect.akbal.dev/github">GitHub</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/twitter">Twitter</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/dev.to">Dev.to</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/medium">Medium</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/linkedin">LinkedIn</a></li>
</ul>
<p>Or support me financially. 💸</p>
<ul>
<li><a target="_blank" href="https://redirect.akbal.dev/github/sponsor">GitHub Sponsors</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/liberapay">LiberaPay</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/paypal">PayPal</a></li>
</ul>

<h3 id="conclusion">Conclusion</h3>
<p>Congratulations, today you have set up automatic PWA updates for your project.</p>
<p><strong>Let me know if this tutorial was useful to you in the comments!</strong></p>
]]></content:encoded></item><item><title><![CDATA[How to set up Matomo analytics in your Hashnode blog]]></title><description><![CDATA[Introduction
Today I bring incredible news; Hashnode has finally added support for Matomo analytics!

Matomo are powerful open source web analytics which gives you 100% data ownership.

I just noticed it in the integrations tab of Hashnode's dashboar...]]></description><link>https://blog.akbal.dev/how-to-set-up-matomo-analytics-in-your-hashnode-blog</link><guid isPermaLink="true">https://blog.akbal.dev/how-to-set-up-matomo-analytics-in-your-hashnode-blog</guid><category><![CDATA[Tutorial]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[Hashnode]]></category><category><![CDATA[blog]]></category><category><![CDATA[backend]]></category><dc:creator><![CDATA[Alejandro Akbal]]></dc:creator><pubDate>Sat, 16 Jan 2021 21:44:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1610833395746/E-D0XTWbU.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[
<h1 id="introduction">Introduction</h1>
<p>Today I bring incredible news; Hashnode has finally added <a target="_blank" href="https://hashnode.com/post/support-for-matomo-formerly-piwik-ckevg1tnn00tcn1s18ima1ihs">support for Matomo analytics</a>!</p>
<blockquote>
<p>Matomo are powerful open source web analytics which gives you 100% data ownership.</p>
</blockquote>
<p>I just noticed it in the <em>integrations</em> tab of Hashnode's dashboard, so...</p>
<p><strong>Let's learn how to integrate Matomo in Hashnode!</strong></p>
<hr />
<h2 id="before-we-start">Before we start</h2>
<h3 id="requirements">Requirements</h3>
<ul>
<li>A Hashnode blog</li>
<li>A Matomo server</li>
</ul>
<hr />
<h2 id="new-matomo-website">New Matomo website</h2>
<p>To start, you will need to go to your Matomo server and create a new website.</p>
<p>Create it in: <code>All websites</code> &gt; <code>Add a new website</code>.</p>
<p>Now, fill the form with your current Hashnode details, grab the site ID, and you are ready to go!</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/qob56vcg9hgq74mx9x64.png" alt="Matomo form" /></p>
<hr />
<h2 id="add-matomo-integration">Add Matomo integration</h2>
<p>Now that you have the Matomo site set up, go to your Hashnode dashboard and choose the <code>integrations</code> tab.</p>
<p>There you will see a form for Matomo. Just fill it with your Matomo server details.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/cxixcmxqqla761cmrnka.png" alt="Hashnode form" /></p>
<hr />
<h2 id="end">End</h2>
<p>And that was it, <strong>100% open source analytics on your Hashnode blog in less than 5 minutes</strong>. Easy right?</p>
<h3 id="whats-next">What's next?</h3>
<p>Take a look at your Matomo analytics, you should start to see people coming in to your blog to enjoy your incredible articles. 😉</p>

<h3 id="self-promotion">Self-promotion</h3>
<p>If you have found this useful then you should follow me, I will be posting more interesting content! 🥰</p>
<ul>
<li><a target="_blank" href="https://redirect.akbal.dev/github">GitHub</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/twitter">Twitter</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/dev.to">Dev.to</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/medium">Medium</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/linkedin">LinkedIn</a></li>
</ul>
<p>Or support me financially. 💸</p>
<ul>
<li><a target="_blank" href="https://redirect.akbal.dev/github/sponsor">GitHub Sponsors</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/liberapay">LiberaPay</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/paypal">PayPal</a></li>
</ul>

<h3 id="conclusion">Conclusion</h3>
<p>Congratulations, today you have set up Matomo analytics for your Hashnode blog in a breeze.</p>
<p><strong>Let me know if the tutorial was useful to you in the comments!</strong></p>
]]></content:encoded></item><item><title><![CDATA[How to set up automatic updates on Ubuntu server]]></title><description><![CDATA[Introduction
Setting up automatic updates can be a daunting task. But fear not, this tutorial will help you set up automatic updates correctly in less than 10 minutes.

Before we start
Preface
While this tutorial is focused on Ubuntu Server, it can b...]]></description><link>https://blog.akbal.dev/how-to-set-up-automatic-updates-on-ubuntu-server</link><guid isPermaLink="true">https://blog.akbal.dev/how-to-set-up-automatic-updates-on-ubuntu-server</guid><category><![CDATA[Tutorial]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Ubuntu]]></category><category><![CDATA[webdev]]></category><dc:creator><![CDATA[Alejandro Akbal]]></dc:creator><pubDate>Fri, 15 Jan 2021 13:54:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1610718788329/mhwUYNgJC.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="introduction">Introduction</h1>
<p>Setting up automatic updates can be a daunting task. But fear not, this tutorial will help you set up automatic updates correctly in less than 10 minutes.</p>
<hr />
<h2 id="before-we-start">Before we start</h2>
<h3 id="preface">Preface</h3>
<p>While this tutorial is focused on <strong>Ubuntu Server</strong>, it can be used for many other distributions that use the same package manager, like Ubuntu Desktop, Debian, Linux Mint, etc.</p>
<h3 id="requirements">Requirements</h3>
<ul>
<li>An Ubuntu server</li>
<li>An internet connection</li>
<li>Access to your server</li>
</ul>
<hr />
<h2 id="update">Update</h2>
<p>First you'll have to update to the latest package repository definition.</p>
<pre><code class="lang-sh">sudo apt update
</code></pre>
<hr />
<h2 id="install">Install</h2>
<p>Then, we will need to install the package that does all the magic for us, <a target="_blank" href="https://wiki.debian.org/UnattendedUpgrades">unattended-upgrades</a>.</p>
<pre><code class="lang-sh">sudo apt install -y unattended-upgrades
</code></pre>
<p><em>Chances are that you already have this package installed.</em></p>
<hr />
<h2 id="configuration">Configuration</h2>
<p>Next step is to configure the package, lets start by opening the configuration file in the <code>nano</code> text editor.</p>
<pre><code class="lang-sh">sudo nano /etc/apt/apt.conf.d/50unattended-upgrades
</code></pre>
<pre><code class="lang-php"><span class="hljs-comment">// ...</span>

Unattended-Upgrade::Allowed-Origins {
        <span class="hljs-string">"${distro_id}:${distro_codename}"</span>;
        <span class="hljs-string">"${distro_id}:${distro_codename}-security"</span>;
        <span class="hljs-comment">// Extended Security Maintenance; doesn't necessarily exist for</span>
        <span class="hljs-comment">// every release and this system may not have it installed, but if</span>
        <span class="hljs-comment">// available, the policy for updates is such that unattended-upgrades</span>
        <span class="hljs-comment">// should also install from here by default.</span>
        <span class="hljs-string">"${distro_id}ESMApps:${distro_codename}-apps-security"</span>;
        <span class="hljs-string">"${distro_id}ESM:${distro_codename}-infra-security"</span>;
<span class="hljs-comment">//      "${distro_id}:${distro_codename}-updates";</span>
<span class="hljs-comment">//      "${distro_id}:${distro_codename}-proposed";</span>
<span class="hljs-comment">//      "${distro_id}:${distro_codename}-backports";</span>
};

<span class="hljs-comment">// ...</span>
</code></pre>
<p>You should read the configuration file to understand what it is doing, don't worry if you don't understand most things.</p>
<p>The important step is to uncomment the following lines.</p>
<pre><code class="lang-php"><span class="hljs-comment">// Required, updates common software</span>
<span class="hljs-string">"${distro_id}:${distro_codename}-updates"</span>;

<span class="hljs-comment">// Optional, removes unused packages when updating</span>
Unattended-Upgrade::Remove-Unused-Kernel-Packages <span class="hljs-string">"true"</span>;
Unattended-Upgrade::Remove-Unused-Dependencies <span class="hljs-string">"true"</span>;

<span class="hljs-comment">// Optional, reboot automatically the system if needed at certain time</span>
Unattended-Upgrade::Automatic-Reboot <span class="hljs-string">"true"</span>;
Unattended-Upgrade::Automatic-Reboot-Time <span class="hljs-string">"02:00"</span>;
</code></pre>
<blockquote>
<p>You can search the file with <code>ctrl</code> + <code>w</code></p>
</blockquote>
<p>Then exit <code>nano</code> with <code>ctrl</code> + <code>x</code> and press <code>Y</code> to save modifications.</p>
<hr />
<h2 id="enable">Enable</h2>
<p>Now that everything is set up, lets enable the automatic updates. For this, you will need to configure one last file.</p>
<pre><code class="lang-sh">sudo nano /etc/apt/apt.conf.d/20auto-upgrades
</code></pre>
<p>The file might be empty, in that case, just paste the following.</p>
<pre><code class="lang-php">APT::Periodic::Update-Package-Lists <span class="hljs-string">"1"</span>;
APT::Periodic::Download-Upgradeable-Packages <span class="hljs-string">"1"</span>;
APT::Periodic::Unattended-Upgrade <span class="hljs-string">"1"</span>;
APT::Periodic::AutocleanInterval <span class="hljs-string">"7"</span>;
</code></pre>
<p>The values are specified in days, so auto-clean will happen every week and auto-updates every day.</p>
<hr />
<h2 id="test">Test</h2>
<p>Let's test if everything is set up correctly.</p>
<pre><code class="lang-sh">sudo unattended-upgrades --dry-run
</code></pre>
<p>This will run <code>unattended-upgrades</code> without making any real change, making sure everything is correctly set up.</p>
<hr />
<h2 id="end">End</h2>
<p>That was it, easy right?</p>
<h3 id="whats-next">What's next?</h3>
<p>You might want to <a target="_blank" href="https://blog.akbal.dev/how-to-free-up-space-on-ubuntu-server">free up space on your server</a> after all the updates are done!</p>

<h3 id="self-promotion">Self-promotion</h3>
<p>If you have found this useful then you should follow me, I will be posting more interesting content! 🥰</p>
<ul>
<li><a target="_blank" href="https://redirect.akbal.dev/github">GitHub</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/twitter">Twitter</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/dev.to">Dev.to</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/medium">Medium</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/linkedin">LinkedIn</a></li>
</ul>
<p>Or support me financially. 💸</p>
<ul>
<li><a target="_blank" href="https://redirect.akbal.dev/github/sponsor">GitHub Sponsors</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/liberapay">LiberaPay</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/paypal">PayPal</a></li>
</ul>

<h3 id="conclusion">Conclusion</h3>
<p>Congratulations, today you have learned how to set up and configure automatic updates on your Ubuntu server thanks to the <code>unattended-upgrades</code> package.</p>
<p><strong>Let me know if the tutorial was useful to you in the comments!</strong></p>
]]></content:encoded></item><item><title><![CDATA[How to remove unused Docker resources]]></title><description><![CDATA[Introduction
Chances are that you've been running Docker for some time and found out that your system's storage is almost full.
This is completely normal as Docker bundles all the needed dependencies with each container and doesn't remove anything if...]]></description><link>https://blog.akbal.dev/how-to-remove-unused-docker-resources</link><guid isPermaLink="true">https://blog.akbal.dev/how-to-remove-unused-docker-resources</guid><category><![CDATA[Docker]]></category><category><![CDATA[Devops]]></category><category><![CDATA[server]]></category><dc:creator><![CDATA[Alejandro Akbal]]></dc:creator><pubDate>Wed, 16 Dec 2020 15:21:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1608133196884/RIoMzf5nR.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[
<h1 id="introduction">Introduction</h1>
<p>Chances are that you've been running Docker for some time and found out that your system's storage is almost full.</p>
<p>This is completely normal as Docker bundles all the needed dependencies with each container and doesn't remove anything if you don't explicitly tell it to do so.</p>
<p>So let's learn how to prune unused and unnecessary <strong>images, containers, volumes and networks</strong>!</p>
<blockquote>
<p>This tutorial will help you liberate space on your system without breaking anything in the process.</p>
</blockquote>
<hr />
<h2 id="before-we-start">Before we start</h2>
<p>We will be using the Docker CLI, so I expect you to be a bit familiar with it.</p>
<p>Otherwise, just use <code>docker --help</code> on the terminal and toy with it a little.</p>
<h3 id="requisites">Requisites</h3>
<ul>
<li>A bit of Docker knowledge</li>
</ul>
<hr />
<h2 id="images">Images</h2>
<p>Remove all images that are not tagged or referenced by any container</p>
<pre><code class="lang-sh">docker image prune
</code></pre>
<hr />
<h2 id="containers">Containers</h2>
<p>Remove all stopped containers</p>
<pre><code class="lang-sh">docker container prune
</code></pre>
<hr />
<h2 id="volumes">Volumes</h2>
<p>Remove all volumes not used by at least one container</p>
<pre><code class="lang-sh">docker volume prune
</code></pre>
<hr />
<h2 id="networks">Networks</h2>
<p>Remove all networks not used by at least one container</p>
<pre><code class="lang-sh">docker network prune
</code></pre>
<hr />
<h2 id="everything">Everything</h2>
<p>To finalize, lets remove everything --<em>but volumes</em>-- with a single command.</p>
<pre><code class="lang-sh">docker system prune
</code></pre>
<p>If you want to remove volumes too, just append <code>--volumes</code> at the end.</p>
<pre><code class="lang-sh">docker system prune --volumes
</code></pre>
<p>And if you want to remove EVERYTHING, just append <code>--all</code> at the end.</p>
<pre><code class="lang-sh">docker system prune --all
</code></pre>
<p><strong>And voilà, that removed every single resource that was unnecessary on your system!</strong></p>
<hr />
<h2 id="troubleshooting">Troubleshooting</h2>
<p>You might find that some images can't be removed because they are used. In this case you want to remove the resource that is using it, most likely a container.</p>
<hr />
<h2 id="end">End</h2>
<h3 id="whats-next">What's next?</h3>
<p>If you want to read more, please check out the <a target="_blank" href="https://docs.docker.com/config/pruning/">official Docker guide to pruning</a>.</p>

<h3 id="self-promotion">Self-promotion</h3>
<p>If you have found this useful, then you should follow me, I will be posting more interesting content! 🥰</p>
<ul>
<li><a target="_blank" href="https://redirect.akbal.dev/github">GitHub</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/twitter">Twitter</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/dev.to">Dev.to</a></li>
</ul>
<p>Or support me financially. 💸</p>
<ul>
<li><a target="_blank" href="https://redirect.akbal.dev/github/sponsor">GitHub Sponsors</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/liberapay">LiberaPay</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/paypal">PayPal</a></li>
</ul>

<h3 id="conclusion">Conclusion</h3>
<p>Today you have learned how to free up space on your system by removing Docker's unused images, containers, volumes and networks.</p>
<p><strong>Let me know how much space you have recovered in the comments!</strong></p>
]]></content:encoded></item><item><title><![CDATA[How to free up space on Ubuntu Server]]></title><description><![CDATA[Introduction
So you have been running your Ubuntu Server for a while and recently found out that the disk usage is already at 70%!? Then lets free some space up.
This tutorial will help you liberate space on your system without breaking anything in t...]]></description><link>https://blog.akbal.dev/how-to-free-up-space-on-ubuntu-server</link><guid isPermaLink="true">https://blog.akbal.dev/how-to-free-up-space-on-ubuntu-server</guid><category><![CDATA[Ubuntu]]></category><category><![CDATA[Security]]></category><category><![CDATA[Devops]]></category><category><![CDATA[server]]></category><dc:creator><![CDATA[Alejandro Akbal]]></dc:creator><pubDate>Tue, 15 Dec 2020 10:59:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1608029615270/t8HxqcxPR.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[
<h1 id="introduction">Introduction</h1>
<p>So you have been running your Ubuntu Server for a while and <strong>recently found out that the disk usage is already at 70%!?</strong> Then lets free some space up.</p>
<p>This tutorial will help you liberate space on your system without breaking anything in the process.</p>
<hr />
<h2 id="before-we-start">Before we start</h2>
<h3 id="preface">Preface</h3>
<p>While this tutorial is focused on <strong>Ubuntu Server</strong>, it can be used for many other distributions that use the same packages, like Ubuntu Desktop, Debian, Linux Mint, etc.</p>
<h3 id="requisites">Requisites</h3>
<ul>
<li>An Ubuntu server</li>
<li>Access to your server</li>
</ul>
<hr />
<h2 id="clean-packages">Clean packages</h2>
<p>Packages are archived and stored, if these versions can't be downloaded anymore --<em>because there is a newer version or any other reason</em>--, they end up being unnecessary. So let's clean lingering packages.</p>
<pre><code class="lang-sh"><span class="hljs-comment"># Find no longer available packages and remove them</span>
sudo apt autoclean -y
</code></pre>
<hr />
<h2 id="remove-packages">Remove packages</h2>
<p>Chances are that <strong>when you update and upgrade your system, some packages end up being unnecessary</strong>. But your system won't remove them, so lets tell it to do that.</p>
<pre><code class="lang-sh"><span class="hljs-comment"># Find unnecessary or redundant packages and remove them</span>
sudo apt autoremove -y
</code></pre>
<hr />
<h2 id="logs">Logs</h2>
<p>Application logs keep increasing the disk usage of your server, <strong>specially if it is a busy one</strong>. But if we don't care much about keeping records, we can just delete them.</p>
<pre><code class="lang-sh"><span class="hljs-comment"># Check current logs disk usage</span>
sudo journalctl --disk-usage

<span class="hljs-comment"># Rotate logs so they are saved to disk</span>
sudo journalctl --rotate

<span class="hljs-comment"># Clean any log that is older than one second</span>
sudo journalctl --vacuum-time=1s

<span class="hljs-comment"># One liner</span>
sudo journalctl --rotate &amp;&amp; sudo journalctl --vacuum-time=1s
</code></pre>
<hr />
<h2 id="biggest-files">Biggest files</h2>
<p>Now we are switching to a more manual approach.</p>
<p>We will be using <code>ncdu</code>, a very easy-to-use CLI tool that will show us the biggest files on our system.</p>
<pre><code class="lang-sh">sudo apt update -y

<span class="hljs-comment"># Install ncdu</span>
sudo apt-get install -y ncdu

<span class="hljs-comment"># Show biggest files in the system</span>
sudo ncdu /
</code></pre>
<p>Now that ncdu has scanned your system you can:</p>
<ul>
<li>Select with the arrow keys</li>
<li>Enter directories with <code>Enter</code></li>
<li>Remove directories and files with <code>d</code></li>
</ul>
<p>Thats how easy it is.</p>
<p>⚠ Be careful not to delete any important file. In case of doubt, don't do it. ⚠</p>
<hr />
<h2 id="end">End</h2>
<h3 id="whats-next">What's next?</h3>
<p>You can now search for more specific guides.</p>
<p>For example, if you are using Docker, you might want to learn <a target="_blank" href="https://blog.akbal.dev/how-to-remove-unused-docker-resources">how to remove unnecessary resources</a>.</p>

<h3 id="self-promotion">Self-promotion</h3>
<p>If you have found this useful, then you should follow me, I will be posting more interesting content! 🥰</p>
<ul>
<li><a target="_blank" href="https://redirect.akbal.dev/github">GitHub</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/twitter">Twitter</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/dev.to">Dev.to</a></li>
</ul>
<p>Or support me financially. 💸</p>
<ul>
<li><a target="_blank" href="https://redirect.akbal.dev/github/sponsor">GitHub Sponsors</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/liberapay">LiberaPay</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/paypal">PayPal</a></li>
</ul>

<h3 id="conclusion">Conclusion</h3>
<p>Today you have learned how to free up space on your system by removing packages, logs and files.</p>
<p><strong>Let me know how much space you have recovered in the comments!</strong></p>
]]></content:encoded></item><item><title><![CDATA[How to completely secure an Ubuntu server]]></title><description><![CDATA[Introduction
This tutorial will help you to set up a secure Ubuntu server from scratch.
Keep in mind that this is not a common tutorial, this is the culmination of all the knowledge I have gathered from managing my own servers for more than three yea...]]></description><link>https://blog.akbal.dev/how-to-completely-secure-an-ubuntu-server</link><guid isPermaLink="true">https://blog.akbal.dev/how-to-completely-secure-an-ubuntu-server</guid><category><![CDATA[Ubuntu]]></category><category><![CDATA[Security]]></category><category><![CDATA[Devops]]></category><category><![CDATA[server]]></category><dc:creator><![CDATA[Alejandro Akbal]]></dc:creator><pubDate>Wed, 02 Dec 2020 15:45:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1607010410934/EbvH92zDC.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[
<h1 id="introduction">Introduction</h1>
<p>This tutorial will help you to set up a secure Ubuntu server from scratch.</p>
<p>Keep in mind that this is not a common tutorial, <strong>this is the culmination of all the knowledge I have gathered from managing my own servers</strong> for more than three years.</p>
<hr />
<h2 id="before-we-start">Before we start</h2>
<h3 id="preface">Preface</h3>
<p>While this tutorial is focused on <strong>Ubuntu 20.04</strong>, it can be used for many other versions, like 18.04 and 16.04. As they are very similar.</p>
<h3 id="requisites">Requisites</h3>
<ul>
<li>An Ubuntu server</li>
<li>Access to your server</li>
</ul>
<p><em>It doesn't matter if your server is hosted on DigitalOcean, Google Cloud Engine or Amazon Web Services, Ubuntu should be the same.</em></p>
<h3 id="requisite-info">Requisite info</h3>
<p>If you don't have a server you might want to look at the <a class="post-section-overview" href="#useful-resources">Useful resources</a> step.</p>
<hr />
<h2 id="updates">Updates</h2>
<p>The first and probably most important step is to <strong>always keep the system up-to-date</strong>. To do so just open the terminal to update and upgrade the packages via <a target="_blank" href="https://linuxize.com/post/how-to-use-apt-command/">apt</a>.</p>
<pre><code class="lang-sh">sudo apt update           <span class="hljs-comment"># Update package information</span>
sudo apt full-upgrade -y  <span class="hljs-comment"># Upgrade packages</span>
sudo apt autoremove -y    <span class="hljs-comment"># Remove unnecessary packages</span>

<span class="hljs-comment"># One liner</span>
sudo apt update &amp;&amp; sudo apt dist-upgrade -y &amp;&amp; sudo apt autoremove -y
</code></pre>
<hr />
<h2 id="automatic-updates">Automatic updates</h2>
<p>Now that the packages are updated, we should install an automated solution to keep the system always up-to-date.</p>
<p><a target="_blank" href="https://linuxize.com/post/how-to-set-up-automatic-updates-on-ubuntu-18-04/">This tutorial on Linuxize</a> will help you install and configure the <code>unattended-upgrades</code> package, which is exactly what is needed.</p>
<hr />
<h2 id="new-user">New user</h2>
<p>Using the default super user <code>root</code> is always <strong>bad practice</strong>, it does everything with the maximum level of permissions, allowing you to break anything; and more critically... <em>Access to anything on the system</em>.</p>
<p>Instead, we should use a normal user with super user <strong>privileges</strong>. <a target="_blank" href="https://www.digitalocean.com/community/tutorials/how-to-create-a-new-sudo-enabled-user-on-ubuntu-20-04-quickstart">This tutorial on DigitalOcean</a> will guide you to do that.</p>
<hr />
<h2 id="ssh-credentials">SSH credentials</h2>
<p>Now that you have a new user with super user privileges, you might want to SSH in your server with it, <em>but might find that you can't</em>.</p>
<p>This is because the credentials were stored on the user you were using before, most likely <code>root</code>. Just SSH again with the previous user and copy the credentials to the new user with the <code>rsync</code> utility package.</p>
<p>Follow the <strong>5th step</strong> of <a target="_blank" href="https://www.digitalocean.com/community/tutorials/initial-server-setup-with-ubuntu-20-04#if-the-root-account-uses-ssh-key-authentication">this tutorial on DigitalOcean</a> to do so.</p>
<hr />
<h2 id="sshd">SSHD</h2>
<p>SSHD manages the SSH connections to the server. Its default configuration is good but some changes must be made, like disabling the <code>root</code> user login and changing the default <code>SSH</code> port.</p>
<p>Follow the <strong>first step</strong> <a target="_blank" href="https://www.digitalocean.com/community/tutorials/how-to-harden-openssh-on-ubuntu-18-04">of this tutorial on DigitalOcean</a> to learn how to configure SSHD.</p>
<blockquote>
<p>It is recommended that you change the default <code>SSH</code> port</p>
</blockquote>
<hr />
<h2 id="ufw">UFW</h2>
<p>UFW is Ubuntu's default firewall and is extremely useful. By default it allows <code>http</code> and <code>ssh</code> connections, depending of your use case you might not need some of those rules.</p>
<p>Check out <a target="_blank" href="https://www.digitalocean.com/community/tutorials/how-to-set-up-a-firewall-with-ufw-on-ubuntu-20-04">this tutorial on DigitalOcean</a> to learn how to configure UFW.</p>
<blockquote>
<p>If you changed the <code>SSH</code> port on an earlier step, you might want to create a new UFW rule for that port.</p>
</blockquote>
<hr />
<h2 id="fail2ban">Fail2Ban</h2>
<p>Fail2Ban protects you from brute-force attacks. It bans bad actors from accessing the server for a specified quantity of time.</p>
<p>Learn how to install and configure Fail2Ban <a target="_blank" href="https://linuxize.com/post/install-configure-fail2ban-on-ubuntu-20-04/">with this tutorial on Linuxize</a>.</p>
<hr />
<h2 id="miscellaneous">Miscellaneous</h2>
<p>These are some <strong>quick specific guides</strong> that you should keep in mind if you use any of this software.</p>
<h3 id="nginx">Nginx</h3>
<p>Nginx has various configuration files, its defaults are pretty good but you might want to take a look at it.</p>
<p>Use <a target="_blank" href="https://www.acunetix.com/blog/web-security-zone/hardening-nginx/">this tutorial on Acunetix</a> as a starting point.</p>
<p>There is also <a target="_blank" href="https://www.digitalocean.com/community/tools/nginx">this pretty nifty tool by DigitalOcean</a> that allows you to configure Nginx in a visual manner. It includes popular presets, for example for NodeJS and PHP applications.</p>
<h3 id="apache2">Apache2</h3>
<p>Apache might require more work, as its defaults leak some information about your system.</p>
<p>Start your configuration journey with <a target="_blank" href="https://www.tecmint.com/apache-security-tips/">this tutorial by Tecmint</a>.</p>
<h3 id="php">PHP</h3>
<h3 id="database">Database</h3>
<p>I have used MySQL and MariaDB on the past, by default their ports are opened externally, that shouldn't be allowed, as it is a security risk.</p>
<p>The database should only be allowed from local connections; or if ran externally, by whitelisted IPs.</p>
<ul>
<li>Learn how to configure MySQL with <a target="_blank" href="https://www.digitalocean.com/community/tutorials/how-to-install-mysql-on-ubuntu-20-04">this tutorial on DigitalOcean</a>.</li>
<li>Learn how to configure MariaDB with <a target="_blank" href="https://www.digitalocean.com/community/tutorials/how-to-install-mariadb-on-ubuntu-20-04">this tutorial on DigitalOcean</a>.</li>
</ul>
<hr />
<h2 id="recommendations">Recommendations</h2>
<h3 id="dokku">Dokku</h3>
<p>Now that you have your own secure infrastructure, you might want to create applications and services.</p>
<p><a target="_blank" href="http://dokku.viewdocs.io/dokku/">Dokku</a> is perfect for that. It allows you to containerize, build and run your applications with a simple <code>git push</code>.</p>
<p>Check out <a target="_blank" href="https://dev.to/alejandroakbal/create-your-own-heroku-with-dokku-on-digitalocean-14ef">my own tutorial</a> to learn how to set up and use Dokku.</p>
<blockquote>
<p><em>There are some parts that you might want to skip, as they are similar to this tutorial.</em></p>
</blockquote>
<hr />
<h2 id="end">End</h2>
<h3 id="useful-resources">Useful resources</h3>
<ul>
<li><a target="_blank" href="https://dev.to/phocks/how-to-get-a-free-google-server-forever-1fpf">How to get a free Google server forever</a>, a perfect test environment for this tutorial.</li>
<li><a target="_blank" href="https://dev.to/phocks/how-to-get-2x-oracle-cloud-servers-free-forever-4o22">How to get 2x Oracle Cloud servers free forever</a>, a more powerful alternative to the free GCE server.</li>
<li><a target="_blank" href="https://dev.to/alejandroakbal/create-your-own-heroku-with-dokku-on-digitalocean-14ef">Create your own Heroku with Dokku on DigitalOcean</a>, a guide to deploy your applications to your now-secure server.</li>
</ul>
<h3 id="self-promotion">Self promotion</h3>

<p>If you have found this tutorial useful then you should follow me, I will be posting more interesting content! :')</p>
<ul>
<li><a target="_blank" href="https://redirect.akbal.dev/github">GitHub</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/twitter">Twitter</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/dev.to">Dev.to</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/medium">Medium</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/linkedin">LinkedIn</a></li>
</ul>
<p>Or support me financially. &lt;3</p>
<ul>
<li><a target="_blank" href="https://redirect.akbal.dev/github/sponsor">GitHub Sponsors</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/liberapay">LiberaPay</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/paypal">PayPal</a></li>
</ul>

<h3 id="credit">Credit</h3>
<p>Thanks to</p>
<ul>
<li>Any linked website and community for their wonderful tutorials and help</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Create your own Heroku with Dokku on DigitalOcean]]></title><description><![CDATA[Introduction
So, you want to have your own infrastructure while having the best commodities to push your code to production, right?
Then you have found your guide. We will go through every single thing that you will need.
We will set up a server on D...]]></description><link>https://blog.akbal.dev/create-your-own-heroku-with-dokku-on-digitalocean</link><guid isPermaLink="true">https://blog.akbal.dev/create-your-own-heroku-with-dokku-on-digitalocean</guid><category><![CDATA[Heroku]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Alejandro Akbal]]></dc:creator><pubDate>Tue, 17 Nov 2020 15:36:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1607009988951/_LCxCPuJh.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[
<h1 id="introduction">Introduction</h1>
<p>So, you want to have your own infrastructure while having the best commodities to push your code to production, right?</p>
<p>Then you have found your guide. We will go through every single thing that you will need.</p>
<p><strong>We will set up a server on DigitalOcean, configure your application for usage with Dokku, learn how to push your code directly to production and finalize the guide by adding TLS/SSL to your application.</strong></p>
<p>The best thing? This is all free. (*)</p>
<blockquote>
<p>(*) <em>If you are a new user on DigitalOcean and register through a <a class="post-section-overview" href="#requirements-info">referral link</a></em>.</p>
</blockquote>
<hr />
<h2 id="0-before-we-start">0. Before we start</h2>
<h3 id="what-is-dokku">What is Dokku</h3>
<p>You might be wondering what Dokku is.</p>
<blockquote>
<p><a target="_blank" href="https://github.com/dokku/dokku">Dokku</a> is a docker-powered <strong>Platform-as-a-Service</strong> (PaaS) that helps you build and manage the lifecycle of applications.</p>
</blockquote>
<p>This means that it shares many similarities with Heroku, so if you have used Heroku before, this will come to you as familiar. For example, you can <strong>push code to deploy</strong>, use buildpacks, scale processes, etc</p>
<h3 id="preface">Preface</h3>
<p>For this guide I will be using Windows 10 with the <code>Git Bash</code> terminal, but everything should be the same on any GNU/Linux system.</p>
<p>The server will be a DigitalOcean's Ubuntu 20.04 image with Dokku already set up.</p>
<p>As an example application we will use this <a target="_blank" href="https://github.com/heroku/node-js-getting-started">NodeJS getting started project</a> by Heroku.</p>
<h3 id="requirements">Requirements</h3>
<ul>
<li>A DigitalOcean account</li>
<li>A Domain</li>
<li>A Terminal</li>
<li>A SSH key pair</li>
</ul>
<h3 id="requirements-info">Requirements info</h3>
<p>If you don't have a DigitalOcean account, you should create one, its free! And if you use my <a target="_blank" href="https://m.do.co/c/89d935019679">referral link</a> you will receive \$100 as credit for free!</p>
<p>If you don't have a SSH Key pair, then <a target="_blank" href="https://www.digitalocean.com/docs/droplets/how-to/add-ssh-keys/">follow this tutorial</a> to learn how to generate and add them to your DigitalOcean account.</p>
<hr />
<h2 id="1-droplet-creation">1. Droplet creation</h2>
<p>This might be the easiest step, as DigitalOcean offers a <a target="_blank" href="https://do.co/3nzKhrp">Ubuntu 20.04 image with Dokku already set up</a>, just follow that link and create a droplet.</p>
<p>Then just choose whatever options meet your needs.</p>
<blockquote>
<p>ÔÜá You should choose a Datacenter that is near you or your clients. ÔÜá</p>
</blockquote>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/1mdln5aicjyzlguc7yuq.PNG" alt="Datacenter Region" /></p>
<p>Once it's created, go to the "Networking" tab of your new droplet and assign a floating IP.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/8r8zri2yq1lyvs57skak.PNG" alt="Floating IP" /></p>
<p>Visit that IP on your browser and you'll find the Dokku set up.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/alo111qcfqd3rbjw0e0m.PNG" alt="Alt Text" /></p>
<p>Just fill the details with your public SSH Key and the domain you will be using. I recommend enabling <code>virtualhost naming</code>, as its easier and more common than using ports.</p>
<blockquote>
<p>Tip: If you had added the public SSH Key to your DigitalOcean before, the <code>Public SSH Key</code> form should have been filled automagically!</p>
</blockquote>
<p>For the sake of this guide, I will be using the subdomain <code>dokku</code> on my own domain, <a target="_blank" href="https://akbal.dev">akbal.dev</a>.</p>
<hr />
<h2 id="2-domain-management">2. Domain management</h2>
<p>My current domain manager is CloudFlare, but you can use any other domain registrar or manager, the instructions should be the same. ­ƒÿè</p>
<h3 id="dns-records">DNS records</h3>
<p>We have to configure our DNS settings to point to our new droplet's floating IP.</p>
<p>Start by adding a <strong><code>A</code></strong> type record with the hostname and floating IP you assigned earlier in the Dokku set up.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/af7brirn5r9bv1pwq0i3.PNG" alt="CloudFlare A record" /></p>
<p>You should also add a <strong>catch-all</strong> rule so anything you deploy on a subdomain is automatically forwarded to the droplet.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/bk71sau6852rtprtkkud.PNG" alt="CloudFlare A catch-all rule" /></p>
<blockquote>
<p><em>If you're using CloudFlare and have trouble connecting in the next steps, try to set the DNS record proxy to <code>DNS only</code> instead of <code>Proxied</code>, as it seems to cause trouble.</em></p>
</blockquote>
<hr />
<h2 id="3-local-configuration">3. Local configuration</h2>
<p>Before we start, we should configure some things on our end.</p>
<blockquote>
<p>­ƒÆ╗ These steps are done on our <strong>local machine</strong>.</p>
</blockquote>
<h3 id="add-ssh-key">Add SSH Key</h3>
<p>We have to add our Private SSH Key to our local terminal so we can authenticate and connect to the droplet.</p>
<pre><code class="lang-sh"><span class="hljs-built_in">eval</span> `ssh-agent -s` <span class="hljs-comment"># Start the agent that holds on to our keys</span>
ssh-add <span class="hljs-string">'~/path/to/ssh/private.key'</span> <span class="hljs-comment"># Add our private SSH key</span>
</code></pre>
<blockquote>
<p>ÔÜá This is an important step, if you don't add the private SSH key to the terminal, you won't be able to push code to Dokku later!</p>
</blockquote>
<p>We should check that the key was added successfully. If the output is empty, the key was <em>not</em> added.</p>
<pre><code class="lang-sh">ssh-add -l
</code></pre>
<p>Then lets try to connect to our droplet via SSH as the root user.</p>
<pre><code class="lang-sh">ssh root@&lt;domain&gt;

<span class="hljs-comment"># E.g.</span>
ssh root@dokku.akbal.dev
</code></pre>
<p>Voil├á, <strong>we are in</strong>!</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/yf40wqhzhyi0fb7jv3l0.PNG" alt="SSH Connection" /></p>
<hr />
<h2 id="4-application-set-up">4. Application set up</h2>
<p>We will need an application to run on Dokku, don't we?</p>
<blockquote>
<p>­ƒÆ╗ These steps are done on our <strong>local machine</strong>.</p>
</blockquote>
<h3 id="clone-repository">Clone repository</h3>
<p>Lets clone the <a class="post-section-overview" href="#preface">example application</a>.</p>
<pre><code class="lang-sh">git <span class="hljs-built_in">clone</span> https://github.com/heroku/node-js-getting-started.git

<span class="hljs-built_in">cd</span> node-js-getting-started
</code></pre>
<p>Notice that there is a file that you should be familiar with if you have used Heroku before, the <code>Procfile</code>. We will be talking about it in the next step.</p>
<h3 id="getting-familiar">Getting familiar</h3>
<p>Now that we have cloned the repository we can install the dependencies and play with the application if we want to.</p>
<pre><code class="lang-sh">npm install <span class="hljs-comment"># Install dependencies</span>

npm run start <span class="hljs-comment"># Start the application</span>
</code></pre>
<p>Now if we visit <code>http://localhost:5000</code> on our browser we should see the application running.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/hcc5nd1oruamwlslvypq.png" alt="Heroku's node-js-getting-started project" /></p>
<hr />
<h2 id="5-application-configuration">5. Application configuration</h2>
<p>We should configure our application so Dokku is able to run it.</p>
<p><em>You can <strong>skip this step if you are using the <a class="post-section-overview" href="#preface">example application</a></strong>, since it already has been configured.</em></p>
<blockquote>
<p>­ƒÆ╗ These steps are done on our <strong>local machine</strong>.</p>
</blockquote>
<h3 id="buildpacks-trivia">Buildpacks trivia</h3>
<p>Dokku uses Heroku's own Buildpacks to build your applications and detect what language it is using.</p>
<p>In this guide we are using a <a class="post-section-overview" href="#preface">NodeJS application</a>, so Dokku will detect and use Heroku's NodeJS Buildpack.</p>
<p><a target="_blank" href="https://devcenter.heroku.com/articles/nodejs-support">Learn more about the NodeJS buildpack.</a></p>
<h3 id="procfile">Procfile</h3>
<p>Dokku needs a <code>Procfile</code> to identify what <code>process types</code> and commands our application uses, <em>for example</em>, to start a web server.</p>
<p>You should create this file if you haven't. A common NodeJS <code>Procfile</code> looks like this:</p>
<pre><code class="lang-sh">web: node index.js

<span class="hljs-comment"># Or like this</span>
web: npm run start
</code></pre>
<p><a target="_blank" href="https://devcenter.heroku.com/articles/procfile">Learn more about the <code>Procfile</code> and <code>process types.</code></a></p>
<h3 id="node-version">Node version</h3>
<p>Dokku will download and use the NodeJS version specified in the <code>engines</code> section of our <code>package.json</code>.</p>
<pre><code class="lang-json"><span class="hljs-comment">// ...</span>
<span class="hljs-string">"engines"</span>: {
    <span class="hljs-attr">"node"</span>: <span class="hljs-string">"12.x"</span>
  },
</code></pre>
<p>You should add this part, otherwise Dokku will chose a version of NodeJS that might or might not work with your application.</p>
<h3 id="further-configuration">Further configuration</h3>
<p>You should check out the <a target="_blank" href="https://devcenter.heroku.com/articles/buildpacks">Buildpack documentation</a> of the language you're using to further configure things!</p>
<hr />
<h2 id="6-server-configuration">6. Server configuration</h2>
<p>Now that we can connect to the droplet and have set up our application, lets configure the server a bit.</p>
<blockquote>
<p>­ƒûÑ These steps are done on the <strong>Dokku droplet</strong>.</p>
</blockquote>
<h3 id="ram-swap">RAM Swap</h3>
<p>Chances are that you will be using Dokku on a low memory (&lt;2GB) droplet.</p>
<p>To fix possible failures due to low memory, we should create a bigger <a target="_blank" href="https://techterms.com/definition/swap_file">swap file</a>. Read <a target="_blank" href="http://dokku.viewdocs.io/dokku/getting-started/advanced-installation/#vms-with-less-than-1-gb-of-memory">this tutorial</a> to learn how to do so.</p>
<hr />
<h2 id="7-server-security">7. Server security</h2>
<p>While securing a server is not an easy task, the following instructions will take care of the most common problems.</p>
<blockquote>
<p>­ƒûÑ These steps are done on the <strong>Dokku droplet</strong>.</p>
</blockquote>
<h3 id="root-user">Root user</h3>
<p>We have been using the <code>root</code> user, but it is a <strong>security risk</strong>. We shouldn't be using it for everything, we should use instead our own user with <code>root</code> <strong>privileges</strong>.</p>
<p>Follow <a target="_blank" href="https://www.digitalocean.com/community/tutorials/how-to-create-a-new-sudo-enabled-user-on-ubuntu-20-04-quickstart">this guide</a> to learn how to create a new user with sudo privileges.</p>
<p>Once you have done that, we should copy the Public SSH Key from the <code>root</code> user to our own user.</p>
<pre><code class="lang-sh">USER=&lt;user&gt; <span class="hljs-comment"># Set the user variable</span>
mkdir -p /home/<span class="hljs-variable">$USER</span>/.ssh <span class="hljs-comment"># Create ssh directory</span>
chmod 700 /home/<span class="hljs-variable">$USER</span>/.ssh <span class="hljs-comment"># Apply directory permissions</span>
cp /root/.ssh/authorized_keys /home/<span class="hljs-variable">$USER</span>/.ssh/authorized_keys <span class="hljs-comment"># Copy the ssh authorized key</span>
chown -R <span class="hljs-variable">$USER</span>:<span class="hljs-variable">$USER</span> /home/<span class="hljs-variable">$USER</span>/.ssh <span class="hljs-comment"># Apply user and group permissions</span>
sudo chmod 600 /home/<span class="hljs-variable">$USER</span>/.ssh/authorized_keys <span class="hljs-comment"># Apply file permissions</span>


<span class="hljs-comment"># As a one-liner</span>
USER=&lt;user&gt;; mkdir -p /home/<span class="hljs-variable">$USER</span>/.ssh &amp;&amp; chmod 700 /home/<span class="hljs-variable">$USER</span>/.ssh &amp;&amp; cp /root/.ssh/authorized_keys /home/<span class="hljs-variable">$USER</span>/.ssh/authorized_keys &amp;&amp; chown -R <span class="hljs-variable">$USER</span>:<span class="hljs-variable">$USER</span> /home/<span class="hljs-variable">$USER</span>/.ssh &amp;&amp; sudo chmod 600 /home/<span class="hljs-variable">$USER</span>/.ssh/authorized_keys

<span class="hljs-comment"># E.g.</span>
USER=alejandro; mkdir -p /home/<span class="hljs-variable">$USER</span>/.ssh &amp;&amp; chmod 700 /home/<span class="hljs-variable">$USER</span>/.ssh &amp;&amp; cp /root/.ssh/authorized_keys /home/<span class="hljs-variable">$USER</span>/.ssh/authorized_keys &amp;&amp; chown -R <span class="hljs-variable">$USER</span>:<span class="hljs-variable">$USER</span> /home/<span class="hljs-variable">$USER</span>/.ssh &amp;&amp; sudo chmod 600 /home/<span class="hljs-variable">$USER</span>/.ssh/authorized_keys
</code></pre>
<p>Now try to reconnect to the droplet with the new user.</p>
<pre><code class="lang-sh">ssh &lt;user&gt;@&lt;domain&gt;

<span class="hljs-comment"># E.g.</span>
ssh alejandro@dokku.akbal.dev
</code></pre>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/kuyd4gmz0pib1he2r6og.PNG" alt="New user SSH connection" /></p>
<p>Perfect. It has worked like a charm.</p>
<h3 id="sshd">SSHD</h3>
<p>The default droplet configuration allows anyone to log in as the <code>root</code> user, this should be disabled as it is a security risk.</p>
<p>Look at the <strong>first step</strong> of <a target="_blank" href="https://www.digitalocean.com/community/tutorials/how-to-harden-openssh-on-ubuntu-18-04">this guide</a> to learn how to configure SSHD and disable <code>root</code> user login.</p>
<h3 id="docker-ports">Docker ports</h3>
<p>Docker ports are open by the default on the firewall, we should shut them, as it can be a security concern.</p>
<pre><code class="lang-sh">sudo ufw status <span class="hljs-comment"># Optional, check the current rules on the firewall</span>

<span class="hljs-comment"># Delete Docker rules</span>
sudo ufw delete allow 2375/tcp
sudo ufw delete allow 2376/tcp

sudo ufw status <span class="hljs-comment"># Optional, check that Docker's ports are deleted</span>
</code></pre>
<p>UFW is Ubuntu's firewall manager, <a target="_blank" href="https://wiki.ubuntu.com/UncomplicatedFirewall">learn more</a>.</p>
<h3 id="fail2ban">Fail2Ban</h3>
<p>Fail2Ban is a utility to <strong>protect your server from brute-force and automated attacks</strong>. It blocks repeated connection attempts and bans them temporarily.</p>
<p>Here is an <a target="_blank" href="https://linuxize.com/post/install-configure-fail2ban-on-ubuntu-20-04/">easy guide</a> on how to install, configure and use it.</p>
<hr />
<h2 id="8-dokku">8. Dokku</h2>
<p>Now that every step of configuration is complete, we can move to the actually interesting step: <strong>actually using Dokku</strong>!</p>
<blockquote>
<p>­ƒûÑ These steps are done on the <strong>Dokku droplet</strong>.</p>
</blockquote>
<h3 id="dokku-cli">Dokku CLI</h3>
<p>Dokku is used from the terminal, <em>it doesn't have an interface or a web view</em>, and that is its strong point, as it consumes less resources of your system that your applications will need.</p>
<p>To start using Dokku just type <code>dokku</code> on the terminal.</p>
<pre><code class="lang-sh">dokku
</code></pre>
<h3 id="common-dokku-commands">Common Dokku commands</h3>
<p>These are some commands that you will definitely have to use, so visit the documentation links and get familiar with them.</p>
<ul>
<li><a target="_blank" href="http://dokku.viewdocs.io/dokku/deployment/application-management/">Application Management</a></li>
<li><a target="_blank" href="http://dokku.viewdocs.io/dokku/deployment/process-management/">Process Management</a></li>
<li><a target="_blank" href="http://dokku.viewdocs.io/dokku/configuration/domains/">Domain Configuration</a></li>
</ul>
<h3 id="app-creation">App creation</h3>
<p>First step will be to create an application.</p>
<pre><code class="lang-sh">dokku apps:create &lt;app&gt;

<span class="hljs-comment"># E.g.</span>
dokku apps:create my-app
</code></pre>
<p>The app <em>shell</em> is now created, but empty. Until we push code it won't do anything.</p>
<p>But before doing so we should configure other aspects, like the domain it is going to use.</p>
<p><a target="_blank" href="http://dokku.viewdocs.io/dokku/deployment/application-management/">Read more about application management in Dokku.</a></p>
<h3 id="domain-configuration">Domain configuration</h3>
<p>Lets add a domain so we can access our application easily from our browser once its deployed.</p>
<pre><code class="lang-sh">dokku domains:add &lt;app&gt; &lt;domain&gt;

<span class="hljs-comment"># E.g.</span>
dokku domains:add my-app my-app.dokku.akbal.dev
</code></pre>
<p><a target="_blank" href="http://dokku.viewdocs.io/dokku/configuration/domains/">Read more about managing domains in Dokku.</a></p>
<h3 id="environment-configuration">Environment configuration</h3>
<p>If our application uses any environment variables, <em>for example an <code>.env</code> file</em>, you should add them through Dokku.</p>
<pre><code class="lang-sh">dokku config:<span class="hljs-built_in">set</span> &lt;app&gt; &lt;variables&gt;

<span class="hljs-comment"># E.g.</span>
dokku config:<span class="hljs-built_in">set</span> my-app NODE_ENV=production

<span class="hljs-comment"># You can add multiple environment variables.</span>
dokku config:<span class="hljs-built_in">set</span> my-app NODE_ENV=production SECURITY_KEY=XXX
</code></pre>
<blockquote>
<p>Keep in mind that executing this command will restart the application.</p>
</blockquote>
<p><a target="_blank" href="http://dokku.viewdocs.io/dokku/configuration/environment-variables/">Read more about managing environment variables in Dokku.</a></p>
<h3 id="deploying-code">Deploying code</h3>
<p>Now its time for the real deal, we have configured everything on our local machine and on the server, so <strong>lets deploy</strong>!</p>
<blockquote>
<p>­ƒÆ╗ These steps are done on our <strong>local machine</strong>.</p>
</blockquote>
<p>Lets add Dokku as a git remote so we can push code to it.</p>
<pre><code class="lang-sh">git remote add &lt;remote-name&gt; dokku@&lt;remote&gt;:&lt;application&gt;

<span class="hljs-comment"># E.g.</span>
git remote add dokku dokku@dokku.akbal.dev:my-app
</code></pre>
<p>Only thing left is to push the code, lets try!</p>
<pre><code class="lang-sh">git push &lt;remote-name&gt; &lt;local-branch&gt;:master

<span class="hljs-comment"># E.g.</span>
git push dokku main

<span class="hljs-comment"># Or if your branch is already master</span>
git push dokku master
</code></pre>
<p><strong>And that is it!</strong> Once the code is pushed you should see Dokku detecting and building your application! ­ƒÑ░</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/5g8ilnovup6lkgh6tz1d.PNG" alt="Successful deploy" /></p>
<blockquote>
<p>If this step is giving you trouble, chances are that you might not have added your private SSH Key to your current terminal, learn more in the <em><a class="post-section-overview" href="#add-ssh-key">Add SSH Key</a></em> step.</p>
</blockquote>
<p>Once Dokku is done it will output a link to visit your application in your browser, do that!</p>
<blockquote>
<p>_The website might not work because Dokku forces <a target="_blank" href="https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security">HSTS</a>, and we haven't configured TLS/SSL yet. <a target="_blank" href="http://dokku.viewdocs.io/dokku/configuration/nginx/#hsts-header">Learn how to disable HSTS.</a>_</p>
</blockquote>
<h3 id="tlsssl-configuration">TLS/SSL configuration</h3>
<p>We will add the <a target="_blank" href="https://github.com/dokku/dokku-letsencrypt">lets encrypt plugin</a> to Dokku so the application will be served with <strong>HTTPS</strong>.</p>
<p>First add the <code>letsencrypt</code> plugin to Dokku.</p>
<pre><code class="lang-sh">sudo dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git
</code></pre>
<p>Then add a <strong>required</strong> <code>letsencrypt</code> contact email through environment variables.</p>
<pre><code class="lang-sh">dokku config:<span class="hljs-built_in">set</span> &lt;app&gt; DOKKU_LETSENCRYPT_EMAIL=&lt;email&gt;

<span class="hljs-comment"># E.g.</span>
dokku config:<span class="hljs-built_in">set</span> my-app DOKKU_LETSENCRYPT_EMAIL=contact@akbal.dev

<span class="hljs-comment"># We can set the contact email globally too, so it works for any application.</span>
dokku config:<span class="hljs-built_in">set</span> --global DOKKU_LETSENCRYPT_EMAIL=&lt;email&gt;
</code></pre>
<p>Now its time to generate a certificate for our application.</p>
<pre><code class="lang-sh">dokku letsencrypt &lt;app&gt;

<span class="hljs-comment"># E.g.</span>
dokku letsencrypt my-app
</code></pre>
<p>It was that easy to add <strong>HTTPS</strong>.</p>
<p>Check our super secure site!</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/8rky1dlsnndvvc0i8vxg.PNG" alt="Application with TLS" /></p>
<p><a target="_blank" href="https://github.com/dokku/dokku-letsencrypt">Read more about configuring the letsencrypt plugin.</a></p>
<h3 id="scale-processes">Scale processes</h3>
<p>Scaling the processes is really easy, you just have to tell Dokku and it will handle everything.</p>
<blockquote>
<p>­ƒûÑ These steps are done on the <strong>Dokku droplet</strong>.</p>
</blockquote>
<pre><code class="lang-sh">dokku ps:scale &lt;app&gt; &lt;process-type&gt;=&lt;count&gt;

<span class="hljs-comment"># E.g.</span>
dokku ps:scale my-app web=1

<span class="hljs-comment"># Even multiple types</span>
dokku ps:scale my-app web=1 worker=2
</code></pre>
<p><a target="_blank" href="http://dokku.viewdocs.io/dokku/deployment/process-management/#psscale-command">Read more about scaling processes in Dokku.</a></p>
<hr />
<h2 id="9-misc">9. Misc</h2>
<h3 id="further-knowledge">Further knowledge</h3>
<p>I encourage you to go to the <a target="_blank" href="http://dokku.viewdocs.io/dokku/">Dokku documentation</a> and toy with your <strong>now complete</strong> Dokku set up. You will learn many great things like <a target="_blank" href="http://dokku.viewdocs.io/dokku/deployment/logs/">how to read the logs</a> of your application.</p>
<h3 id="troubleshooting">Troubleshooting</h3>
<p>{% collapsible I need to identify what is going wrong %}</p>
<p>Enable <a target="_blank" href="http://dokku.viewdocs.io/dokku/getting-started/troubleshooting/">Trace Mode</a>.</p>
<p>In this mode output will be more verbose, helping you locate any error!</p>
<p>{% endcollapsible %}</p>

<p>{% collapsible Can't push code to Dokku remote %}</p>
<p>This might be because you have not added the private SSH Key to your terminal, try adding it!</p>
<p>{% endcollapsible %}</p>

<p>{% collapsible Dokku remote Git branch is ahead of time %}</p>
<p>I've been in a situation where I had commited to Dokku and went back locally to an earlier commit, this created many problems when trying to push again, because Dokku's own git repository was <em>in the future</em>. Thankfully, theres a very easy solution: force push! ­ƒÆ¬</p>
<pre><code class="lang-sh">git push --force dokku &lt;branch&gt;

<span class="hljs-comment"># E.g.</span>
git push --force dokku main
</code></pre>
<p>{% endcollapsible %}</p>

<p>{% collapsible Agent returned different signature type ssh-rsa (expected rsa-sha2-512) (<em>Windows 10</em>) %}</p>
<p>It seems that the ssh binaries on windows 10 are outdated and the connection is refused by the server, a solution is to use <code>Git Bash</code> or any other terminal with updated binaries, more on this <a target="_blank" href="https://github.com/PowerShell/Win32-OpenSSH/issues/1263">issue here</a></p>
<p>{% endcollapsible %}</p>
<hr />
<h2 id="10-end">10. End</h2>
<p>I hope you have enjoyed my complete guide and find it useful, It has taken many days to bring you this knowledge.</p>
<h3 id="useful-links">Useful links</h3>
<p>These are some resources that can be very useful through your Dokku journey.</p>
<ul>
<li><a target="_blank" href="https://cheatography.com/jejete/cheat-sheets/dokku/pdf/">Dokku cheatsheet by jejete</a></li>
<li><a target="_blank" href="https://glider-slackin.herokuapp.com/">Dokku Slack community</a></li>
</ul>
<h3 id="self-promotion">Self promotion</h3>

<p>If you have found this tutorial useful then you should follow me, I will be posting more interesting content! :')</p>
<ul>
<li><a target="_blank" href="https://redirect.akbal.dev/github">GitHub</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/twitter">Twitter</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/dev.to">Dev.to</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/medium">Medium</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/linkedin">LinkedIn</a></li>
</ul>
<p>Or support me financially. &lt;3</p>
<ul>
<li><a target="_blank" href="https://redirect.akbal.dev/github/sponsor">GitHub Sponsors</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/liberapay">LiberaPay</a></li>
<li><a target="_blank" href="https://redirect.akbal.dev/paypal">PayPal</a></li>
</ul>

<p>If you want to continue learning check out this "<strong><a target="_blank" href="https://dev.to/alejandroakbal/how-to-completely-secure-an-ubuntu-server-55i2">How to completely secure an Ubuntu server</a></strong>" article by myself.</p>
<h3 id="credit">Credit</h3>
<p>This guide was made possible thanks to many wonderful people.</p>
<ul>
<li>Linuxize</li>
<li>DigitalOcean and their community</li>
<li>AskUbuntu for the <a target="_blank" href="https://askubuntu.com/questions/1218023/copying-ssh-key-from-root-to-another-user-on-same-machine">troubleshooting help</a></li>
</ul>
]]></content:encoded></item></channel></rss>