<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Bits and pieces]]></title><description><![CDATA[bit by bit]]></description><link>https://bojana.dev/</link><image><url>https://bojana.dev/favicon.png</url><title>Bits and pieces</title><link>https://bojana.dev/</link></image><generator>Ghost 5.77</generator><lastBuildDate>Sat, 11 Apr 2026 19:48:37 GMT</lastBuildDate><atom:link href="https://bojana.dev/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Self-Hosting n8n – Your Automation Sidekick (with a Dash of AI)]]></title><description><![CDATA[<p>In this post, I&#x2019;ll walk you through <strong>how to fully self-host the n8n automation platform</strong> &#x2014; from buying a domain and renting a VPS, to deploying it with Docker. Whether you&apos;re building workflows, scraping data, or experimenting with AI agents, n8n is flexible, extendable, and best</p>]]></description><link>https://bojana.dev/self-hosting-n8n-your-automation-sidekick-with-a-dash-of-ai/</link><guid isPermaLink="false">684fbd529751fd03289f01b2</guid><dc:creator><![CDATA[Bojana Dejanovic]]></dc:creator><pubDate>Mon, 16 Jun 2025 08:33:20 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1563207153-f403bf289096?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDQyfHxyb2JvdHxlbnwwfHx8fDE3NTAwNjEyMzN8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1563207153-f403bf289096?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDQyfHxyb2JvdHxlbnwwfHx8fDE3NTAwNjEyMzN8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="Self-Hosting n8n &#x2013; Your Automation Sidekick (with a Dash of AI)"><p>In this post, I&#x2019;ll walk you through <strong>how to fully self-host the n8n automation platform</strong> &#x2014; from buying a domain and renting a VPS, to deploying it with Docker. Whether you&apos;re building workflows, scraping data, or experimenting with AI agents, n8n is flexible, extendable, and best of all &#x2014; yours to control.<br>This guide is long, but contains all the necessary details.</p><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">&#x1F4CC; Official n8n self-hosting docs:<br><a href="https://docs.n8n.io/hosting/installation/server-setups/docker-compose/?ref=bojana.dev" rel="noopener noreferrer">docs.n8n.io/hosting/installation/server-setups/docker-compose</a></div></div><p>We&#x2019;re all feeling the <em>AI-everywhere</em> moment &#x2014; from indie hackers to enterprises, everyone is &#x201C;AI-washing&#x201D; their workflows, products, and even job titles. <br>There&#x2019;s this ongoing fear that AI will soon replace programmers, but let&#x2019;s be honest: <strong>the truth, as always, lies somewhere in the middle</strong>.</p><p>Will AI <em>assist</em> us? Absolutely.</p><p>Will it <em>replace</em> developers entirely? Not anytime soon.</p><p>That&#x2019;s where tools like <strong>n8n</strong> come in. While it&#x2019;s not entirely (although a big portion is&#x1F60A;) branded as an &quot;AI tool&quot;, because of its ability to integrate with LLMs and even build <a href="https://docs.n8n.io/advanced-ai/intro-tutorial/?ref=bojana.dev">autonomous AI agents</a>, it&apos;s riding the wave and helping automate tasks that previously required code or manual effort.</p><h3 id="what-you%E2%80%99ll-learn">What You&#x2019;ll Learn</h3><p>By the end of this guide, you&#x2019;ll know how to:</p><ol><li>Set up a secure VPS (Virtual Private Server)</li><li>Purchase and configure a custom domain</li><li>Deploy and run n8n with Docker</li><li>Build your own private, extensible automation system &#x2014; no subscription required</li></ol><p>Here are the sections for quick reference:</p><ul>
<li><a href="#setup-vps">&#x1F5C4;&#xFE0F;Setup a secure VPS</a>
<ul>
<li><a href="#ssh-keys">&#x1F510;SSH Keys &#x2014; Securing Access to Your VPS</a></li>
</ul>
</li>
<li><a href="#setup-domain">&#x1F310;Purchase and configure a custom domain</a></li>
<li><a href="#setup-n8n">&#x1F916;Install n8n</a>
<ul>
<li><a href="#note-on-license">&#x1F9FE;A Note on Licensing &#x2014; n8n Isn&#x2019;t Fully Open Source</a></li>
</ul>
</li>
</ul>
<div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">A quick heads-up from the official docs: <br>n8n recommends self-hosting only for experienced users. Mistakes can lead to data loss, security issues, or downtime. If you&apos;re unsure, try <a href="https://app.n8n.cloud/register?ref=bojana.dev" rel="noreferrer">n8n Cloud </a>instead.</div></div><p>That said &#x2014; if you&apos;re comfortable with a bit of Linux, Docker, and DNS configuration, or you&apos;re curious and ready to learn &#x2014; this guide is for you.</p><p></p>
<!--kg-card-begin: html-->
<a id="setup-vps"><h2>&#x1F5C4;&#xFE0F;Set up a secure VPS</h2></a>
<!--kg-card-end: html-->
<p>For using a VPS I recommend also purchasing a VPS from any of cloud providers. My choice (again unaffiliated) is <a href="https://www.hetzner.com/cloud?ref=bojana.dev">Hetzner</a>.</p><p>Click a new project on their <a href="https://console.hetzner.cloud/projects?ref=bojana.dev">cloud console dashboard</a> and name project to your liking (ex. N8N Automation).</p><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2025/06/Hetzner_AddNewProject.png" class="kg-image" alt="Self-Hosting n8n &#x2013; Your Automation Sidekick (with a Dash of AI)" loading="lazy" width="1374" height="516" srcset="https://bojana.dev/content/images/size/w600/2025/06/Hetzner_AddNewProject.png 600w, https://bojana.dev/content/images/size/w1000/2025/06/Hetzner_AddNewProject.png 1000w, https://bojana.dev/content/images/2025/06/Hetzner_AddNewProject.png 1374w" sizes="(min-width: 720px) 720px"></figure><p>And after that click &#x201C;+Create server&#x201D;</p><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2025/06/Hetzner__CreateServer.png" class="kg-image" alt="Self-Hosting n8n &#x2013; Your Automation Sidekick (with a Dash of AI)" loading="lazy" width="634" height="296" srcset="https://bojana.dev/content/images/size/w600/2025/06/Hetzner__CreateServer.png 600w, https://bojana.dev/content/images/2025/06/Hetzner__CreateServer.png 634w"></figure><p>Chose the location closest to your location and in Image section click on &#x201C;Apps&#x201D; and choose &#x201C;Docker CE&#x201D;. This will install Ubuntu 24.04 together with Docker.</p><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2025/06/Hetzner_Docker.png" class="kg-image" alt="Self-Hosting n8n &#x2013; Your Automation Sidekick (with a Dash of AI)" loading="lazy" width="1397" height="1004" srcset="https://bojana.dev/content/images/size/w600/2025/06/Hetzner_Docker.png 600w, https://bojana.dev/content/images/size/w1000/2025/06/Hetzner_Docker.png 1000w, https://bojana.dev/content/images/2025/06/Hetzner_Docker.png 1397w" sizes="(min-width: 720px) 720px"></figure><p>Next, for the Type of the VPS we will choose &#x201C;Shared vCPU&#x201D; and based on our app selection Hetzner recommends CPX21 which is a machine with 3 VCPUS <strong>4GB</strong> of RAM and <strong>80GB</strong> of SSD storage, that will cost you <strong>&#x20AC;7.05 /month</strong>.</p><p>If you check <a href="https://docs.n8n.io/embed/prerequisites/?ref=bojana.dev">prerequisites</a> on n8n documentation, it states that n8n is not CPU intensive,so this should be more than enough, though the disclaimer is that it varies depending on number of users, workflows and executions.</p><p>So since this is the case, and the fact you can rescale (scale up - bump up CPU and RAM) on Hetzner&#x2019;s VPS, we will actually choose CX22 with <strong>2VCPUS 4GB</strong> of RAM and <strong>40GB</strong> SSD storage, that will cost you only <strong>&#x20AC;3.29/month</strong>.</p><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2025/06/Hetzner_CX22.png" class="kg-image" alt="Self-Hosting n8n &#x2013; Your Automation Sidekick (with a Dash of AI)" loading="lazy" width="1383" height="1053" srcset="https://bojana.dev/content/images/size/w600/2025/06/Hetzner_CX22.png 600w, https://bojana.dev/content/images/size/w1000/2025/06/Hetzner_CX22.png 1000w, https://bojana.dev/content/images/2025/06/Hetzner_CX22.png 1383w" sizes="(min-width: 720px) 720px"></figure><p>Next, in the Networking section you can leave default selection of IPv4 and IPv6. (Note: having public IP costs &#x20AC;0.5/month)</p>
<!--kg-card-begin: html-->
<a id="ssh-keys"><h2>&#x1F510;SSH Keys &#x2014; Securing Access to Your VPS</h2></a>
<!--kg-card-end: html-->
<p>This is an important step &#x2014; <strong>your VPS will have a public IP</strong>, which means it&apos;s directly exposed to the internet. Even if you&apos;re just setting up a hobby project, <strong>you should always secure access properly</strong>. The best practice is to use <strong>SSH key authentication</strong> instead of passwords.</p><p>We&apos;ll start by generating a new SSH key:</p><pre><code class="language-bash">ssh-keygen -t ed25519 -C &quot;n8n-selfhost&quot;</code></pre><p>You&#x2019;ll be prompted to:</p><ol><li>Choose where to save the key (press <code>Enter</code> to use the default: <code>~/.ssh/id_ed25519</code>)</li><li>Set a passphrase (optional but recommended)</li></ol><p>Example output of <code>ssh-keygen</code> :</p><pre><code class="language-bash">bojana@7OfNine:~$ ssh-keygen -t ed25519 -C &quot;n8n-selfhost&quot;
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/bojana/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/bojana/.ssh/id_ed25519
Your public key has been saved in /home/bojana/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:UujX91QnAsb1rrVAAMSS07KbgUxa3GI39zGIYB2OoRQ n8n-selfhost
The key&apos;s randomart image is:
+--[ED25519 256]--+
|  Eo++.Bo++..    |
| . o*+@.=.+o .   |
|  .*.+oB.. oo o o|
|  . o.o. ... o o.|
|      o+S . o +  |
|      oo   . * . |
|            . o  |
|                 |
|                 |
+----[SHA256]-----+</code></pre><p>Now do:</p><pre><code class="language-bash">bojana@7OfNine:~$ cat ~/.ssh/id_ed25519.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIC4w30a+sB56njYFmueuHIs2WMxhqaFTruSjb9coSayT n8n-selfhost</code></pre><p>And copy the output of the public key to the Hetzner&#x2019;s form in SSH keys section:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://bojana.dev/content/images/2025/06/Hetzner_AddSshKey.png" class="kg-image" alt="Self-Hosting n8n &#x2013; Your Automation Sidekick (with a Dash of AI)" loading="lazy" width="866" height="864" srcset="https://bojana.dev/content/images/size/w600/2025/06/Hetzner_AddSshKey.png 600w, https://bojana.dev/content/images/2025/06/Hetzner_AddSshKey.png 866w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Paste your public key (id_ed25519.pub) here. It should start with ssh-ed25519 and be a single line.</span></figcaption></figure><p>And click <strong>&#x201C;Add SSH key&#x201D;</strong></p><p>Next, we have option to attach volumes (additional storage), create firewalls (we will use ufw on the VPS itself for this) and/or use backups. Depending of seriousness of this project, you can choose to or not this option, it&#x2019;s <strong>&#x20AC;0.66/month</strong> and it will create a copies of your server&#x2019;s disk every day.</p><p>Finally, we can give this server a name, choose whatever will be helpful to you and hit &#x201C;Create &amp; Buy now&#x201D; and wait until VPS is provisioned.</p><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2025/06/Hetzner_CreateAndBuyNow.png" class="kg-image" alt="Self-Hosting n8n &#x2013; Your Automation Sidekick (with a Dash of AI)" loading="lazy" width="1896" height="1618" srcset="https://bojana.dev/content/images/size/w600/2025/06/Hetzner_CreateAndBuyNow.png 600w, https://bojana.dev/content/images/size/w1000/2025/06/Hetzner_CreateAndBuyNow.png 1000w, https://bojana.dev/content/images/size/w1600/2025/06/Hetzner_CreateAndBuyNow.png 1600w, https://bojana.dev/content/images/2025/06/Hetzner_CreateAndBuyNow.png 1896w" sizes="(min-width: 720px) 720px"></figure><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2025/06/Hetzner_CreateAndBuyNow.png" class="kg-image" alt="Self-Hosting n8n &#x2013; Your Automation Sidekick (with a Dash of AI)" loading="lazy" width="1896" height="1618" srcset="https://bojana.dev/content/images/size/w600/2025/06/Hetzner_CreateAndBuyNow.png 600w, https://bojana.dev/content/images/size/w1000/2025/06/Hetzner_CreateAndBuyNow.png 1000w, https://bojana.dev/content/images/size/w1600/2025/06/Hetzner_CreateAndBuyNow.png 1600w, https://bojana.dev/content/images/2025/06/Hetzner_CreateAndBuyNow.png 1896w" sizes="(min-width: 720px) 720px"></figure><p>Once the VPS is provisioned, copy the Public IP assigned to it and do:</p><pre><code class="language-bash">#:~$ ssh root@&lt;VPS-Public-IP&gt;</code></pre><p>If everything went well you should be prompted with similar message as bellow, and asked to provide passphrase for a key if you created one after which you should be logged into your Ubuntu 24.04 instance!</p><pre><code class="language-bash">The authenticity of host &apos;&lt;VPS-Public-IP&gt;&apos; can&apos;t be established.
ECDSA key fingerprint is SHA256:EHJB0s61tt4WcsuGPkhTRIn6CjuLz0lGOItY3mkT/dA.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added &apos;&lt;VPS-Public-IP&gt;&apos; (ECDSA) to the list of known hosts.
Enter passphrase for key &apos;/home/bojana/.ssh/id_ed25519&apos;:
Welcome to Ubuntu 24.04.1 LTS (GNU/Linux 6.8.0-52-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

 System information as of Fri Jun 13 12:16:15 PM UTC 2025

  System load:  0.05              Processes:             129
  Usage of /:   4.3% of 37.23GB   Users logged in:       0
  Memory usage: 6%                IPv4 address for eth0: 91.99.136.92
  Swap usage:   0%                IPv6 address for eth0: 2a01:4f8:1c1a:3f29::1


Expanded Security Maintenance for Applications is not enabled.

0 updates can be applied immediately.

Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status


The list of available updates is more than a week old.
To check for new updates run: sudo apt update

root@n8n-automation:~#</code></pre><p>Let&#x2019;s verify docker and docker compose are there:</p><pre><code class="language-bash">root@n8n-automation:~# docker -v
Docker version 27.5.1, build 9f9e405
root@n8n-automation:~# docker compose version
Docker Compose version v2.32.4</code></pre><p>Before proceeding with n8n installation let&#x2019;s take a few steps related to mentioned security and ssh access. We already connected using generated ssh key which is great, although connecting/sshing with root user is not recommended.</p><p>Let&#x2019;s create non-root user with sudo privileges:</p><pre><code class="language-bash">root@n8n-automation:~# adduser bojana</code></pre><p>And add user to the sudo group:</p><pre><code class="language-bash">root@n8n-automation:~# usermod -aG sudo bojana</code></pre><p>Now, try logging in with newly created user:</p><pre><code class="language-bash">bojana@7OfNine:~$ ssh bojana@&lt;VPS-Public-IP&gt;</code></pre><p>We also want to allow login as the new user (with only the key):</p><pre><code class="language-bash">rsync --archive --chown=username:username ~/.ssh /home/username</code></pre><p>Now, to prevent root login and enforce key-only auth:</p><pre><code class="language-bash">nano /etc/ssh/sshd_config</code></pre><p>And ensure these lines are set:</p><pre><code class="language-bash">PermitRootLogin prohibit-password
PubkeyAuthentication yes</code></pre><p>Then restart the ssh service:</p><pre><code class="language-bash">systemctl restart ssh</code></pre><p></p>
<!--kg-card-begin: html-->
<a id="setup-domain"><h2>&#x1F310;Purchasing domain</h2></a>
<!--kg-card-end: html-->
<p>If you already own a domain, you don&apos;t have to purchase a new one. We can create a subdomain and use that when configuring n8n. If you don&apos;t own one, you can purchase one from domain registers such as:</p><ul><li>Porkbun</li><li>Namecheap</li><li>Hostinger</li></ul><p>Or any other. I use porkbun for most of my projects, and haven&apos;t had any issues so far. I recommend either .dev or .cloud or even .xyz as they will cost you anywhere between 2$ and 8$ for a year. (Note: they usually renew and at bit higher prices so pay attention)</p><p>When you purchased a domain, and created account, say on Porkbun, you will want to go to their &#x201C;domain management&#x201D; dashboard and find your domain.</p><p>After that you select &quot;DNS&#x201D; which will take you to the page where you are able to manage DNS records for the domain. We simply need to insert an A record for our subdomain.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://bojana.dev/content/images/2025/06/image.png" class="kg-image" alt="Self-Hosting n8n &#x2013; Your Automation Sidekick (with a Dash of AI)" loading="lazy" width="1266" height="837" srcset="https://bojana.dev/content/images/size/w600/2025/06/image.png 600w, https://bojana.dev/content/images/size/w1000/2025/06/image.png 1000w, https://bojana.dev/content/images/2025/06/image.png 1266w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">A record for n8n subdomain</span></figcaption></figure><p>Click add and that&apos;s it. Depending on TTL it your new subdomain should propagate rather quickly.</p><p>To verify new subdomain resolves to your VPS&apos;s IP do:</p><pre><code class="language-bash">dig +short n8n.mydmomain.com</code></pre><p></p>
<!--kg-card-begin: html-->
<a id="note-on-license"><h2>&#x1F9FE;A Note on Licensing &#x2014; n8n Isn&#x2019;t Fully Open Source</h2></a>
<!--kg-card-end: html-->
<p>While n8n offers self-hosting and provides source code access, it&#x2019;s <strong>not licensed under a permissive open-source license</strong> like MIT or Apache 2.0. <br>Instead, it uses a <strong>Sustainable Use License (SUL)</strong> &#x2014; a custom license designed to allow personal and internal use, while preventing commercial exploitation of the product itself.</p><p>This is important to understand <strong>before building a product or service around n8n</strong>.</p><p>Here&#x2019;s a quick breakdown:</p><h3 id="%E2%9C%85what-is-allowed-under-the-sul">&#x2705;What <em>is allowed</em> under the SUL:</h3><ul><li>Self-hosting n8n for personal use or inside your company/team</li><li>Using it to automate tasks, processes, or workflows internally</li><li>Extending or customizing n8n for your own non-commercial needs</li><li>Embedding n8n in an internal tool used only within your organization</li></ul><h3 id="%E2%9D%8C-what-is-not-allowed-under-the-sul">&#x274C; What <em>is not allowed</em> under the SUL:</h3><ul><li>Selling n8n as a service (e.g., &quot;n8n Cloud clone&quot; or &quot;automation-as-a-service&quot;)</li><li>Embedding n8n in a commercial SaaS offering</li><li>Rebranding and reselling the platform or charging others to access your hosted version</li><li>Offering hosted n8n to external users/customers, even if it&apos;s &#x201C;free but monetized indirectly&#x201D;</li></ul><p>With this said, let&apos;s start with the first step in the guide.</p>
<!--kg-card-begin: html-->
<a id="setup-n8n"><h2>&#x1F916;Installing n8n</h2></a>
<!--kg-card-end: html-->
<p>Finally, we are at step where we want to setup the actual n8n platform.<br>In the previous step we verified that the docker and docker-compose are installed. We can optionally grant access to non-root user</p><pre><code class="language-bash">sudo usermod -aG docker ${USER}
# Register the `docker` group memebership with current session without changing your primary group
exec sg docker newgrp</code></pre><p>Now, you should be able to user docker commands without sudo:</p><pre><code class="language-bash">bojana@n8n-automation:~$ docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES</code></pre><p>Now, we want to setup some env variables required for n8n installation.<br>Let&apos;s create a dir with the required .env file and (later) docker-compose file:</p><pre><code class="language-bash">bojana@n8n-automation:~$ mkdir n8n-compose
bojana@n8n-automation:~$ cd n8n-compose/
bojana@n8n-automation:~/n8n-compose$</code></pre><p>Now, within this folder create an .env file and paste the contents below (with changes lines with your domain/email etc.)</p><pre><code class="language-bash">bojana@n8n-automation:~/n8n-compose$ nano .env</code></pre><pre><code class="language-bash"># DOMAIN_NAME and SUBDOMAIN together determine where n8n will be reachable from
# The top level domain to serve from
DOMAIN_NAME=example.com

# The subdomain to serve from
SUBDOMAIN=n8n

# The above example serve n8n at: https://n8n.example.com

# Optional timezone to set which gets used by Cron and other scheduling nodes
# New York is the default value if not set
GENERIC_TIMEZONE=Europe/Berlin

# The email address to use for the TLS/SSL certificate creation
SSL_EMAIL=user@example.com</code></pre><p>Next, <a href="https://docs.n8n.io/hosting/installation/server-setups/docker-compose/?ref=bojana.dev">n8n documentation</a> suggest creating directory <code>local-files</code> for sharing files between n8n instance and the host system. These files should be created with docker compose, but this ensures proper permissions and ownership are set.</p><pre><code class="language-bash">bojana@n8n-automation:~/n8n-compose$ mkdir local-files
bojana@n8n-automation:~/n8n-compose$ ls
local-files</code></pre><p>Finally, we create a following <code>compose.yaml</code> file:</p><pre><code class="language-bash">services:
  traefik:
    image: &quot;traefik&quot;
    restart: always
    command:
      - &quot;--api.insecure=true&quot;
      - &quot;--providers.docker=true&quot;
      - &quot;--providers.docker.exposedbydefault=false&quot;
      - &quot;--entrypoints.web.address=:80&quot;
      - &quot;--entrypoints.web.http.redirections.entryPoint.to=websecure&quot;
      - &quot;--entrypoints.web.http.redirections.entrypoint.scheme=https&quot;
      - &quot;--entrypoints.websecure.address=:443&quot;
      - &quot;--certificatesresolvers.mytlschallenge.acme.tlschallenge=true&quot;
      - &quot;--certificatesresolvers.mytlschallenge.acme.email=${SSL_EMAIL}&quot;
      - &quot;--certificatesresolvers.mytlschallenge.acme.storage=/letsencrypt/acme.json&quot;
    ports:
      - &quot;80:80&quot;
      - &quot;443:443&quot;
    volumes:
      - traefik_data:/letsencrypt
      - /var/run/docker.sock:/var/run/docker.sock:ro

  n8n:
    image: docker.n8n.io/n8nio/n8n
    restart: always
    ports:
      - &quot;127.0.0.1:5678:5678&quot;
    labels:
      - traefik.enable=true
      - traefik.http.routers.n8n.rule=Host(`${SUBDOMAIN}.${DOMAIN_NAME}`)
      - traefik.http.routers.n8n.tls=true
      - traefik.http.routers.n8n.entrypoints=web,websecure
      - traefik.http.routers.n8n.tls.certresolver=mytlschallenge
      - traefik.http.middlewares.n8n.headers.SSLRedirect=true
      - traefik.http.middlewares.n8n.headers.STSSeconds=315360000
      - traefik.http.middlewares.n8n.headers.browserXSSFilter=true
      - traefik.http.middlewares.n8n.headers.contentTypeNosniff=true
      - traefik.http.middlewares.n8n.headers.forceSTSHeader=true
      - traefik.http.middlewares.n8n.headers.SSLHost=${DOMAIN_NAME}
      - traefik.http.middlewares.n8n.headers.STSIncludeSubdomains=true
      - traefik.http.middlewares.n8n.headers.STSPreload=true
      - traefik.http.routers.n8n.middlewares=n8n@docker
    environment:
      - N8N_HOST=${SUBDOMAIN}.${DOMAIN_NAME}
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - NODE_ENV=production
      - WEBHOOK_URL=https://${SUBDOMAIN}.${DOMAIN_NAME}/
      - GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
    volumes:
      - n8n_data:/home/node/.n8n
      - ./local-files:/files

volumes:
  n8n_data:
  traefik_data:</code></pre><p>As stated in the docs, this creates two containers:</p><ul><li>n8n (actual n8n app)</li><li>traefik (application proxy to manage TLS/SSL certificates and manage routing)</li></ul><p>It also creates two docker volumes (n8n_data, traefik_data) and mounts the <code>local-files</code> directory we created earlier.</p><p>Finally, we run the docker-compose to bootstrap everything:</p><pre><code class="language-bash">bojana@n8n-automation:~/n8n-compose$ sudo docker compose up -d</code></pre><p>After this, you should see 2 running above mentioned containers:</p><pre><code class="language-bash">bojana@n8n-automation:~/n8n-compose$ docker ps
CONTAINER ID   IMAGE                     COMMAND                  CREATED          STATUS          PORTS                                                                      NAMES
77f28a4f1e8f   docker.n8n.io/n8nio/n8n   &quot;tini -- /docker-ent&#x2026;&quot;   14 seconds ago   Up 13 seconds   127.0.0.1:5678-&gt;5678/tcp                                                   n8n-compose-n8n-1
762beb6046f5   traefik                   &quot;/entrypoint.sh --ap&#x2026;&quot;   14 seconds ago   Up 13 seconds   0.0.0.0:80-&gt;80/tcp, :::80-&gt;80/tcp, 0.0.0.0:443-&gt;443/tcp, :::443-&gt;443/tcp   n8n-compose-traefik-1</code></pre><p>If everything went well and you see running containers check your configured subdomain (<a href="http://n8n.mydomain.com/?ref=bojana.dev">n8n.mydomain.com</a>)</p><p>You should see n8n setup screen like below:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://bojana.dev/content/images/2025/06/n8n_setup.png" class="kg-image" alt="Self-Hosting n8n &#x2013; Your Automation Sidekick (with a Dash of AI)" loading="lazy" width="2000" height="1109" srcset="https://bojana.dev/content/images/size/w600/2025/06/n8n_setup.png 600w, https://bojana.dev/content/images/size/w1000/2025/06/n8n_setup.png 1000w, https://bojana.dev/content/images/size/w1600/2025/06/n8n_setup.png 1600w, https://bojana.dev/content/images/2025/06/n8n_setup.png 2000w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">setup dialog for n8n</span></figcaption></figure><p>Fill up the form and hit Next. And fill out the required n8n&apos;s &quot;customization form&quot;</p><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2025/06/n8n_customization.png" class="kg-image" alt="Self-Hosting n8n &#x2013; Your Automation Sidekick (with a Dash of AI)" loading="lazy" width="2000" height="1109" srcset="https://bojana.dev/content/images/size/w600/2025/06/n8n_customization.png 600w, https://bojana.dev/content/images/size/w1000/2025/06/n8n_customization.png 1000w, https://bojana.dev/content/images/size/w1600/2025/06/n8n_customization.png 1600w, https://bojana.dev/content/images/2025/06/n8n_customization.png 2000w" sizes="(min-width: 720px) 720px"></figure><p>Next you can skip or register with email for some additional features on Community Edition such as folders, debug in editor, custom execution data etc.<br>If you register, you should receive an activation code on your email address:<br></p><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2025/06/image-1.png" class="kg-image" alt="Self-Hosting n8n &#x2013; Your Automation Sidekick (with a Dash of AI)" loading="lazy" width="709" height="325" srcset="https://bojana.dev/content/images/size/w600/2025/06/image-1.png 600w, https://bojana.dev/content/images/2025/06/image-1.png 709w"></figure><p>After this you should receive your license with activation code on registered email, just click Activate License Key in the email and that&apos;s it.</p><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2025/06/image-2.png" class="kg-image" alt="Self-Hosting n8n &#x2013; Your Automation Sidekick (with a Dash of AI)" loading="lazy" width="1144" height="819" srcset="https://bojana.dev/content/images/size/w600/2025/06/image-2.png 600w, https://bojana.dev/content/images/size/w1000/2025/06/image-2.png 1000w, https://bojana.dev/content/images/2025/06/image-2.png 1144w" sizes="(min-width: 720px) 720px"></figure><p>And this is it, you should be able to create and edit workflows, create AI agents etc.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://bojana.dev/content/images/2025/06/image-3.png" class="kg-image" alt="Self-Hosting n8n &#x2013; Your Automation Sidekick (with a Dash of AI)" loading="lazy" width="2000" height="1028" srcset="https://bojana.dev/content/images/size/w600/2025/06/image-3.png 600w, https://bojana.dev/content/images/size/w1000/2025/06/image-3.png 1000w, https://bojana.dev/content/images/size/w1600/2025/06/image-3.png 1600w, https://bojana.dev/content/images/2025/06/image-3.png 2000w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">n8n workflow</span></figcaption></figure><h3 id="%F0%9F%93%8Bwrapping-up">&#x1F4CB;Wrapping Up</h3><p>If you&apos;ve made it this far and everything is up and running &#x2014; <strong>congratulations!</strong> You&apos;ve successfully deployed and secured your own self-hosted <strong>n8n instance</strong>.</p><p>If something didn&#x2019;t work along the way, I encourage you to double-check the steps &#x2014; it&#x2019;s easy to miss a small detail, especially when security configurations are involved. I&#x2019;ve aimed to make this guide thorough, not overwhelming, because skipping over things like proper SSH setup or server hardening can leave you vulnerable, even for a hobby project.</p><h3 id="%E2%98%81%EF%B8%8F%E2%80%9Cbut-i-could-just-use-n8n-cloud%E2%80%9D">&#x2601;&#xFE0F;&#x201C;But I could just use n8n Cloud...&#x201D;</h3><p>Absolutely &#x2014; and that&#x2019;s a valid choice.</p><p>If you&#x2019;re looking for a <strong>plug-and-play experience</strong>, the <a href="https://n8n.io/pricing/?ref=bojana.dev">n8n Cloud Starter plan</a> is a solid option. It saves time and abstracts away the complexity.</p><p>But here&apos;s the trade-off:</p><ul><li>With self-hosting, <strong>you own your data</strong> &#x2014; no third party in the loop</li><li>You can <strong>tinker and extend freely</strong>, without worrying about platform limitations</li><li>You learn valuable skills &#x2014; from infrastructure to automation</li></ul><p>In the long run, a few euros a month for a VPS and domain is a small price to pay for <strong>freedom, control, and learning</strong>.</p><p>Thanks for following along &#x2014; I hope this guide helped you build something real, useful, and yours.</p><p><strong>Cheers, and happy automating!</strong>&#x1F389;</p>]]></content:encoded></item><item><title><![CDATA[Azure Load Balancing Solutions - Lab 1 Azure Load Balancer]]></title><description><![CDATA[Learn about Azure Load Balancer with follow along lab.]]></description><link>https://bojana.dev/azure-load-balancing-solutions-lab-1-azure-load-balancer/</link><guid isPermaLink="false">682b4f6e9751fd03289f0059</guid><category><![CDATA[azure]]></category><category><![CDATA[load balancer]]></category><category><![CDATA[az-104]]></category><category><![CDATA[front door]]></category><category><![CDATA[application gateway]]></category><category><![CDATA[azure lab]]></category><category><![CDATA[network]]></category><dc:creator><![CDATA[Bojana Dejanovic]]></dc:creator><pubDate>Tue, 20 May 2025 12:07:20 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1458501534264-7d326fa0ca04?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDE5fHxzY2FsZXxlbnwwfHx8fDE3NDc3NDI1Njh8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<ul>
<li><a href="#what-is-load-balancing">What is load balancing?</a></li>
<li><a href="#static-vs-dynamic">Static vs dynamic load balancing?</a></li>
<li><a href="#azure-load-balancer">Lab 1: Azure Load Balanxer</a></li>
</ul>

<!--kg-card-begin: html-->
<a id="what-is-load-balancing"><h3>What is load balancing?</h3></a>
<!--kg-card-end: html-->
<img src="https://images.unsplash.com/photo-1458501534264-7d326fa0ca04?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDE5fHxzY2FsZXxlbnwwfHx8fDE3NDc3NDI1Njh8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="Azure Load Balancing Solutions - Lab 1 Azure Load Balancer"><p>When diving into cloud services, I&#x2019;ve found that theory alone can only take you so far. While it&#x2019;s important to build a solid foundation of concepts, things often remain abstract until you apply them to real-world scenarios. Azure offers several load balancing solutions&#x2014;many of which sound similar and serve overlapping purposes. Without seeing them in action or understanding the specific use cases, it&apos;s easy to get stuck in a loop of confusion, unsure of when to choose one over the other.</p><p>Before diving into the various Azure services, it&#x2019;s worth clarifying what <em>l<strong>oad balancing</strong></em> actually means. At its core, load balancing is about distributing incoming network traffic across multiple resources to ensure reliability, performance, and scalability.</p><p>Imagine you&apos;re at a busy coffee shop on a Monday morning. There&apos;s a single barista trying to take orders, make drinks, and handle payments &#x2014; the line moves slowly, and customers start getting frustrated. Now imagine there are four baristas, each handling specific tasks or splitting the workload evenly. Suddenly, everything flows faster, orders are processed smoothly, and customers leave happy.</p><p>That&#x2019;s the essence of <strong><em>load balancing</em></strong>: efficiently distributing work across multiple servers (or people) to improve speed, reliability, and user experience.</p><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2025/05/LoadBalancer.png" class="kg-image" alt="Azure Load Balancing Solutions - Lab 1 Azure Load Balancer" loading="lazy" width="1705" height="1139" srcset="https://bojana.dev/content/images/size/w600/2025/05/LoadBalancer.png 600w, https://bojana.dev/content/images/size/w1000/2025/05/LoadBalancer.png 1000w, https://bojana.dev/content/images/size/w1600/2025/05/LoadBalancer.png 1600w, https://bojana.dev/content/images/2025/05/LoadBalancer.png 1705w" sizes="(min-width: 720px) 720px"></figure><p>It&apos;s important to note that load balancer can be either hardware or software-based. In. context of cloud services it is mostly the software-based one.</p>
<!--kg-card-begin: html-->
<a id="static-vs-dynamic"><h3>Static vs dynamic load balancing?</h3></a>
<!--kg-card-end: html-->
<p>Which server should handle each request load balancers are determining based on number of different algorithms. These fall into two categories:</p><ul><li>static</li><li>dynamic</li></ul><p><strong>Static load balancing</strong> algorithms do not take into account current state of the work when distributing workload. It will not determine which servers are not utilised enough and which are overused. Typical static load balancing algorithms include round robin, least connections, IP hash, fixed partitioning etc. ****</p><p>Static load balancing is used in scenarios where traffic patterns are predictable and backend servers are homogenous and have similar capacity.</p><p><strong>Dynamic load balancing</strong> is also a process of distributing traffic or workload across multiple servers but at <em>runtime,</em> and based on real-time information about server health, resource utilisation or connections count. ****</p><p>When a server becomes overloaded or fails, traffic can be automatically/dynamically rerouted to healthy instances.</p><p>So, unlike static load balancing, dynamic methods react to changing conditions in the system. (CPU usage, memory, network throughput..)</p><p>Dynamic load balancing is typically integrated with <strong>health probes</strong> so that traffic is not sent to degraded or unavailable nodes.</p><p>Due to continuous monitoring it may introduce latency and it&apos;s typically more complex than static load balancing.</p><p>In cloud environments like Azure, load balancing plays a critical role in ensuring that your applications stay responsive and available &#x2014; especially under heavy traffic. But here&#x2019;s where it gets tricky: Azure offers multiple load balancing services that, at first glance, seem to do the same thing. With names like Azure Load Balancer, Application Gateway, Front Door, and Traffic Manager, it&#x2019;s easy to get them mixed up.</p>
<!--kg-card-begin: html-->
<a id="azure-load-balancer"><h3>Lab 1: Azure Load Balancer</h3></a>
<!--kg-card-end: html-->
<p>Azure Load Balancer operates at OSI Layer 4 (TCP/UDP) and distributes inbound traffic across multiple backend instances (like VMs) within a region. It provides high throughput and is ideal for network-level load balancing of VMs or container instances. In this lab, we&#x2019;ll create a <strong>public Load Balancer</strong> to distribute HTTP traffic between two web server VMs.</p><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2025/05/Screenshot-2025-05-18-at-17.00.22.png" class="kg-image" alt="Azure Load Balancing Solutions - Lab 1 Azure Load Balancer" loading="lazy" width="1870" height="806" srcset="https://bojana.dev/content/images/size/w600/2025/05/Screenshot-2025-05-18-at-17.00.22.png 600w, https://bojana.dev/content/images/size/w1000/2025/05/Screenshot-2025-05-18-at-17.00.22.png 1000w, https://bojana.dev/content/images/size/w1600/2025/05/Screenshot-2025-05-18-at-17.00.22.png 1600w, https://bojana.dev/content/images/2025/05/Screenshot-2025-05-18-at-17.00.22.png 1870w" sizes="(min-width: 720px) 720px"></figure><ol><li>First we will create a resource group for our little setup. (I will use &#x2018;eastus&#x2019; for $LOCATION here). You can define following variables before start since we will be referencing these through the setup.</li></ol><pre><code class="language-bash"># Variables (replace values as needed)
RESOURCE_GROUP=&quot;az-load-balancer-lab&quot;
LOCATION=&quot;eastus&quot;
LB_NAME=&quot;MainLoadBalancer&quot;
PUBIP_NAME=&quot;LBPublicIP&quot;
BACKEND_POOL_NAME=&quot;MyBackendPool&quot;</code></pre><pre><code class="language-bash">az group create --location $LOCATION --resource-group $RESOURCE_GROUP</code></pre><ol start="2"><li>Then we create a virtual network and a corresponding subnet.</li></ol><pre><code class="language-bash">az network vnet create -g $RESOURCE_GROUP -n MyVnet --subnet-name MySubnet</code></pre><ol start="3"><li>Now, you can use existing VMs or create a new ones. For the purpose of the exercise we will create 2 new Linux VMs (Ubuntu 24.04), without public IPs (because Load Balancer will provide one). We will generate ssh keys for each and install nginx. Finally we well create simple entry in <strong>/var/www/html/index.htm</strong>l echoing <strong>hostname</strong>, so we can observe which VM is responding to client request once we finish setting up everything.</li></ol><p>So to create VM1 issue command:</p><pre><code class="language-bash">az vm create -g $RESOURCE_GROUP -n VM1 --image Ubuntu2404 --vnet-name MyVNet --subnet MySubnet --public-ip-address &quot;&quot; --generate-ssh-keys --size Standard_B1s</code></pre><p><strong>Note</strong>: size argument is optional, if not supplied it will default to Standard_DS1_v2. I think Standard_B1s will suffice for our exercise. You can get list of all size with following command:</p><pre><code class="language-bash">az vm list-sizes --location eastus --output table</code></pre><p>We can proceed and create VM2 with same configuration:</p><pre><code class="language-bash">az vm create -g $RESOURCE_GROUP -n VM2 --image Ubuntu2404 --vnet-name MyVNet --subnet MySubnet --public-ip-address &quot;&quot; --generate-ssh-keys --size Standard_B1s</code></pre><p>Now we will crate a sample entry files in /var/www/html/index.html on each machine so we can see which machine is responding to the call.<br>But since these VMs do not have a public IPs, we will use command:</p><pre><code class="language-bash">az vm run-command invoke</code></pre><p>to remotely execute a script or command on Azure VM without needing to manually connect to it via SSH or RDP. This uses Azure VM Agent, which is a small service automatically isntalled on most Azure images to run these commands inside VM.</p><p>For VM1:</p><pre><code class="language-bash">az vm run-command invoke \
  --resource-group $RESOURCE_GROUP \
  --name VM1 \
  --command-id RunShellScript \
  --scripts &apos;
sudo apt update &amp;&amp; sudo apt install -y nginx;
echo &quot;Hello from - $(hostname)&quot; | sudo tee /var/www/html/index.html
&apos;
</code></pre><p>The command will install nginx and create the desired index.html with hostname of machine.<br>Do the same for VM2:</p><pre><code class="language-bash">az vm run-command invoke \
--resource-group az-load-balancer-lab \
--name VM2 \
--command-id RunShellScript \
--scripts &apos;
sudo apt update &amp;&amp; sudo apt install -y nginx;
echo &quot;Hello from - $(hostname)&quot; | sudo tee /var/www/html/index.html</code></pre><ol start="4"><li>Now, we want to open port 80 on both VMs Network Security Group so that load balancer can reach the web service.</li></ol><pre><code class="language-bash">az vm open-port -g $RESOURCE_GROUP -n VM1 --port 80 --priority 1001
az vm open-port -g $RESOURCE_GROUP -n VM2 --port 80 --priority 1001</code></pre><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2025/05/Screenshot-2025-05-18-at-17.23.41.png" class="kg-image" alt="Azure Load Balancing Solutions - Lab 1 Azure Load Balancer" loading="lazy" width="2000" height="928" srcset="https://bojana.dev/content/images/size/w600/2025/05/Screenshot-2025-05-18-at-17.23.41.png 600w, https://bojana.dev/content/images/size/w1000/2025/05/Screenshot-2025-05-18-at-17.23.41.png 1000w, https://bojana.dev/content/images/size/w1600/2025/05/Screenshot-2025-05-18-at-17.23.41.png 1600w, https://bojana.dev/content/images/2025/05/Screenshot-2025-05-18-at-17.23.41.png 2000w" sizes="(min-width: 720px) 720px"></figure><ol start="5"><li>And create a public IP for the load balancer</li></ol><pre><code class="language-bash">az network public-ip create -g $RESOURCE_GROUP -n $PUBIP_NAME --sku Standard</code></pre><ol start="6"><li>In this step we will create actual load balancer with one frontend and one backend pool.</li></ol><pre><code class="language-bash">az network lb create -g $RESOURCE_GROUP -n $LB_NAME --sku Standard \
    --frontend-ip-name FrontEndConfig --public-ip-address $PUBIP_NAME \
    --backend-pool-name $BACKEND_POOL_NAME</code></pre><ol start="7"><li>Next, we want to create a health probe on port 80 (TCP).</li></ol><p>A health probe is used to determine the health status of the instances in the back-end pool. This health probe determines if an instance is healthy and can receive traffic.</p><pre><code class="language-bash">az network lb probe create --resource-group  $RESOURCE_GROUP --lb-name $LB_NAME --name MyProbe --protocol http --port 80 --path /</code></pre><ol start="8"><li>Create a load balancing rule for HTTP (port 80)</li></ol><pre><code class="language-bash">az network lb rule create -g $RESOURCE_GROUP --lb-name $LB_NAME -n HTTPRule \
    --protocol tcp --frontend-port 80 --backend-port 80 \
    --frontend-ip-name FrontEndConfig --backend-pool-name $BACKEND_POOL_NAME \
    --probe-name HealthProbe</code></pre><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://bojana.dev/content/images/2025/05/load-balancer-rules.png" class="kg-image" alt="Azure Load Balancing Solutions - Lab 1 Azure Load Balancer" loading="lazy" width="624" height="183" srcset="https://bojana.dev/content/images/size/w600/2025/05/load-balancer-rules.png 600w, https://bojana.dev/content/images/2025/05/load-balancer-rules.png 624w"><figcaption><span style="white-space: pre-wrap;">load balancer rule defines how traffic is distributed to the back-end pool</span></figcaption></figure><p>9. Add VM NICs to the Load Balancer&#x2019;s backend pool</p><pre><code class="language-bash">NIC1=$(az vm show -g $RESOURCE_GROUP -n VM1 --query &quot;networkProfile.networkInterfaces[0].id&quot; -o tsv)
NIC2=$(az vm show -g $RESOURCE_GROUP -n VM2 --query &quot;networkProfile.networkInterfaces[0].id&quot; -o tsv)
az network nic ip-config address-pool add -g $RESOURCE_GROUP --lb-name $LB_NAME \
    --address-pool $BACKEND_POOL_NAME --nic-name $(basename $NIC1) --ip-config-name $(az network nic show -g $RESOURCE_GROUP --name $(basename $NIC1) --query &quot;ipConfigurations[0].name&quot; -o tsv)
az network nic ip-config address-pool add -g $RESOURCE_GROUP --lb-name $LB_NAME \
    --address-pool $BACKEND_POOL_NAME --nic-name $(basename $NIC2) --ip-config-name $(az network nic show -g $RESOURCE_GROUP --name $(basename $NIC2) --query &quot;ipConfigurations[0].name&quot; -o tsv)
</code></pre><ol start="10"><li>And finally get the public IP of the Load Balancer</li></ol><pre><code class="language-bash">az network public-ip show -g $RESOURCE_GROUP -n $PUBIP_NAME --query &quot;ipAddress&quot; -o tsv</code></pre><p>After running the above, navigate to the displayed IP address in a browser. You should hit one of two VMs via the Load Balancer&#x2019;s IP.</p><figure class="kg-card kg-video-card kg-width-regular" data-kg-thumbnail="https://bojana.dev/content/media/2025/05/Screen-Recording-2025-05-20-at-12.57.49_thumb.jpg" data-kg-custom-thumbnail>
            <div class="kg-video-container">
                <video src="https://bojana.dev/content/media/2025/05/Screen-Recording-2025-05-20-at-12.57.49.mp4" poster="https://img.spacergif.org/v1/1604x1250/0a/spacer.png" width="1604" height="1250" playsinline preload="metadata" style="background: transparent url(&apos;https://bojana.dev/content/media/2025/05/Screen-Recording-2025-05-20-at-12.57.49_thumb.jpg&apos;) 50% 50% / cover no-repeat;"></video>
                <div class="kg-video-overlay">
                    <button class="kg-video-large-play-icon" aria-label="Play video">
                        <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                            <path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/>
                        </svg>
                    </button>
                </div>
                <div class="kg-video-player-container">
                    <div class="kg-video-player">
                        <button class="kg-video-play-icon" aria-label="Play video">
                            <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                                <path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/>
                            </svg>
                        </button>
                        <button class="kg-video-pause-icon kg-video-hide" aria-label="Pause video">
                            <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                                <rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"/>
                                <rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"/>
                            </svg>
                        </button>
                        <span class="kg-video-current-time">0:00</span>
                        <div class="kg-video-time">
                            /<span class="kg-video-duration">0:18</span>
                        </div>
                        <input type="range" class="kg-video-seek-slider" max="100" value="0">
                        <button class="kg-video-playback-rate" aria-label="Adjust playback speed">1&#xD7;</button>
                        <button class="kg-video-unmute-icon" aria-label="Unmute">
                            <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                                <path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"/>
                            </svg>
                        </button>
                        <button class="kg-video-mute-icon kg-video-hide" aria-label="Mute">
                            <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                                <path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"/>
                            </svg>
                        </button>
                        <input type="range" class="kg-video-volume-slider" max="100" value="100">
                    </div>
                </div>
            </div>
            
        </figure><p>And that&apos;s it, you&apos;ve successfully configured Level 4 Load Balancer.<br><br>Since it operates and Layer 4 (Transport Layer) Azure Load Balancer is designed to handle high-throughput scenarios efficiently - ultra-low latency and high performance (think gaming servers, financial services, or real-time applications)</p><p>You <strong>should not</strong> use Azure Load Balancer if your web app only receives a small amount of traffic and existing infrastructure already deals with the load just fine.</p><p>In one one of the following labs we will explore <a href="https://learn.microsoft.com/en-us/azure/application-gateway/overview?ref=bojana.dev" rel="noreferrer">Azure Application Gateway</a>, offering various Layer 7 capabilities.</p><p></p>]]></content:encoded></item><item><title><![CDATA[Writing is hard, but you should try it anyway.]]></title><description><![CDATA[<p>It is! Allow me a second to lament that my last entry on this blog was written on January 26th last year. Well, what can you do? </p><p>Why is writing generally a good idea, even if it&apos;s just a blog post with a limited audience? I wondered that</p>]]></description><link>https://bojana.dev/writing-is-hard/</link><guid isPermaLink="false">646219b058e60a0589967790</guid><dc:creator><![CDATA[Bojana Dejanovic]]></dc:creator><pubDate>Mon, 15 Jan 2024 07:54:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1583558954748-e8666eb10c0c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDJ8fGJyb2tlbiUyMHBlbmNpbHxlbnwwfHx8fDE2ODQxNTA3MTF8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1583558954748-e8666eb10c0c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDJ8fGJyb2tlbiUyMHBlbmNpbHxlbnwwfHx8fDE2ODQxNTA3MTF8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Writing is hard, but you should try it anyway."><p>It is! Allow me a second to lament that my last entry on this blog was written on January 26th last year. Well, what can you do? </p><p>Why is writing generally a good idea, even if it&apos;s just a blog post with a limited audience? I wondered that myself.</p><figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">Observation suggests that people are switching to using ChatGPT to write things for them with almost indecent haste. Most people hate to write as much as they hate math. Way more than admit it. Within a year the median piece of writing could be by AI.</p>&#x2014; Paul Graham (@paulg) <a href="https://twitter.com/paulg/status/1655925905527537666?ref_src=twsrc%5Etfw&amp;ref=bojana.dev">May 9, 2023</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></figure><p>Ah, yes, the &quot;AI takeover&quot; everyone is buzzing about. Why bother writing when you can ask ChatGPT to do it for you? In the tweet above, Paul Graham writes how people are using AI (or ChatGPT, to be precise) more and more to write all sorts of stuff for them because writing is hard. He then continues : </p><blockquote>I warn you now, this is going to have unfortunate consequences, just as switching to living in suburbia and driving everywhere did. When you lose the ability to write, you also lose some of your ability to think.  </blockquote><blockquote>.... I&apos;m not warning about the switch to AI in the hope of averting it, but to warn the few people who care enough to save themselves, or their kids. Learn to use AI. It&apos;s a powerful technology, and you should know how to use it. But also learn how to write.</blockquote><p>These few lines got me thinking about writing and what and how I can do to write more and write better. </p><p>To be clear, I am not talking about writing in general &quot;literature form.&quot; I am talking about writing as a technical person, software engineer, engineer, or architect. After all, the goal of these writings is not to entertain or make you wonder (although it can sometimes?) but more to be precise and unambiguous, to <em>communicate </em>things well. </p><p>I have never considered myself good at writing (present-day included). <br>When I think about it, through most of high school and college, where these kinds of skills could be exercised in the form of essays, and so on, I was never good at it. At least the ones for <em>Literature </em>classes. You might guess why I&apos;ve chosen the STEM path.</p><p>I think it&apos;s even harder to write in your non-native language. English is <em>the lingua franca</em> of today&apos;s day and age, especially in tech. So, being a non-native English speaker is one more barrier you must overcome.</p><h2 id="why-writing-is-essential-for-software-engineers">Why writing is essential for software engineers?</h2><p>Ok, so writing is not easy. But why should you still try to be better at it?</p><blockquote>&quot;Writing is thinking&quot;</blockquote><p>The quote above, which I searched for and found out was uttered by the <a href="https://medium.learningbyshipping.com/writing-is-thinking-an-annotated-twitter-thread-2a75fe07fade?ref=bojana.dev">VP of the Word team</a>, holds the same theme as Paul Graham&apos;s tweet above - writing enables you to think, to carve out thoughts from your brain, for them to take form so that others can see them and understand them. They say that if you can think clearly, you can write clearly. It&apos;s just a matter of willingness to try and learn the skill.</p><p>As a software engineer, yes, you write code, but maybe more importantly - you communicate with others on your team and organization. While face-to-face communication is crucial, sometimes it&apos;s impossible to do it (especially with today&apos;s prevailing async communication and remote teams), and you need something more durable and with higher reach. Composing a feature/system documentation, setup guide, requirements review, or just a simple code review, the ability to write well is essential. If you can compose precise and concise writing, you can communicate well. It boils down to that - communication is a much-needed skill in the tool-belt of every engineer.</p><h2 id="how-do-i-write-better">How do I write better?</h2><p>That&apos;s a good question. As with any skill, you need to practice. A lot.</p><p>Besides that obvious advice, there are some universal rules for better writing, regardless of purpose and form. An excellent book called <a href="https://www.amazon.com/Writing-Well-Classic-Guide-Nonfiction/dp/0060891548?ref=bojana.dev">On Writing Well: The Classic Guide to Writing Nonfiction</a> details basic rules related to writing and a bunch of examples that the author collected. The guide is nicely written, and although the main subject is a &quot;meta&quot; subject, it isn&apos;t dull or dry.</p><p>While the books cover a lot of principles, methods, and forms of writing, I will highlight here a few that I found interesting because I think they are easily applicable to the &quot;software engineer&quot; or writing a blog like this one.</p><h3 id="reduce-clutter">Reduce clutter</h3><p>When I think about clutter, the first thing that comes to my mind is local politicians. They often overuse certain words and phrases and talk too much without saying anything. This, of course, should be avoided. The book&apos;s author mentioned above compares clutter to weeds, constantly integrating into speech and writing. Go over your written sentences. Is anything creating clutter? Could you remove it? <br>This is also important when writing documentation for a product or a feature. If it&apos;s concise and easily understandable, it&apos;s considered good if you can quickly find the answer to your question.</p><h3 id="rewriting">Rewriting</h3><p>This is the essence of writing well; it&apos;s intuitive and natural. It&apos;s a process. Not even professional writers get the sentence the &quot;right way&quot; for the first time. They rewrite, fiddle with it, and sentences evolve with every rewrite.</p><blockquote>Writing is like a good watch&#x2014;it should run smoothly and have no extra parts</blockquote><h3 id="sequential-storytelling">Sequential storytelling</h3><p>This is specifically important for &quot;technical writing.&quot; Why? Because often you are explaining some concept or process to the audience. It forced you to make sure how it works so you can guide your audience to the same sequence of ideas and deductions that helped you to make the process clear.</p><blockquote>It&apos;s the principle of leading readers who know nothing, step by step, to a grasp of subjects they didn&apos;t think they had an aptitude for or were afraid they were too dumb to understand.</blockquote><h3 id="the-audience">The audience</h3><p>In short, you are writing for yourself. Don&apos;t get intimidated by some large audience that will read your piece and criticize you. <br>That is not to say your writing shouldn&apos;t have structure and you shouldn&apos;t care, <br>on the contrary. There is no excuse if your reader is lost in the middle of the writing. <br>The first is a matter of style, and expressing who you are, the second is a matter of craft.</p><blockquote>Never say anything in writing that you wouldn&apos;t comfortably say in conversation.</blockquote><p>Another thing I often hear from others, or even myself, &quot;No one will be reading this; no one cares about this.&quot; But that doesn&apos;t matter. Even if no one will, you will. For example, I often go back to some older blog posts because I&apos;ve written some details that come in handy. Or they are simply a reminder about the interests I had and the learning I did.</p><h3 id="writing-is-not-a-contest">Writing is not a contest.</h3><p>In high school and college, I always feared that someone else would write better than I would if I wrote about something. This is nonsense. Writing is not a contest, and everyone should go at their own pace; the only one you are competing with is yourself.</p><p>This book has many other useful pieces of advice, and I recommend you take a look.<br>After all, one must read a lot to be better at writing. </p><blockquote>Make a habit of reading what is being written today and what has been written by earlier masters. Writing is learned by imitation. If anyone asked me how I learned to write, I&apos;d say I learned by reading the men and women who were doing the kind of writing <strong>I</strong> wanted to do and trying to figure out how they did it.</blockquote>]]></content:encoded></item><item><title><![CDATA[DNS - Resource records, DNS tools, DNSSec (Part 2)]]></title><description><![CDATA[<p></p><p><a href="https://bojana.dev/dns-structure-organization-and-how-it-works-part-1/">In the last article</a>, I said that the DNS zone is defined for each domain and that it&#x2019;s a file that consists of <strong>Resource Records</strong>. When the DNS resolver forwards the domain name to the DNS server, it gets the resource records that match that domain from it.</p>]]></description><link>https://bojana.dev/dns-resource-records-types-and-explanation-dns-tools-dnssec-part-2/</link><guid isPermaLink="false">63cea14a2a85ee53ab44c740</guid><category><![CDATA[DNS]]></category><category><![CDATA[Resource Records]]></category><category><![CDATA[DNSSEC]]></category><category><![CDATA[DNS querying]]></category><dc:creator><![CDATA[Bojana Dejanovic]]></dc:creator><pubDate>Thu, 26 Jan 2023 09:19:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1596385447162-ac514c8db85c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDI3fHxyZWNvcmRzfGVufDB8fHx8MTY3NDcyNzI4MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1596385447162-ac514c8db85c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDI3fHxyZWNvcmRzfGVufDB8fHx8MTY3NDcyNzI4MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="DNS - Resource records, DNS tools, DNSSec (Part 2)"><p></p><p><a href="https://bojana.dev/dns-structure-organization-and-how-it-works-part-1/">In the last article</a>, I said that the DNS zone is defined for each domain and that it&#x2019;s a file that consists of <strong>Resource Records</strong>. When the DNS resolver forwards the domain name to the DNS server, it gets the resource records that match that domain from it.</p><p>DNS  zone can include a single domain name, one domain, many subdomains, or many domain names. Usually, the DNS zone is equivalent to &apos;domain&#x2019;, but it doesn&#x2019;t have to be.</p><ul>
<li><a href="https://bojana.dev/dns-resource-records-types-and-explanation-dns-tools-dnssec-part-2#resource-records"> Resource records</a>
<ul>
<li>SOA</li>
<li>A record</li>
<li>AAAA record</li>
<li>MX record</li>
<li>CNAME record</li>
<li>NS record</li>
<li>SRV record</li>
<li>PTR record</li>
<li>TXT record</li>
</ul>
</li>
<li><a href="https://bojana.dev/dns-resource-records-types-and-explanation-dns-tools-dnssec-part-2/#DNSSEC">DNS security issues (DNSSEC)</a></li>
<li><a href="https://bojana.dev/dns-resource-records-types-and-explanation-dns-tools-dnssec-part-2/#dns-tools">DNS tools</a></li>
</ul>
<h3 id="resource-records">Resource Records</h3><p>Resource Record is comprised of 5 fields:</p><ul><li><strong>Name</strong> - it can be a domain name or host address</li><li><strong>Time to live (TTL)</strong> - how long the record will &#x201C;live&#x201D; in those caches I mentioned in the last article. It shows how stable the record is. The bigger this number is, the longer the record will be kept in the cache. For ex. SOA record which we will examine shortly this value is 86400 which is 24h (TTL values are in seconds).</li><li><strong>Class</strong> - for data that is important for the Internet, this is always IN, for other, non-Internet data other codes can be used, but very rarely.</li><li><strong>Type  -</strong> type of record. You can see the most important types in the table below.</li><li><strong>Value -</strong> the value that is assigned to the RR (ex. address, name, etc.)</li></ul><h3 id="soa-start-of-authority">SOA (Start of Authority)</h3><p>This is the first record in each DNS zone.<br>SOA record contains the name of the primary DNS server, email of the DNS administrator (optional, and nowadays mostly skipped due to spam issues), unique serial number, and various other indicators and timers.</p><pre><code class="language-bash">$ORIGIN vps-playground.cloud.
$TTL 86400
; SOA Records
@		IN	SOA	hydrogen.ns.hetzner.com. dns.hetzner.com. 2023011205 86400 10800 3600000 3600
</code></pre><p><strong>Serial number</strong> (version, in the example above <em>2023011205</em>) for DNS zone file - this &#x201C;number&#x201D; is in format &#x201C;yyyymmddnn&#x201D; and it has to be incremented during every change so that secondary DNS knows and it can retrieve it. This was a common mistake when handling DNS zone files, not having the serial number incremented, and thus not propagating changes to secondary DNS servers. However, by using any of the VPS provider&#x2019;s dashboards for updating DNS zone files, this value is incremented automatically, after you make any changes to the DNS zone file.</p><p>This process of sending a DNS record from a primary DNS server to a secondary is called a <strong>zone transfer</strong>. <a href="https://bojana.dev/dns-structure-organization-and-how-it-works-part-1/">In the Part 1</a> I said that DNS data is sent over port UDP port 53, this zone transfer is an exception, and it&#x2019;s sent over TCP (because we need to ensure the transfer was completed correctly, hence the more reliable protocol).</p><p><strong>Refresh</strong> timer - after how many seconds secondary DNS will check for changes on the primary<br><strong>Retry -</strong> if the check didn&#x2019;t succeed, after how many seconds the retry will happen<br><strong>Expire -</strong> How long DNS zone(s) from primary will be kept<br><strong>Minimum</strong>  - sets the time to live for negative answers that are cached*</p><p>*From the example above you can see $TTL directive defines times for all records in the zone file. This is the default value for <em>positive answers</em> (i.e., actual records). The <em>minimum</em> parameter in the SOA record sets the time to live for <em>negative answers</em> that are cached (cache misses if you will).</p><h3 id="ns-records">NS records</h3><p>NS (name server) records identify the authoritative servers for a zone.<br>These records are usually defined, just after the SOA record. The format is:<br><br>zone [ttl] [IN] NS hostname</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">; NS Records, &quot;@&quot; - represents root
@	600	IN	NS	helium.ns.hetzner.de.
@	600	IN	NS	hydrogen.ns.hetzner.com.
@	600	IN	NS	oxygen.ns.hetzner.com.
</code></pre><figcaption><p><span style="white-space: pre-wrap;">NS records</span></p></figcaption></figure><h3 id="a-record-ipv4-address">A record (IPv4 address)</h3><p>This is the key record of the DNS database, because they provide ma mapping from hostname to IP address. The format is:</p><p>hostname [ttl] [IN] A ipaddr</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">www	300	IN	A	142.132.165.72
</code></pre><figcaption><p><span style="white-space: pre-wrap;">example of A record</span></p></figcaption></figure><h3 id="aaaa-record-ipv6-address">AAAA record (IPv6 address)</h3><p>IPv6 equivalent of A records. IPv6 records in your DNS zone don&#x2019;t mean you have to answer DNS queries over IPv6. The format is:</p><p>hostname [ttl] [IN] AAAA ipaddr</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">f.root-servers.net. IN AAAA 2001:500:2f::f</code></pre><figcaption><p><span style="white-space: pre-wrap;">example of AAAA record</span></p></figcaption></figure><h3 id="mx">MX</h3><p>MX records are used by mail systems to route emails more efficiently. MX records specify the host&apos;s name prepared to accept the email for the specified domain.</p><p>The format of an MX record is: name [ttl] [IN] MX preference host ...</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">mail.vps-playground.cloud  86400 IN MX 10 mail
</code></pre><figcaption><p><span style="white-space: pre-wrap;">example of MX record</span></p></figcaption></figure><p>The <em>preference</em> or <em>priority</em> parameter is indicating which host has a priority for receiving/handling emails. In case of message send failure, the server will default to host with next priority preference (lower number means bigger priority).</p>
<!--kg-card-begin: html-->
<table>
<thead>
<tr>
<th><a href="http://example.com/?ref=bojana.dev">example.com</a></th>
<th>record type</th>
<th>priority</th>
<th>value</th>
<th>TTL</th>
</tr>
</thead>
<tbody>
<tr>
<td>@</td>
<td>MX</td>
<td>10</td>
<td><a href="http://mailserver1.exmaple.com/?ref=bojana.dev">mailserver1.exmaple.com</a></td>
<td>45000</td>
</tr>
<tr>
<td>@</td>
<td>MX</td>
<td>20</td>
<td><a href="http://mailserver2.example.com/?ref=bojana.dev">mailserver2.example.com</a></td>
<td>45000</td>
</tr>
</tbody>
</table>
<!--kg-card-end: html-->
<p>When a user sends an email, the MTA (Mail Transfer Agent), sends a DNS query to identify the mail servers for the email recipients. SMTP connection will be established with the domain with the highest priority (mailserver1 in the example above).</p><h3 id="cname-record">CNAME record</h3><p>This record allows aliases to be created. These &#x201C;nicknames&#x201D; are used to either shorten a long hostname or associate a function with a host. The real name is often called &#x201C;canonical name&#x201D; (hence &#x201C;CNAME&#x201D;). CNAME must always point to a domain, never to an IP address. Imagine I have this record in my DNS zone:</p><pre><code class="language-bash">bojana.dev 120 IN CNAME blog.bojana.dev </code></pre><p>When a DNS software encounters CNAME record, it stops the query for this &#x201C;nickname&#x201D; and re-quires for the real name. For the example above, it means the querying will finish once A record for <a href="https://bojana.dev/">bojana.dev</a> is found.</p><p>Pointing a CNAME to another CNAME, is possible (up to seven times, the  eighth target must be a real hostname), but it&#x2019;s considered inefficient because it requires multiple DNS lookups before the domain can be loaded which ultimately slower experience for the user. MX and NS records <strong>cannot</strong> point to a CNAME record (they have to point to A or AAAA record).</p><h3 id="srv-records">SRV records</h3><p>This record specifies the location of the service (a host and a port) within a domain. Services such as VoIP, instant messaging, etc. This DNS record is unique because most other DNS records specify a server or an IP address, whereas the SRV record includes <strong>port</strong> at that IP address as well.</p><p>The format is: <a href="http://service.proto.name/?ref=bojana.dev">service.proto.name</a> [ttl] [IN] SRV pri weight port target</p><p>The <em>service</em> is a service defined in the <a href="https://www.iana.org/protocols?ref=bojana.dev">IANA assigned numbers database</a>, <em>proto</em> is either TCP or UDP, <em>name</em> is a domain name to which the SRV record refers, <em>pri</em> is priority, weight is used for load balancing among several servers, <em>port</em> is a port on which service runs, and the target is the hostname of the server that provides the service.</p><p>Example :</p><pre><code class="language-bash">; main server on port 80, backup on new box, port 8000
_http._tcp SRV 0 0 80 www-server.example.com.
	   SRV 10 0 8000 new-fast-box.example.com.
</code></pre><h3 id="txt-records">TXT records</h3><p>This record adds arbitrary text to the host&#x2019;s DNS records. Originally, it was intended as a place for human-readable notes. Now, you can also put some machine-readable data into TXT records.</p><p>You can create many TXT records for one domain.</p><p>Example:</p><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2023/01/firefox_TZGS5IqSC8.png" class="kg-image" alt="DNS - Resource records, DNS tools, DNSSec (Part 2)" loading="lazy" width="1127" height="123" srcset="https://bojana.dev/content/images/size/w600/2023/01/firefox_TZGS5IqSC8.png 600w, https://bojana.dev/content/images/size/w1000/2023/01/firefox_TZGS5IqSC8.png 1000w, https://bojana.dev/content/images/2023/01/firefox_TZGS5IqSC8.png 1127w" sizes="(min-width: 720px) 720px"></figure><p>TXT records don&#x2019;t have any particular format, so they are sometimes used to add information for other purposes without requiring changes to the DNS system itself.</p><p>Most DNS records will put a limit on number of TXT records which can be created and how big TXT records can be.</p><h3 id="spf-dkim-and-dmarc-records">SPF, DKIM, and DMARC records</h3><p>SPF (Sender Policy Framework), DKIM (DomainKeys Identified Mail), and DMARC (Domain-based Message Authentication, Reporting, and Conformance) are all actually TXT records, and are all standards in an attempt to fight &#x201C;unsolicited commercial email&#x201D; aka <strong>spam.</strong> Going into details about these would require new blogpost.</p><h2 id="dnssec">DNSSEC</h2><p>Initially, DNS protocol did not considered security. DNS name servers or resolvers could manipulate the content of any DNS record, thus causing the client to receive incorrect information.</p><p><a href="https://www.rfc-editor.org/rfc/rfc3833?ref=bojana.dev">RFC 3833</a> highlight some security threats to DNS, and how DNSSEC addresses these threats.</p><p>DNS Security Extensions (DNSSEC) is a security protocol created to mitigate this problem. The protection against attacks is done by digitally signing the data to help ensure its validity. This signing must happen at every level in the DNS lookup process.</p><p>DNSSEC implements a hierarchical digital signing policy across all layers of DNS. For example, in the case of a &#x2018;bojana.dev&#x2019; lookup, a root DNS Server would sign a key for the .DEV nameserver, and the .DEV nameserver would then sign a key for bojana.dev&#x2019;s authoritative nameserver.</p><p>DNSSEC is also designed to be backward compatible. What that means is, while improved security is of course important and preferred, it has to ensure that traditional DNS lookups still resolve correctly (without added security).</p><p><a href="https://bojana.dev/dns-structure-organization-and-how-it-works-part-1/">In the part 1</a> I mentioned the tree structure behind DNS, and root DNS servers at the beginning of this structure. DNSSEC creates a parent-child chain of trust that travels all the way up to the root zone. This chain of trust cannot be compromised at any layer of DNS.</p><p>The root zone itself needs to be validated (proven to be free of tampering or fraud). An interesting detail is that this validation of the root zone is actually done by human intervention. In something that&#x2019;s called <a href="https://www.cloudflare.com/dns/dnssec/root-signing-ceremony/?ref=bojana.dev">Root Zone Signing Ceremony</a>. Selected individuals from around the world are gathered to sign the root DNSKEY in a public and audited way. This process is done <a href="https://www.iana.org/dnssec/ceremonies?ref=bojana.dev">every few months</a>.</p><p>As mentioned above, DNS servers are vulnerable to broad spectrum of attacks, such as spoofing, DoS (Denial of Service), or interception of private <a href="https://en.wikipedia.org/wiki/Personal_data?ref=bojana.dev">personal information</a>.</p><p>Besides DNSSEC, there are additional measures that operator of DNS zone can perform in order to secure their servers. Over-provisioning infrastructure - in simple terms allowing your nameservers to handle multiplies more traffic than expected, in order to overcome DDOS attacks.</p><p>Other strategies include using <a href="https://www.cloudflare.com/dns/dns-firewall/?ref=bojana.dev">DNS firewall</a>, <a href="https://en.wikipedia.org/wiki/DNS_over_TLS?ref=bojana.dev">DNS over TLS</a> and <a href="https://en.wikipedia.org/wiki/DNS_over_HTTPS?ref=bojana.dev">DNS over HTTPS</a>.<br>Again, DNS security is a broad topic, and would require at least a blogpost of it&apos;s own, if not many blogposts ;)</p><h2 id="dns-tools">DNS tools</h2><p>Of course, whether you are administering DNS zones or not, there will come time where you&#x2019;ll need some kind of way to verify your DNS records are correct and up-to-date. There are numerous tools for analysis, detection etc. of DNS queries. One of them is nslookup. This is, of course, a command line tool. It comes preinstalled on both Windows and Unix systems.</p><pre><code class="language-bash">nslookup bojana.dev
Server:  UnKnown
Address:  192.168.100.1

Non-authoritative answer:
Name:    bojana.dev
Address:  157.90.124.251
</code></pre><p>However, when you want to know some IP behind the domain name, it&#x2019;s often easier to issue ping command, because it will perform nslookup automatically.</p><pre><code class="language-bash">ping bojana.dev

Pinging bojana.dev [157.90.124.251] with 32 bytes of data:
Reply from 157.90.124.251: bytes=32 time=41ms TTL=49
Reply from 157.90.124.251: bytes=32 time=39ms TTL=49

Ping statistics for 157.90.124.251:
    Packets: Sent = 2, Received = 2, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
    Minimum = 39ms, Maximum = 41ms, Average = 40ms
</code></pre><h3 id="dig">dig</h3><p>(domain information groper) is also a tool for querying DNS servers. <br>A typical invocation of <strong>dig</strong> looks like this:<br>dig @server name type<br>Usage is as follows:</p><pre><code class="language-bash">dig [server] [name] [type]

</code></pre><p><code>[server]</code>  The hostname or IP address the query is directed to</p><p><code>[name]</code> DNS (Domain Name Server) of the server to query</p><p><code>[type]</code> The type of DNS record to retrieve. By default (or if left blank), dig uses the A record type<br>In part 1, I also mentioned that there are <a href="https://www.iana.org/domains/root/servers?ref=bojana.dev">13 root DNS servers</a>, and typically web browsers and operating systems already know about those addresses. If you just enter dig command without parameters, it will return you exactly that list of 13 root DNS servers:</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">$ dig
; &lt;&lt;&gt;&gt; DiG 9.18.8 &lt;&lt;&gt;&gt;
;; global options: +cmd
;; Got answer:
;; -&gt;&gt;HEADER&lt;&lt;- opcode: QUERY, status: NOERROR, id: 58489
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 13, AUTHORITY: 0, ADDITIONAL: 27

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;.                              IN      NS

;; ANSWER SECTION:
.                       516137  IN      NS      a.root-servers.net.
.                       516137  IN      NS      b.root-servers.net.
.                       516137  IN      NS      c.root-servers.net.
.                       516137  IN      NS      d.root-servers.net.
.                       516137  IN      NS      e.root-servers.net.
.                       516137  IN      NS      f.root-servers.net.
.                       516137  IN      NS      g.root-servers.net.
.                       516137  IN      NS      h.root-servers.net.
.                       516137  IN      NS      i.root-servers.net.
.                       516137  IN      NS      j.root-servers.net.
.                       516137  IN      NS      k.root-servers.net.
.                       516137  IN      NS      l.root-servers.net.
.                       516137  IN      NS      m.root-servers.net.

;; ADDITIONAL SECTION:
a.root-servers.net.     516137  IN      A       198.41.0.4
a.root-servers.net.     516137  IN      AAAA    2001:503:ba3e::2:30
b.root-servers.net.     516137  IN      A       199.9.14.201
b.root-servers.net.     516137  IN      AAAA    2001:500:200::b
c.root-servers.net.     516137  IN      A       192.33.4.12
c.root-servers.net.     516137  IN      AAAA    2001:500:2::c
d.root-servers.net.     516137  IN      A       199.7.91.13
d.root-servers.net.     516137  IN      AAAA    2001:500:2d::d
e.root-servers.net.     516137  IN      A       192.203.230.10
e.root-servers.net.     516137  IN      AAAA    2001:500:a8::e
f.root-servers.net.     516137  IN      A       192.5.5.241
f.root-servers.net.     516137  IN      AAAA    2001:500:2f::f
g.root-servers.net.     516137  IN      A       192.112.36.4
g.root-servers.net.     516137  IN      AAAA    2001:500:12::d0d
h.root-servers.net.     516137  IN      A       198.97.190.53
h.root-servers.net.     516137  IN      AAAA    2001:500:1::53
i.root-servers.net.     516137  IN      A       192.36.148.17
i.root-servers.net.     516137  IN      AAAA    2001:7fe::53
j.root-servers.net.     516137  IN      A       192.58.128.30
j.root-servers.net.     516137  IN      AAAA    2001:503:c27::2:30
k.root-servers.net.     516137  IN      A       193.0.14.129
k.root-servers.net.     516137  IN      AAAA    2001:7fd::1
l.root-servers.net.     516137  IN      A       199.7.83.42
l.root-servers.net.     516137  IN      AAAA    2001:500:9f::42
m.root-servers.net.     516137  IN      A       202.12.27.33
m.root-servers.net.     516137  IN      AAAA    2001:dc3::35

;; Query time: 850 msec
;; SERVER: 172.18.176.1#53(172.18.176.1) (UDP)
;; WHEN: Thu Jan 26 09:59:09 CET 2023
;; MSG SIZE  rcvd: 811
</code></pre><figcaption><p><span style="white-space: pre-wrap;">dig output of 13 root DNS servers and their IPs</span></p></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-bash">dig bojana.dev SOA

; &lt;&lt;&gt;&gt; DiG 9.18.8 &lt;&lt;&gt;&gt; bojana.dev SOA
;; global options: +cmd
;; Got answer:
;; -&gt;&gt;HEADER&lt;&lt;- opcode: QUERY, status: NOERROR, id: 47443
;; flags: qr rd ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;bojana.dev.                    IN      SOA

;; ANSWER SECTION:
bojana.dev.             0       IN      SOA     curitiba.ns.porkbun.com. dns.cloudflare.com. 2297766056 10000 2400 604800 3600

;; Query time: 0 msec
;; SERVER: 172.18.176.1#53(172.18.176.1) (UDP)
;; WHEN: Thu Jan 26 09:52:05 CET 2023
;; MSG SIZE  rcvd: 115
</code></pre><figcaption><p><span style="white-space: pre-wrap;">example of quering for SOA record for bojana.dev domain</span></p></figcaption></figure><h3 id="drill">drill</h3><p>Also a tool to get numerous information out of DNS. It is specifically designed to be used with DNSSEC. The name is sort of a pun on dig. If no arguments are given class defaults to &apos;IN&apos; and type to &apos;A&apos;.</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">bojana@nextcloud:~$ drill -T bojana.dev
.       518400  IN      NS      i.root-servers.net.
.       518400  IN      NS      b.root-servers.net.
.       518400  IN      NS      c.root-servers.net.
.       518400  IN      NS      f.root-servers.net.
.       518400  IN      NS      h.root-servers.net.
.       518400  IN      NS      d.root-servers.net.
.       518400  IN      NS      g.root-servers.net.
.       518400  IN      NS      e.root-servers.net.
.       518400  IN      NS      k.root-servers.net.
.       518400  IN      NS      j.root-servers.net.
.       518400  IN      NS      a.root-servers.net.
.       518400  IN      NS      l.root-servers.net.
.       518400  IN      NS      m.root-servers.net.
dev.    172800  IN      NS      ns-tld5.charlestonroadregistry.com.
dev.    172800  IN      NS      ns-tld3.charlestonroadregistry.com.
dev.    172800  IN      NS      ns-tld2.charlestonroadregistry.com.
dev.    172800  IN      NS      ns-tld1.charlestonroadregistry.com.
dev.    172800  IN      NS      ns-tld4.charlestonroadregistry.com.
bojana.dev.     10800   IN      NS      fortaleza.ns.porkbun.com.
bojana.dev.     10800   IN      NS      salvador.ns.porkbun.com.
bojana.dev.     10800   IN      NS      curitiba.ns.porkbun.com.
bojana.dev.     10800   IN      NS      maceio.ns.porkbun.com.
bojana.dev.     600     IN      A       157.90.124.251
</code></pre><figcaption><p><span style="white-space: pre-wrap;">output of drill with argument -T (Trace </span><i><em class="italic" style="white-space: pre-wrap;">name</em></i><span style="white-space: pre-wrap;"> from the root down)</span></p></figcaption></figure><p>This is also a verification of the story about how DNS names are resolved.</p><p>DNS might seem as a fairly simple system, however, there are many details, &quot;nuts and bolts&quot; included in order for it to work properly. I mentioned a few pitfalls of DNS and how new updates to the protocol are trying to address them.</p><p>Hope you enjoyed the reading, and are know a bit more knowledgable and appreciative of DNS.  ;)<br>For the end, here&apos;s one haiku about DNS. :)</p><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2023/01/dns.webp" class="kg-image" alt="DNS - Resource records, DNS tools, DNSSec (Part 2)" loading="lazy" width="550" height="707"></figure>]]></content:encoded></item><item><title><![CDATA[How to self-host Bitwarden password manager]]></title><description><![CDATA[<p>Using a password manager is a good security practice, of which I will not be talking much nor convince you to use one. Since you are reading this article, chances are you are already using one or want to try it out.</p><p>However, with the recent <a href="https://techcrunch.com/2022/12/22/lastpass-customer-password-vaults-stolen/?ref=bojana.dev">LastPass hack</a> question arises</p>]]></description><link>https://bojana.dev/how-to-selfhost-bitwarden-password-manager/</link><guid isPermaLink="false">63bff7b42a85ee53ab44c6c4</guid><category><![CDATA[password manager]]></category><category><![CDATA[Bitwarden]]></category><category><![CDATA[security]]></category><category><![CDATA[self-host]]></category><dc:creator><![CDATA[Bojana Dejanovic]]></dc:creator><pubDate>Tue, 17 Jan 2023 14:05:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1634804306598-f2efe3ead034?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDEzfHxwYXNzd29yZHxlbnwwfHx8fDE2NzM4ODM3NzE&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1634804306598-f2efe3ead034?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDEzfHxwYXNzd29yZHxlbnwwfHx8fDE2NzM4ODM3NzE&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="How to self-host Bitwarden password manager"><p>Using a password manager is a good security practice, of which I will not be talking much nor convince you to use one. Since you are reading this article, chances are you are already using one or want to try it out.</p><p>However, with the recent <a href="https://techcrunch.com/2022/12/22/lastpass-customer-password-vaults-stolen/?ref=bojana.dev">LastPass hack</a> question arises whether it is safe to trust a 3rd party cloud provider to safely store all your valuable passwords. Using a password manager is a &#x201C;no-brainer&#x201D; for most of <s>you</s> us security-aware folks, but if you are also concerned about where and how they are stored as well, then this &#x201C;self-hosted&#x201D; option might be for you.</p><p>A &quot;Silver bullet&quot; solution doesn&apos;t exist. Nothing will make you 100% secure, however this way at least you will have &#xA0;complete control. </p><p><a href="https://bitwarden.com/?ref=bojana.dev">Bitwarden</a> is, as they claim on their website, &#xA0;<em>an integrated open-source password management solution for individuals, teams, and business organizations</em>.</p><p>While they have plans for both personal use and business, they also have the option to <a href="https://bitwarden.com/blog/new-deployment-option-for-self-hosting-bitwarden/?ref=bojana.dev">self-host Bitwarden</a>.</p><p>Self-host option of Bitwarden can be hosted on Linux, MacOS and Windows. We will be focusing on Linux hosting.</p><p>They do offer something they call &#x201C;Unified Self-Host deployment&#x201D; which is essentially all of the services needed for Bitwarden to run in one unified Docker container, whereas the &#x201C;Standard Deployment Offer&#x201D; uses 12 individual Docker containers working together. Furthermore, in the unified deployment you can choose between database providers such as Microsoft SQL Server, MySQL, and PostgreSQL, and &#xA0;Standard Deployment uses Microsoft SQL Server. Or if you like to look at fancy diagrams, something like the below:</p><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2023/01/Standard_self-host_deployment_diagram.svg" class="kg-image" alt="How to self-host Bitwarden password manager" loading="lazy"></figure><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2023/01/Unified_self-host_deployment_diagram.svg" class="kg-image" alt="How to self-host Bitwarden password manager" loading="lazy"></figure><p>The &#x201C;Unified self-host&#x201D; option is still in beta, so we&#x2019;re gonna go with &#x201C;Standard Deployment Offer&#x201D;.</p><ul><li><a href="https://bojana.dev/how-to-selfhost-bitwarden-password-manager#prerequisites">Prerequisites</a></li><li><a href="https://bojana.dev/how-to-selfhost-bitwarden-password-manager#domain-setup">Domain setup</a></li><li><a href="https://bojana.dev/how-to-selfhost-bitwarden-password-manager#docker-and-docker-compose">Docker and Docker compose</a></li><li><a href="https://bojana.dev/how-to-selfhost-bitwarden-password-manager#bitwarden-user-and-directory">Bitwarden user and directory</a></li><li><a href="https://bojana.dev/how-to-selfhost-bitwarden-password-manager#install-bitwarden">Install Bitwarden</a></li><li><a href="https://bojana.dev/how-to-selfhost-bitwarden-password-manager#set-up-2fa">Set up 2FA</a></li></ul><h3 id="prerequisites">Prerequisites</h3><p>Before continuing with the setup, there are some prerequisites that we need to fulfill.</p><ul><li>Domain - we need a domain for our Bitwarden instance</li><li>Linux host - we need a Linux machine. It could be any Linux host, but for the sake of simplicity I will provision an Ubuntu (22.04) VPS on <a href="https://www.hetzner.com/cloud?ref=bojana.dev">Hetzner Cloud</a>, but you should be good with any provider and distribution.</li><li>As per Bitwarden documentation, there are certain requirements in terms of system specification (see the minimum and recommended resource below)</li><li>Time and patience &#x1F60A;</li></ul><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://bojana.dev/content/images/2023/01/firefox_z98cqs2XIR.png" class="kg-image" alt="How to self-host Bitwarden password manager" loading="lazy" width="695" height="265" srcset="https://bojana.dev/content/images/size/w600/2023/01/firefox_z98cqs2XIR.png 600w, https://bojana.dev/content/images/2023/01/firefox_z98cqs2XIR.png 695w"><figcaption>System specifications for bitwarden</figcaption></figure><p>I will use recommended system resources, so a Linux VPS with 2 cores and 4GB of RAM. (Note: on mentioned provider, this cost is about 5.35&#x20AC;/month)</p><p>As stated above, we will also need a domain/subdomain. If you already own a domain, you can create a subdomain for bitwarden.</p><h3 id="domain-setup">Domain setup</h3><p>I already have a domain purchased, so the first thing I will do is set up a DNS zone for my domain. By default, DNS zone is already set up wherever you purchased your domain, but I found it easier to be managed on the VPS provider side. Now, depending on your provider this might look different in different dashboards/systems, but it boils down to creating a file that will hold DNS records for your domain.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://bojana.dev/content/images/2023/01/firefox_R2zrFhxdFW.png" class="kg-image" alt="How to self-host Bitwarden password manager" loading="lazy" width="762" height="272" srcset="https://bojana.dev/content/images/size/w600/2023/01/firefox_R2zrFhxdFW.png 600w, https://bojana.dev/content/images/2023/01/firefox_R2zrFhxdFW.png 762w" sizes="(min-width: 720px) 720px"><figcaption>DNS Console dashboard in Hetzner</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://bojana.dev/content/images/2023/01/ixJ8GSUxWO.png" class="kg-image" alt="How to self-host Bitwarden password manager" loading="lazy" width="977" height="905" srcset="https://bojana.dev/content/images/size/w600/2023/01/ixJ8GSUxWO.png 600w, https://bojana.dev/content/images/2023/01/ixJ8GSUxWO.png 977w" sizes="(min-width: 720px) 720px"><figcaption>setting up DNS zone for domain</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://bojana.dev/content/images/2023/01/firefox_i4oj9MSEYJ.png" class="kg-image" alt="How to self-host Bitwarden password manager" loading="lazy" width="1133" height="527" srcset="https://bojana.dev/content/images/size/w600/2023/01/firefox_i4oj9MSEYJ.png 600w, https://bojana.dev/content/images/size/w1000/2023/01/firefox_i4oj9MSEYJ.png 1000w, https://bojana.dev/content/images/2023/01/firefox_i4oj9MSEYJ.png 1133w" sizes="(min-width: 720px) 720px"><figcaption>configured name servers for <strong>vps-playground.cloud</strong> domain (Hetzner)</figcaption></figure><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2023/01/firefox_LtrJDPUMYm.png" class="kg-image" alt="How to self-host Bitwarden password manager" loading="lazy" width="724" height="132" srcset="https://bojana.dev/content/images/size/w600/2023/01/firefox_LtrJDPUMYm.png 600w, https://bojana.dev/content/images/2023/01/firefox_LtrJDPUMYm.png 724w" sizes="(min-width: 720px) 720px"></figure><p>The next step is to configure nameservers. You can see I purchased my domain at <a href="http://porkbun.com/?ref=bojana.dev">porkbun.com</a>, so I would need (as suggested on the screenshot above) to remove their nameservers and replace them with my VPS provider&#x2019;s name servers (Hetzner) on the porkbun side, so in their dashboard for domain setup.</p><p>As I said, depending on your VPS provider and domain registrar this might look different. So I will go on porkbun dashboard for my domain, and there is a section &#x201C;Authoritative Nameservers&#x201D;, which I should edit and add Hetzner&#x2019;s nameservers (as suggested in their message).</p><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2023/01/83JImafDhW.png" class="kg-image" alt="How to self-host Bitwarden password manager" loading="lazy" width="678" height="224" srcset="https://bojana.dev/content/images/size/w600/2023/01/83JImafDhW.png 600w, https://bojana.dev/content/images/2023/01/83JImafDhW.png 678w"></figure><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2023/01/83JImafDhW-1.png" class="kg-image" alt="How to self-host Bitwarden password manager" loading="lazy" width="678" height="224" srcset="https://bojana.dev/content/images/size/w600/2023/01/83JImafDhW-1.png 600w, https://bojana.dev/content/images/2023/01/83JImafDhW-1.png 678w"></figure><p>These take some time to propagate, usually about 24h.</p><p>Now, we should add A record to map our domain to the public IP address of our VPS. The &#x201C;@&#x201D; sign represents root which for my domain would be just:<br><strong>vps-playground.cloud</strong>. The other record with the name &#x201C;www&#x201D; is optional but usually added.</p><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2023/01/firefox_4Lx4zCapre.png" class="kg-image" alt="How to self-host Bitwarden password manager" loading="lazy" width="1067" height="124" srcset="https://bojana.dev/content/images/size/w600/2023/01/firefox_4Lx4zCapre.png 600w, https://bojana.dev/content/images/size/w1000/2023/01/firefox_4Lx4zCapre.png 1000w, https://bojana.dev/content/images/2023/01/firefox_4Lx4zCapre.png 1067w" sizes="(min-width: 720px) 720px"></figure><p>With these configurations in place, let&#x2019;s go and change the hostname on our VPS. But before that, I will create a non-root user.</p><pre><code class="language-bash">root@ubuntu-4gb-nbg1-1:~# adduser bojana
Adding user `bojana&apos; ...
Adding new group `bojana&apos; (1000) ...
Adding new user `bojana&apos; (1000) with group `bojana&apos; ...
Creating home directory `/home/bojana&apos; ...
Copying files from `/etc/skel&apos; ...
New password:
Retype new password:
passwd: password updated successfully
Changing the user information for bojana
Enter the new value, or press ENTER for the default
        Full Name []: Bojana Dejanovic
        Room Number []:
        Work Phone []:
        Home Phone []:
        Other []:
Is the information correct? [Y/n] Y
</code></pre><p>And add the user to sudo group:</p><pre><code class="language-bash">root@ubuntu-4gb-nbg1-1:~# usermod -aG sudo bojana
root@ubuntu-4gb-nbg1-1:~# su - bojana
To run a command as administrator (user &quot;root&quot;), use &quot;sudo &lt;command&gt;&quot;.
See &quot;man sudo_root&quot; for details.
bojana@ubuntu-4gb-nbg1-1:~$
</code></pre><p>Now, we can see that our hostname is some generic one, assigned by VPS provider when we provisioned the machine.</p><pre><code class="language-bash">bojana@ubuntu-4gb-nbg1-1:~$ hostname
ubuntu-4gb-nbg1-1
bojana@ubuntu-4gb-nbg1-1:~$ hostnamectl
 Static hostname: ubuntu-4gb-nbg1-1
       Icon name: computer-vm
         Chassis: vm
      Machine ID: ff55896032564222b6ccd48ae7410afc
         Boot ID: 18b27cdedfe54250992d1180efc17cdb
  Virtualization: kvm
Operating System: Ubuntu 22.04.1 LTS
          Kernel: Linux 5.15.0-57-generic
    Architecture: x86-64
 Hardware Vendor: Hetzner
  Hardware Model: vServer
</code></pre><p>We will use hostnamectl command to set a new hostname which of course will be the same as our domain name.</p><pre><code class="language-bash">bojana@ubuntu-4gb-nbg1-1:~$ sudo hostnamectl set-hostname vps-playground.cloud
</code></pre><p>Additionally, we will modify entry to /etc/hosts file :</p><pre><code class="language-bash">bojana@ubuntu-4gb-nbg1-1:~$ sudoedit /etc/hosts
127.0.1.1 vps-playground.cloud vps-playground
127.0.0.1 localhost
</code></pre><pre><code class="language-bash">bojana@ubuntu-4gb-nbg1-1:~$ hostnamectl
 Static hostname: vps-playground.cloud
       Icon name: computer-vm
         Chassis: vm
      Machine ID: ff55896032564222b6ccd48ae7410afc
         Boot ID: 18b27cdedfe54250992d1180efc17cdb
  Virtualization: kvm
Operating System: Ubuntu 22.04.1 LTS
          Kernel: Linux 5.15.0-57-generic
    Architecture: x86-64
 Hardware Vendor: Hetzner
  Hardware Model: vServer
</code></pre><p>You will notice that your prompt hasn&#x2019;t changed, and still displays the old hostname. Try logging out and then back in and the new hostname should be visible in the prompt.</p><pre><code class="language-bash">bojana@vps-playground:~$
</code></pre><p>Now, to verify our domain is correctly set up, try issuing this command from your local machine:</p><pre><code class="language-bash">[bojana@K7OfNine ~]# drill vps-playground.cloud
;; -&gt;&gt;HEADER&lt;&lt;- opcode: QUERY, rcode: NOERROR, id: 54204
;; flags: qr rd ; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;; vps-playground.cloud.        IN      A

;; ANSWER SECTION:
vps-playground.cloud.   0       IN      A       142.132.165.72

;; AUTHORITY SECTION:

;; ADDITIONAL SECTION:

;; Query time: 124 msec
;; SERVER: 172.20.160.1
;; WHEN: Thu Jan 12 13:01:35 2023
;; MSG SIZE  rcvd: 74
</code></pre><p>We can see our record in answer section.</p><p>After the domain is setup correctly, we have to ensure that the ports 80 and 443 on VPS are open.</p><p>Depending on your distribution, how to do this may vary. I am using Ubuntu which comes with <a href="https://help.ubuntu.com/community/UFW?ref=bojana.dev">ufw</a> preinstalled, which is a more user-friendly interface over iptables. To allow ports 80 and 443 we add two rules:</p><pre><code class="language-bash">bojana@vps-playground:~$ sudo ufw allow 80/tcp
Rule added
Rule added (v6)
bojana@vps-playground:~$ sudo ufw allow 443/tcp
Rule added
Rule added (v6)
</code></pre><p>And to verify the rules are added:</p><pre><code class="language-bash">bojana@vps-playground:~$ sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW       Anywhere
80/tcp                     ALLOW       Anywhere
443/tcp                    ALLOW       Anywhere
22/tcp (v6)                ALLOW       Anywhere (v6)
80/tcp (v6)                ALLOW       Anywhere (v6)
443/tcp (v6)               ALLOW       Anywhere (v6)
</code></pre><h3 id="docker-and-docker-compose">Docker and Docker compose</h3><p>As I mentioned in the intro, we will deploy Bitwarden as an array of Docker containers. A precondition for this is to have Docker Engine installed. To install Docker Engine on your VPS it&#x2019;s best to <a href="https://docs.docker.com/engine/install/ubuntu/?ref=bojana.dev#installation-methods">follow official documentation</a> and install it <a href="https://docs.docker.com/engine/install/ubuntu/?ref=bojana.dev#install-using-the-repository">using the repository</a>. Here, in the Server section, you can find instructions for your <a href="https://docs.docker.com/engine/install/?ref=bojana.dev#server">distribution of choice</a>.</p><p>Once complete, verify that you have both docker and docker compose installed:</p><pre><code class="language-bash">bojana@vps-playground:~$ sudo docker run hello-world
Unable to find image &apos;hello-world:latest&apos; locally
latest: Pulling from library/hello-world
2db29710123e: Pull complete
Digest: sha256:aa0cc8055b82dc2509bed2e19b275c8f463506616377219d9642221ab53cf9fe
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.
.......
bojana@vps-playground:~$ docker compose version
Docker Compose version v2.14.1
</code></pre><h3 id="bitwarden-user-and-directory">Bitwarden user and directory</h3><p>Now that we have docker installed, Bitwarden documentation recommends creating a dedicated Bitwarden service account.</p><p>To create bitwarden user:</p><pre><code class="language-bash">bojana@vps-playground:~$ sudo adduser bitwarden
</code></pre><p>Add bitwarden user to the docker group:</p><pre><code class="language-bash">sudo usermod -aG docker bitwarden
</code></pre><p>Create bitwarden directory:</p><pre><code class="language-bash">sudo mkdir /opt/bitwarden
</code></pre><p>Set permissions for the /opt/bitwarden directory and set bitwarden user as the owner</p><pre><code class="language-bash">sudo chmod -R 700 /opt/bitwarden
sudo chown -R bitwarden:bitwarden /opt/bitwarden
</code></pre><h3 id="install-bitwarden">Install Bitwarden</h3><p>Before retrieving the installation script and running it, first ensure you visit <a href="https://bitwarden.com/host/?ref=bojana.dev">https://bitwarden.com/host/</a> to request hosting installation Id and Key. After filling out firm with your email, you will retrieve the &#xA0;installation id and key. Keep it somewhere, because we will need it during installation.</p><p>Before proceeding with installation script, let&#x2019;s switch to user we created in the previous step and position to /opt/bitwarden directory.</p><pre><code class="language-bash">bojana@vps-playground:~$ su - bitwarden
Password:
bitwarden@vps-playground:~$ cd /opt/bitwarden/
</code></pre><p>Now, we need to download the installation script for Bitwarden:</p><pre><code class="language-bash">bitwarden@vps-playground:~$ curl -Lso bitwarden.sh &lt;https://go.btwrdn.co/bw-sh&gt; &amp;&amp; chmod 700 bitwarden.sh
</code></pre><p>And run the script:</p><pre><code class="language-bash">./bitwarden.sh install
</code></pre><p>First, we will be prompted to enter our domain name:</p><pre><code class="language-bash">bitwarden@vps-playground:/opt/bitwarden$ ./bitwarden.sh install
 _     _ _                         _
| |__ (_) |___      ____ _ _ __ __| | ___ _ __
| &apos;_ \\| | __\\ \\ /\\ / / _` | &apos;__/ _` |/ _ \\ &apos;_ \\
| |_) | | |_ \\ V  V / (_| | | | (_| |  __/ | | |
|_.__/|_|\\__| \\_/\\_/ \\__,_|_|  \\__,_|\\___|_| |_|

Open source password management solutions
Copyright 2015-2023, 8bit Solutions LLC
&lt;https://bitwarden.com&gt;, &lt;https://github.com/bitwarden&gt;

===================================================

bitwarden.sh version 2023.1.0
Docker version 20.10.22, build 3a2c30b
Docker Compose version v2.14.1

(!) Enter the domain name for your Bitwarden instance (ex. bitwarden.example.com): vps-playground.com
</code></pre><p>Next, we will enter y to use Let&#x2019;s Encrypt to generate a free SSL cert:</p><pre><code class="language-bash">(!) Do you want to use Let&apos;s Encrypt to generate a free SSL certificate? (y/n): y
</code></pre><p>Next, enter an email where Let&#x2019;s Encrypt will notify you about SSL expirations, enter a &#xA0;valid email so you can receive the email and be aware:</p><pre><code class="language-bash">(!) Enter your email address (Let&apos;s Encrypt will send you certificate expiration reminders):
</code></pre><p>At this point, you should &#xA0;see output from docker pulling the certbot image and retrieving the certificate:</p><pre><code class="language-bash">Using default tag: latest
latest: Pulling from certbot/certbot
ca7dd9ec2225: Pull complete
9e124a36b9ab: Pull complete
42cba90def7f: Pull complete
036c0ab6a768: Pull complete
de6312618cf7: Pull complete
f2b159e8e18d: Pull complete
5c3094a661e9: Pull complete
ae4269d8cd1f: Pull complete
fc17a613a054: Pull complete
7560c853872e: Pull complete
ab060fadf2d2: Pull complete
b81696353590: Pull complete
144b4c29fbe6: Pull complete
Digest: sha256:f632e55104da84ba5b54bc858a67ac6c9b4f68790ea823515867c993153c3fb4
Status: Downloaded newer image for certbot/certbot:latest
docker.io/certbot/certbot:latest
Saving debug log to /etc/letsencrypt/logs/letsencrypt.log
Account registered.
Requesting a certificate for vps-playground.cloud

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/vps-playground.cloud/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/vps-playground.cloud/privkey.pem
This certificate expires on 2023-04-17.
These files will be updated when the certificate renews.
</code></pre><pre><code class="language-bash">(!) Enter the database name for your Bitwarden instance (ex. vault):
</code></pre><p>This is pretty self-explanatory, enter the database name for Bitwarden instance.</p><p>At this point image called bitwarden/setup is being downloaded and after it&#x2019;s finished it will prompt you to enter the <em>installation id</em> and <em>installation key</em> we retrieved from bitwarden&#x2019;s website:</p><pre><code class="language-bash">(!) Enter your installation id (get at &lt;https://bitwarden.com/host&gt;):
(!) Enter your installation key:
</code></pre><p>After this appropriate keys will be generated and installation should be complete.</p><p>Now we should start bitwarden with:</p><pre><code class="language-bash">./bitwarden.sh start
</code></pre><p>This will pull all the necessary Docker images and start the containers.</p><p>If everything went well you should see the following message:</p><pre><code class="language-bash">Bitwarden is up and running!
===================================================
</code></pre><p>You can verify all containers running by issuing <strong>docker ps</strong> command.</p><p>Now, try visiting your domain, and you should be able to see Bitwarden login/signup screen:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://bojana.dev/content/images/2023/01/0uC7VSAEnE.png" class="kg-image" alt="How to self-host Bitwarden password manager" loading="lazy" width="1434" height="841" srcset="https://bojana.dev/content/images/size/w600/2023/01/0uC7VSAEnE.png 600w, https://bojana.dev/content/images/size/w1000/2023/01/0uC7VSAEnE.png 1000w, https://bojana.dev/content/images/2023/01/0uC7VSAEnE.png 1434w" sizes="(min-width: 720px) 720px"><figcaption>Bitwarden login/signup screen</figcaption></figure><p>First, we need to create an account so click the link. Enter the required fields, of course choose a strong <em>Master password,</em> and click <em>create accoun</em>t.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://bojana.dev/content/images/2023/01/US1QPjxU9R.png" class="kg-image" alt="How to self-host Bitwarden password manager" loading="lazy" width="1425" height="1195" srcset="https://bojana.dev/content/images/size/w600/2023/01/US1QPjxU9R.png 600w, https://bojana.dev/content/images/size/w1000/2023/01/US1QPjxU9R.png 1000w, https://bojana.dev/content/images/2023/01/US1QPjxU9R.png 1425w" sizes="(min-width: 720px) 720px"><figcaption>Create Bitwarden account</figcaption></figure><p>Now, you can log in to your self-hosted Bitwarden instance:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://bojana.dev/content/images/2023/01/chrome_EYAudlE3DB.png" class="kg-image" alt="How to self-host Bitwarden password manager" loading="lazy" width="1431" height="981" srcset="https://bojana.dev/content/images/size/w600/2023/01/chrome_EYAudlE3DB.png 600w, https://bojana.dev/content/images/size/w1000/2023/01/chrome_EYAudlE3DB.png 1000w, https://bojana.dev/content/images/2023/01/chrome_EYAudlE3DB.png 1431w" sizes="(min-width: 720px) 720px"><figcaption>Bitwarden dashboard on the self-hosted instance</figcaption></figure><h3 id="set-up-2fa">Set up 2FA</h3><p>For the next step I would recommend is to enable <a href="https://authy.com/what-is-2fa/?ref=bojana.dev">2FA</a>.</p><p>Go to Account settings within your Bitwarden account, and click on the Security tab and then &#x201C;Two-step login&#x201D; on the &#x201C;Providers&#x201D; list click Manage for Authenticator app.</p><p>At this point, you should have installed either Google Authenticator or Authy app. I have Authy.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://bojana.dev/content/images/2023/01/6JNPovksrG.png" class="kg-image" alt="How to self-host Bitwarden password manager" loading="lazy" width="1425" height="1315" srcset="https://bojana.dev/content/images/size/w600/2023/01/6JNPovksrG.png 600w, https://bojana.dev/content/images/size/w1000/2023/01/6JNPovksrG.png 1000w, https://bojana.dev/content/images/2023/01/6JNPovksrG.png 1425w" sizes="(min-width: 720px) 720px"><figcaption>2FA configuration</figcaption></figure><p>It will once more prompt you for Bitwarden&#x2019;s master password, and after you should be able to see the QR code and scan it using Authy/Google authenticator app. A new app should be added to your list. And that&#x2019;s it! You have successfully configured 2FA for your self-hosted Bitwarden instance.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://bojana.dev/content/images/2023/01/bOgCa1BV9H.png" class="kg-image" alt="How to self-host Bitwarden password manager" loading="lazy" width="1434" height="1552" srcset="https://bojana.dev/content/images/size/w600/2023/01/bOgCa1BV9H.png 600w, https://bojana.dev/content/images/size/w1000/2023/01/bOgCa1BV9H.png 1000w, https://bojana.dev/content/images/2023/01/bOgCa1BV9H.png 1434w" sizes="(min-width: 720px) 720px"><figcaption>Token for Bitwarden as seen in Authy app</figcaption></figure><p>As the warning in a screenshot on the Two-step login configuration page suggests you should backup recovery codes, in case something happens to the device you are using for 2FA.</p><p>A few notes about some additional features of Bitwarden and the installation itself. In this guide, I did not cover how to set up Bitwarden to be able to send various emails. SMTP configuration for Bitwarden can be set up in <code>./bwdata/env/global.override.env</code> file by providing values for SMTP host, port, and such.</p><p>Bear in mind that the installation script uses <code>./bwdata/config.yml</code> to generate the necessary assets for installation. After every change to this yml file you should run:</p><pre><code class="language-bash">./bitwarden.sh rebuild
</code></pre><p>For instance, on one Bitwarden instance, I had to change the default ports (80 and 443) to something else because I already had some other &#xA0;app using the ports on that host. So I had to modify the config and rebuild the images.</p><p>One additional note is that besides using &#xA0;web access, you can also use <a href="https://bitwarden.com/download/?ref=bojana.dev">Bitwarden desktop app</a>. Just make sure that on Settings you enter the proper server URL for your instance.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://bojana.dev/content/images/2023/01/Bitwarden_bnYQqkBeFq.png" class="kg-image" alt="How to self-host Bitwarden password manager" loading="lazy" width="996" height="501" srcset="https://bojana.dev/content/images/size/w600/2023/01/Bitwarden_bnYQqkBeFq.png 600w, https://bojana.dev/content/images/2023/01/Bitwarden_bnYQqkBeFq.png 996w" sizes="(min-width: 720px) 720px"><figcaption>Setting up Server URL on Bitwarden Desktop app for Windows</figcaption></figure><p>After quite a bit of work, I must admit :) you have an operational, self-hosted, Bitwarden instance.</p><p>Hooray! &#x1F389;</p>]]></content:encoded></item><item><title><![CDATA[DNS - structure, organization and how it works (Part 1)]]></title><description><![CDATA[<p>DNS - Domain Name System, is maybe the easiest protocol to grasp. At least, to grasp what its fundamental purpose is, and that is to translate or map human readable - symbolic names (such as <strong>bojana.dev</strong>) to IP addresses (such as <strong>157.90.124.251</strong>). </p><p>It is also possible</p>]]></description><link>https://bojana.dev/dns-structure-organization-and-how-it-works-part-1/</link><guid isPermaLink="false">63bc299d9d3b32e9e4e8b287</guid><dc:creator><![CDATA[Bojana Dejanovic]]></dc:creator><pubDate>Tue, 10 Jan 2023 11:18:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1545987796-200677ee1011?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDI0fHxzZXJ2ZXJ8ZW58MHx8fHwxNjczMjc2NTU4&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1545987796-200677ee1011?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDI0fHxzZXJ2ZXJ8ZW58MHx8fHwxNjczMjc2NTU4&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="DNS - structure, organization and how it works (Part 1)"><p>DNS - Domain Name System, is maybe the easiest protocol to grasp. At least, to grasp what its fundamental purpose is, and that is to translate or map human readable - symbolic names (such as <strong>bojana.dev</strong>) to IP addresses (such as <strong>157.90.124.251</strong>). </p><p>It is also possible to map IP address to some symbolic name, although not as often used, but it is possible, and can come in &#xA0;handy (for instance in when issuing traceroute command).</p><p>While DNS&apos;s fundamental purpose is pretty easy to grasp for everyone, it&apos;s internal workings are maybe not interesting for everyone, but since you are here reading about it, let&apos;s dive in. :)</p><ul><li><a href="https://bojana.dev/dns-structure-organization-and-how-it-works-part-1/#a-bit-of-history">A bit of history</a></li><li><a href="https://bojana.dev/dns-structure-organization-and-how-it-works-part-1/#dns-structure">DNS structure</a></li><li><a href="https://bojana.dev/dns-structure-organization-and-how-it-works-part-1/#how-dns-is-organized">How DNS is organized</a></li><li><a href="https://bojana.dev/dns-structure-organization-and-how-it-works-part-1/#how-it-works">How it works</a></li><li><a href="https://bojana.dev/dns-structure-organization-and-how-it-works-part-1/#name-resolving">Name resolving</a></li></ul><h2 id="a-bit-of-history">A bit of history</h2><p>Once upon time, in a dawn of Internet, there was a file called <em>hosts.txt</em> which contained list of all computer names and their IP addresses. To be &quot;up-to-date&quot;, &#xA0;all computers had to download this file during the night, from a location <a href="https://en.wikipedia.org/wiki/Hosts_(file)?ref=bojana.dev">where it was hosted</a>. For a network of couple hundred computers, this wasn&#x2019;t a big deal.</p><p>But, as network grow, and thousands of computers were added to the network, it was clear that this way of organization is not sustainable. First of all, the file would get too big. And second of all, and more important, if the names weren&#x2019;t managed in one central place, name collisions would be often, which would be unimaginable for the big international network, due to potential load and delay.</p><p>For purpose of solving above mentioned problems, DNS was created. In the core od DNS system is hierarchy of domain names and the system of distributed databases that implements that structure.</p><p>As a relict of the past, <em>hosts.txt</em> still exist on all operating systems, not just as a backup for when we don&#x2019;t have DNS available, but it enables to override contents of DNS. This can come in handy for local development purposes, for instance.</p><pre><code class="language-bash">127.0.0.1 localhost
172.16.4.221 develop.mydomain.com
</code></pre><h2 id="dns-structure">DNS Structure</h2><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2023/01/DNS-structure.svg" class="kg-image" alt="DNS - structure, organization and how it works (Part 1)" loading="lazy" width="464" height="283"></figure><p>DNS is a hierarchy structure, that is global (whole Internet).</p><p>It begins with <strong>root</strong> domain, with a label of an empty string (&#x201C; &#x201C;). Every node in this tree is a symbolic name.</p><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2023/01/ONIYzZq2Xt.png" class="kg-image" alt="DNS - structure, organization and how it works (Part 1)" loading="lazy" width="1194" height="315" srcset="https://bojana.dev/content/images/size/w600/2023/01/ONIYzZq2Xt.png 600w, https://bojana.dev/content/images/size/w1000/2023/01/ONIYzZq2Xt.png 1000w, https://bojana.dev/content/images/2023/01/ONIYzZq2Xt.png 1194w" sizes="(min-width: 720px) 720px"></figure><p>The path from the root over individual nodes generates a full name of domain, so called - Fully Qualified Domain Name (<strong>FQDN</strong>). Usually the dot at the end is omitted.</p><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2023/01/nAv8Efxuut.png" class="kg-image" alt="DNS - structure, organization and how it works (Part 1)" loading="lazy" width="1835" height="800" srcset="https://bojana.dev/content/images/size/w600/2023/01/nAv8Efxuut.png 600w, https://bojana.dev/content/images/size/w1000/2023/01/nAv8Efxuut.png 1000w, https://bojana.dev/content/images/size/w1600/2023/01/nAv8Efxuut.png 1600w, https://bojana.dev/content/images/2023/01/nAv8Efxuut.png 1835w" sizes="(min-width: 720px) 720px"></figure><p>Fully qualified domain name consist of multiple segments:</p><ul><li>each segment consist of max. 63 characters</li><li>maximum length of FQDN is 255 characters</li><li>ASCII characters: letters, numbers, &#x201C;_&#x201D;, &#x201C;-&#x201D;</li><li>Case-insensitive</li></ul><p><strong><a href="http://etf.unibl.org/?ref=bojana.dev">etf.unibl.org</a></strong></p><ul><li><strong>org -</strong> is Top Level Domain (TLD)</li><li><strong>unibl -</strong> labels, subdomains</li><li><strong>etf -</strong> label, can be the name of a device and a subdomain</li></ul><p>Examples:</p><ul><li><a href="http://el.etf.unibl.org/?ref=bojana.dev">el.etf.unibl.org</a></li><li>blog.bojana.dev</li><li><a href="http://www.youtube.com/?ref=bojana.dev">www.youtube.com</a></li></ul><h3 id="tldsubdomains-of-root-domain">TLD - subdomains of root domain</h3><p>The root domain contains all top-level domains of the Internet. The core group of generic top-level domains consists of the com, net, org, biz and info domains. The number of generic Top Level Domains (gTLD) as of March 2018 exceeds 1200 domains.</p><h3 id="cctldcountry-code-tld">ccTLD - Country Code TLD</h3><p>These top level domains belong to individual countries. They were assigned by <a href="https://www.iana.org/?ref=bojana.dev">IANA</a> &#xA0;by two-letter <a href="https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes?ref=bojana.dev">ISO 3166 code</a> of the country.</p><p>Some examples:</p><ul><li>rs - Republic of Serbia</li><li>sr - Surinam</li><li>eu - European Union</li><li>ba - Bosnia and Herzegovina</li></ul><p>While <a href="https://en.wikipedia.org/wiki/GTLD?ref=bojana.dev">gTLDs</a> have to obey international regulations, regulations related to ccTLD are regulated by each country domain regulation body. For example, for <strong>.rs</strong> domain it&apos;s &quot;Serbian National Internet Domain Registry&quot; &#xA0;(<a href="https://www.rnids.rs/en?ref=bojana.dev">RNIDS</a>) , for <strong>.ba</strong> it&apos;s &quot;Univerzitetski tele-informati&#x10D;ki centar - <a href="https://nic.ba/?ref=bojana.dev">UTIC</a>&quot;, for <strong>.hr</strong> it&apos;s <a href="https://www.carnet.hr/?ref=bojana.dev">CARNe</a>t - Croatian Academic and Research Network, for <strong>.me </strong>it&apos;s Government of Montenegro :) and so on.</p><p>As you can see some TLD are more &quot;desirable&quot; than others. In general, anyone can buy or better say &quot;lease&quot; a domain for a period of at least one year (that&apos;s a period for renewal - meaning you will have to pay a fee for &quot;owning&quot; a domain each year).</p><p>Fun fact: <strong>.tv</strong> is ccTLD for <a href="https://en.wikipedia.org/wiki/Tuvalu?ref=bojana.dev">Tuvalu</a>, an island state in Polynesia. For it&apos;s abbreviation for &quot;television&quot; the .tv is quite popular, so much that 8.4% of the revenue of the <a href="https://en.wikipedia.org/wiki/Government_of_Tuvalu?ref=bojana.dev">Government of Tuvalu</a> came from .tv royalties.<br>Similair story goes for <strong>.fm</strong> a ccTLD for <a href="https://en.wikipedia.org/wiki/Federated_States_of_Micronesia?ref=bojana.dev">Federated States Of Micronesia</a>, also an island state, in Oceania. I think you can guess why this domain is popular.</p><h2 id="how-dns-is-organized">How DNS is organized</h2><p>Logical structure is physically organized in a distributed way. Whole tree is divided into ZONES.</p><h3 id="dns-zone">DNS zone</h3><p>Part of the tree (one or more nodes). It contains information of included domains, usually one zone is one node (domain). Administratively speaking, one zone belongs to one organization (one company, or university, or state, etc.). In a nutshell it is one text file defined on one server - DNS or NS server (name server).</p><p>Topology of domains is technically completely independent of physical network topology.</p><p>Computers/servers from one domain can belong to different, physically disjointed and separated networks. And on the other hand, computers/servers from one physical network can belong to different domains.</p><h3 id="how-it-works">How it works</h3><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2023/01/PrimarySecondaryDNS.svg" class="kg-image" alt="DNS - structure, organization and how it works (Part 1)" loading="lazy" width="368" height="201"></figure><p><strong>Primary DNS</strong> is defined for some node in the tree above (some domain), and the administrator on that primary DNS Server can modify data, add domains, change names, records, etc.</p><p>As it is not good for single server to be responsible for domains, we have a redundant copies on one or more <strong>Secondary DNS</strong> Servers. It is also good that they are separated (distant) from the primary DNS servers, so that they can take over if primary DNS fails for whatever reason.</p><p>Secondary DNS server(s), periodically retrieves copy of DNS zone form Primary DNS server.</p><p>Both primary and secondary DNS servers are called <strong>authoritative DNS servers,</strong> because they have whole DNS zones for particular domains. Both primary and secondary DNS servers have equal role in name resolving.</p><h3 id="name-resolving">Name resolving</h3><p>DNS servers are resolving the queries from the clients.</p><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2023/01/Y0jRIB9pU4.png" class="kg-image" alt="DNS - structure, organization and how it works (Part 1)" loading="lazy" width="1907" height="594" srcset="https://bojana.dev/content/images/size/w600/2023/01/Y0jRIB9pU4.png 600w, https://bojana.dev/content/images/size/w1000/2023/01/Y0jRIB9pU4.png 1000w, https://bojana.dev/content/images/size/w1600/2023/01/Y0jRIB9pU4.png 1600w, https://bojana.dev/content/images/2023/01/Y0jRIB9pU4.png 1907w" sizes="(min-width: 720px) 720px"></figure><p>Name resolving is a process of finding IP addresses for the requested domain (name).<br>DNS resolver is a piece of software that resides both on a client and a server side.<br>On the client side, DNS resolver is sending a query to locally configured DNS server (over UDP port 53), if there is no data in the local cache.</p><p>On the server side, DNS resolver will check the local cache or zones database, and if there is no data for a particular name, it will send the query &#xA0;to other DNS servers (primary and secondary), authoritative servers, for corresponding domain.</p><p>DNS server, that local one for the client, when resolving the name, it&#x2019;s doing that in a <strong>recursive</strong> manner. What that means? It means it will either return or resolve the name completely or it will report an error. How the server will resolve the name, client has no interest in, client just wants the name to be resolved.</p><p>DNS server will <strong>iteratively</strong> ask other servers when trying to resolve the name. Other DNS servers will return partially resolved name or &#x201C;the best possible answer&#x201D;, and it will <strong>refer</strong> to other DNS servers in the hierarchy that can resolve the name.</p><p>For ex. let&#x2019;s say the client is requesting following domain : <strong><a href="http://el.etf.unibl.org/?ref=bojana.dev">el.etf.unibl.org</a></strong></p><p>As we said, client doesn&#x2019;t have this in it&#x2019;s local cache so it will ask locally configured DNS server. That locally configured DNS server also doesn&#x2019;t have the name neither in cache or zones database.</p><p>What happens next?</p><p>Well, we said that all domains start from one root domain (the empty string &#x201C; &#x201D; is a root of tree structure from above).</p><p>These servers are 13 well known <a href="https://www.iana.org/domains/root/servers?ref=bojana.dev">root DNS servers</a>, on well known IPs, and web browser and operating systems already know which addresses are those.</p><ol><li>Let&#x2019;s say it will ask this root DNS server &#xA0;- <strong>198.41.0.4</strong> where is <strong><a href="http://el.etf.unibl.org/?ref=bojana.dev">el.etf.unibl.org</a> ?</strong></li><li><strong>198.41.0.4</strong> will then &#x201C;say&#x201D; - &#x201C;Ok, I have no idea what <a href="http://el.etf.unibl.org/?ref=bojana.dev">el.etf.unibl.org</a> is, but I know what <strong>.org.</strong> Because .org is a TLD which is defined on root server. I know authoritative servers for .org domain. Here are the addresses for them.</li><li>Then on of those authoritative servers will know where is <strong><a href="http://unibl.org/?ref=bojana.dev">unibl.org</a></strong> and will again provide the list where <a href="http://etf.unibl.org/?ref=bojana.dev">etf.unibl.org</a> is</li><li>And eventually fully qualified domain name will be resolved. (<a href="http://el.etf.unibl.org/?ref=bojana.dev">el.etf.unibl.org</a>)</li></ol><p>In the next article we will see how are DNS zones defined, and examine each <strong>Resource Record</strong> (RR) that zone is comprised of.</p><p>References:<br>[1] <a href="https://www.amazon.com/Computer-Networks-5th-Andrew-Tanenbaum/dp/0132126958?ref=bojana.dev">Andrew S. Tanenbaum - &quot;Computer Networks&quot;</a><br>[2] <a href="https://www.iana.org/?ref=bojana.dev">IANA.org</a><br>[3] <a href="https://en.wikipedia.org/wiki/Top-level_domain?ref=bojana.dev">Top level domain, Wikipedia.org</a><br>[4] <a href="https://en.wikipedia.org/wiki/Country_code_top-level_domain?ref=bojana.dev">Country code top level domain, Wikipedia.org</a><br><em>Special thanks to <a href="https://www.unibl.org/fis/zaposlen/1143-zlatko-dejanovic?ref=bojana.dev">Zlatko Dejanovi</a>&#x107; for peer reviewing the article.</em></p>]]></content:encoded></item><item><title><![CDATA[Async programming (with C#)]]></title><description><![CDATA[<p>In the world of programming you noticed that term &#x201C;async programming&#x201D; is very frequent, and most often then not someone will ask you (or tell you) to do that particular task in &#x201C;asynchronous way&#x201D;. But what that even means?</p><p>Before mentioning any concrete programming language (like</p>]]></description><link>https://bojana.dev/async-programming/</link><guid isPermaLink="false">634422739d3b32e9e4e8b223</guid><dc:creator><![CDATA[Bojana Dejanovic]]></dc:creator><pubDate>Mon, 17 Oct 2022 08:50:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1591267990439-bc68529677c3?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDJ8fGFzeW5jfGVufDB8fHx8MTY2NTQwOTY2Ng&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1591267990439-bc68529677c3?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDJ8fGFzeW5jfGVufDB8fHx8MTY2NTQwOTY2Ng&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Async programming (with C#)"><p>In the world of programming you noticed that term &#x201C;async programming&#x201D; is very frequent, and most often then not someone will ask you (or tell you) to do that particular task in &#x201C;asynchronous way&#x201D;. But what that even means?</p><p>Before mentioning any concrete programming language (like C# or JavaScript), let&#x2019;s try to understand what asynchronous programming is in a broader way.</p><p>To come closer to understanding, let&#x2019;s see what async programming <strong>isn&#x2019;t</strong>.</p><p><strong>Parallel programming</strong></p><p>In parallel programming you are performing (or rather the code you write) multiple calculations <em>at the same point in time.</em> This is usually some CPU-bound stuff you want to do &#x201C;in parallel&#x201D;. For instance, let&#x2019;s say you have an image and you want to apply some filter to it or do some kind of transformation to it. You could go around and say &#x201C;I want to the transformation for first half of the image (pixel this to pixel that) on CPU1, and the other part of the picture I want to be processed on CPU2&#x201D;. And right there we have an important distinction between parallel and async programming, for parallel programming you need to have multiple processors (or multiple CPU cores), and for async programming you don&#x2019;t need to.</p><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2022/10/ParallelProgramming.png" class="kg-image" alt="Async programming (with C#)" loading="lazy" width="321" height="402"></figure><p>In the diagram above we have an illustration of parallel programming. We have two threads (running on each of the CPUs), thread t1 can perform above mention image transformation (first part of the image), and t2 as well for the other part, and this is done &#x201C;in parallel&#x201D;. At some point our code needs to handle the &#x201C;assembling of the parts&#x201D;, but let&#x2019;s not dwell too much on that here.</p><p><strong>CPU blocking</strong></p><p>Let&#x2019;s now imagine that we have a single core CPU, and that we have a regular desktop app.</p><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2022/10/CPUBlocking.drawio.png" class="kg-image" alt="Async programming (with C#)" loading="lazy" width="521" height="599"></figure><p>We are implementing some kind of UI application. If we start certain program, after the initialization the app is waiting for some kind of user input, so it is idle. At some point in time, user for ex. moves the mouse, app becomes active, app handles mouse movement or keyboard input, and &#xA0;goes idle again. </p><p>At some point in time, application decides to go and grab something from the disk, or over the network, or fetch something from the database. That call to the database, or disk, or network, takes some time to complete. If we <em>do not</em> use async programming our thread will <em>actively wait</em> blocking the CPU. As if you were staring at the watch and waiting for something to complete, doing nothing. <br>So our thread is doing nothing until the database call is complete. <br>Since our app also have the user interface (which is also a thread), application will not react to mouse moves for instance. Application will be frozen for the entire time. This is not a good idea as you can see.</p><p>How we can fix this? Well, we can run a little bit of code which issues a db call.<br>Now, we are using async processing, letting the database do it&apos;s work <em>in the background</em>. Something else is busy, not the CPU. </p><p>Now, when the time is elapsed, we will be informed, actively. So, in one point in time database will tell us that it is finished, and that will trigger another piece of asynchronous code, where we can do the processing the results of database query.<br>The important thing is that in the meantime, our CPU, our UI thread, is free.</p><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2022/10/AsyncProgrammingSingleCore.png" class="kg-image" alt="Async programming (with C#)" loading="lazy" width="410" height="491"></figure><p><strong>Web server</strong></p><p>Now let&#x2019;s imagine that we are building a C# app that has a web server built-in (Kestrel) and that we are running a <em>single process</em>. Now what about threads? How many threads will our web server create? Well, it turns out, at least it&#x2019;s a case for a C#, these web servers have a <em>pool of threads</em> and the number of these threads in the thread pool are equal to number of CPU cores. Why just two you might ask? Why not 100? Well, it&#x2019;s because turns out threads take a lot of memory, so they are not so cheap to create, and furthermore, it takes some time to create them.</p><p>This thread pool that we mentioned, have a limit (configurable), a number of threads a machine can handle, because thread pool cannot and should not grow indefinitely. It can outnumber the number of available cpu cores, but typically you try to keep the number as low as possible, in order to keep your web server efficient, otherwise you will be consuming a lot of memory.</p><p>Let&#x2019;s take a look at the diagram below. Image we have a cpu with two cores and our above imagined web server. We have two threads in the thread pool. Each HTTP request is assigned an idle thread in the thread pool.</p><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2022/10/nonsyncwebserver_t1.drawio.png" class="kg-image" alt="Async programming (with C#)" loading="lazy" width="404" height="362"></figure><p>Now our t1 thread is executing on cpu2, it needs some time to process the request, and let&#x2019;s now imagine it also needs to say query the db for some additional information. If we don&#x2019;t do async programming our thread will be blocked for quite some time and the end we are sending the result (response) back to the client.</p><p>The problem is that we don&#x2019;t have just one client, we have multiple clients.</p><p>Imagine for example that we have a client on the phone, that issues another HTTP request. We now have t2 available in the thread pool .</p><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2022/10/nonsyncwebserver.drawio.png" class="kg-image" alt="Async programming (with C#)" loading="lazy" width="554" height="432"></figure><p>And again some time to process the request, request some additional data from the db etc.</p><p>Now we have a problem. What if we have 3rd and 4th and so on, HTTP request, or 100s of HTTP requests. What the system will do in the background, it will try to create additional threads (t3, t4, t5&#x2026;) and will try to schedule those threads on the CPUs, but we have only limited number of CPUs (2 in our example), so these threads are waiting and waiting, they are idle. They are just <em>blocking</em> the CPU.</p><p><strong>Async programming to the rescue</strong></p><p>Let&#x2019;s now see how asynchronous programming is solving these issues. We have the same scenario as above. HTTP GET comes in, and thread t1 in processing the request, but when the database call is issued (to retrieve additional info), we <em>hand back</em> the thread t1 to the thread pool.</p><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2022/10/WebServerAsync.png" class="kg-image" alt="Async programming (with C#)" loading="lazy" width="514" height="585"></figure><p>So now, when our second user with the phone is issuing the request, it can go again to thread t1 and use t1. Again performing a little bit of processing of the incoming web request and accessing the database. Then the result of the first db call is processed and returned to the second client.</p><p>And if you take a look now, we only need a single thread and we can process multiple HTTP requests at the same time. We are not doing parallel programming here, we are doing asynchronous programming. The important takeaway is that you are always using asynchronous programming when you are doing some kind of non-CPU bound work (accessing the database, file system, network, etc.)<br>If you program like this, your web server will really be efficient and it will <em>scale</em> really well. They can handle much more requests, than if you do locked programming.</p><p>In web server development, you don&apos;t need to do parallel programming on your own, it&apos;s done by the web server, as far as for ex. .NET Core &#xA0;goes (Kestrel).<br>NodeJS is however different, as JavaScript is single threaded. <br>This is the reason why in the cloud so many web server have single thread, because if you need more threads, you spin up additional servers.</p><p>Docker containers, for instance, are often <a href="https://devops.stackexchange.com/questions/447/why-it-is-recommended-to-run-only-one-process-in-a-container?ref=bojana.dev">single threaded</a>. From the point of view of a single server, there is no multi threading, but from overall view there is multi threading, because you have multiple servers.</p><p></p><blockquote>Note: I tried to summarize in a written form this <a href="https://www.youtube.com/watch?v=FIZVKteEFyk&amp;t=1572s&amp;ref=bojana.dev">excellent lecture</a> about async programming by <a href="https://www.youtube.com/c/RainerStropek11?ref=bojana.dev">Rainer Stropek</a></blockquote><figure class="kg-card kg-embed-card"><iframe width="200" height="113" src="https://www.youtube.com/embed/FIZVKteEFyk?start=1572&amp;feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen title="C# Async Programming - Part 1: Conceptual Background"></iframe></figure>]]></content:encoded></item><item><title><![CDATA[Reverse proxy explained (with nginx)]]></title><description><![CDATA[<p>If you work as a developer or in any other IT related field, you&apos;ve probably come across the term &quot;reverse proxy&quot;. From my experience people are more often than not confused by the term. So what is a reverse proxy?</p><p>Maybe it&apos;s better to</p>]]></description><link>https://bojana.dev/reverse-proxy-explained-with-nginx/</link><guid isPermaLink="false">62b98c57492941bbdf3203b5</guid><dc:creator><![CDATA[Bojana Dejanovic]]></dc:creator><pubDate>Mon, 27 Jun 2022 14:15:00 GMT</pubDate><media:content url="https://bojana.dev/content/images/2022/06/ReverseProxy-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://bojana.dev/content/images/2022/06/ReverseProxy-1.png" alt="Reverse proxy explained (with nginx)"><p>If you work as a developer or in any other IT related field, you&apos;ve probably come across the term &quot;reverse proxy&quot;. From my experience people are more often than not confused by the term. So what is a reverse proxy?</p><p>Maybe it&apos;s better to start of by explaining what is a forward proxy (often called proxy server or web proxy).</p><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2022/06/Untitled-Diagram.drawio.png" class="kg-image" alt="Reverse proxy explained (with nginx)" loading="lazy" width="661" height="311" srcset="https://bojana.dev/content/images/size/w600/2022/06/Untitled-Diagram.drawio.png 600w, https://bojana.dev/content/images/2022/06/Untitled-Diagram.drawio.png 661w"></figure><p>If there were no forward proxy, client would reach out directly to Server A (or B or C), and Server A would respond. However since forward proxy is in place, client will reach out to forward proxy (on a diagram above), forward proxy will reach out to Server A. When Server A responds, it will contact forward proxy and forward proxy will forward response to the client.</p><p>The question is why use this middleman when clients want to connect to the Internet. There are several reasons:</p><ul><li><strong>avoid browsing restriction</strong> - ex. some content is restricted based on country from which you are accessing it, forward proxy can get around this and let user connect to a proxy (ex. from some <em>allowed</em> country) rather than connecting directly to the visiting site.</li><li><strong>block access to content</strong> - since it&#x2019;s sitting in front of clients, forward proxy can also be used to restrict access to certain sites. (for ex. in schools or libraries)</li><li><strong>to protect their online identity</strong></li></ul><p>OK, but what about reverse proxy?</p><p>In contrast to forward proxy which sits in front of the clients that are trying to connect, <strong>reverse proxy</strong> (also a server) sits in front of one or more origin servers, intercepting requests from the clients. The reverse proxy server will then send requests to and receive responses from the origin server.</p><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2022/06/ReverseProxy.png" class="kg-image" alt="Reverse proxy explained (with nginx)" loading="lazy" width="661" height="311" srcset="https://bojana.dev/content/images/size/w600/2022/06/ReverseProxy.png 600w, https://bojana.dev/content/images/2022/06/ReverseProxy.png 661w"></figure><p>If there were no reverse proxy in place, requests from the client would go directly to Server A (or B or C). With reverse proxy in place, all requests will go to reverse proxy, and reverse proxy will forward the requests to appropriate origin server - A. Reverse proxy will receive responses from A and forward to the client.</p><p>Again question arises of benefit of this schema? There are several benefits of using a reverse proxy:</p><ul><li><strong>load balancing</strong> - with reverse proxy in place you can distribute the traffic across a group of servers to maximize speed and utilize capacity. Also if one of the servers in the group goes offline, the <strong>load balancer</strong> redirects traffic to remaining online servers.</li><li><strong>web acceleration -</strong> reverse proxy can also cache content as well as perform SSL encryption, taking the load off your web server, thus improving their performance.</li><li><strong>security -</strong> since no request go directly to your backends, reverse proxy also protects their identities and makes harder for attacker to leverage a targeted attack against them (such as DDOS attack).</li></ul><p></p><h2 id="nginxfamous-reverse-proxy-server">nginx - famous reverse proxy server<br></h2><p>Nginx was created in 2004 by Russian developer<a href="https://en.wikipedia.org/wiki/Igor_Sysoev?ref=bojana.dev"> Igor Sysoev</a>.<br>Frustrated with Apache, and wanting to build a repleacement capable of handling 10.000 concurrent connections, with focus on performance, high concurrency and low memory usage.</p><p>Today, nginx serves the majority of <a href="https://w3techs.com/technologies/overview/web_server?ref=bojana.dev">world&apos;s top 1000 websites</a>. This popularity, besides it&apos;s excellent performance, can be credited to it&apos;s relative ease of configuration and due to the fact it&apos;s relatively easy to start with.</p><p>Although often mentioned in the context of web servers, nginx, as it&apos;s core is a reverse proxy server, and it is because of this design that it performs so well. </p><p>We will use nginx to illustrate the reverse proxy mechanism.</p><h2 id="hands-on">Hands on</h2><p>To &quot;follow along&quot; you can use any linux host, be it a cloud hosted VPS or a local VM with some version of linux, or WSL. <br>I am using an Ubuntu 20.04 on a WSL.</p><p>You can install nginx with the package manager :</p><pre><code class="language-jsx">apt-get install nginx
</code></pre><pre><code class="language-bash">bojana@7OfNine:~$ nginx -v
nginx version: nginx/1.18.0 (Ubuntu)
</code></pre><p>To keep things simple, lets create a simple nginx configuration file in the home directory:</p><pre><code class="language-bash">bojana@7OfNine:~$ touch nginx/nginx.conf
</code></pre><p>And let&apos;s add these lines to the config file:</p><pre><code class="language-bash">events { }

http {

  server {
    listen 8888;

    location / {
     return 200 &quot; Hello from NGINX\\n&quot;;
    }
  }
}
</code></pre><p>To understand the configuration file, let&apos;s discuss a few of a nginx configuration terms. <strong>Context </strong>- sections withing the configuration (namely events, http, server in our file) where directives can be set for that given context. You can think of a context as a scope. <strong>Directives </strong>- specific configuration option (ex. listen 8888), that gets set in the configuration file, and consists of a name and a value.</p><p>To start nginx with given configuration issue a command:</p><pre><code class="language-bash">bojana@7OfNine:~$ nginx -c /home/bojana/nginx/nginx.conf
</code></pre><p>The -c argument is indicating that we will pass a configuration file, it&apos;s also worth noting that the path to the config file should be absolute. A good practice before reloading the nginx configuration is to verify if there is no syntax errors in the file itself (such as misplaced bracket or some misspelled directive or similar), kind of a dry run of an nginx configuration, to verify everything is correct (at least as far as syntax goes). To perform the test issue:</p><pre><code class="language-bash">bojana@7OfNine:~/nginx/node$ nginx -t -c /home/bojana/nginx/nginx.conf
nginx: the configuration file /home/bojana/nginx/nginx.conf syntax is ok
nginx: configuration file /home/bojana/nginx/nginx.conf test is successful</code></pre><p>We can see that everything is fine with the configuration file.<br>Let&apos;s test if the nginx is actually running by curl-ing the location:</p><pre><code class="language-bash">bojana@7OfNine:~$ curl http://localhost:8888
 Hello from NGINX
</code></pre><p>To demonstrate nginx working as a reverse proxy, let&apos;s now create simple php server. Prerequisite to create a php server is that you have phpXx-cli package installed with <strong>Xx </strong>being the appropriate version. On my machine I installed php7.4-cli:</p><pre><code class="language-bash">bojana@7OfNine:~$ sudo apt install php7.4-cli
</code></pre><p>After it&apos;s been installed, create a simple php server specifying host and a port like this:</p><pre><code class="language-bash">bojana@7OfNine:~$ php -S localhost:9999
[Mon Jun 27 14:50:07 2022] PHP 7.4.3 Development Server (&lt;http://localhost:9999&gt;) started
</code></pre><p>PHP&apos;s builtin server defaults to serving the directory it&apos;s run from, so in our case that nginx directory.</p><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2022/06/firefox_4oPEncDgCT.png" class="kg-image" alt="Reverse proxy explained (with nginx)" loading="lazy" width="505" height="159"></figure><p>We get a 404, because we didn&apos;t specify a path, nor we have index file in that root directory.</p><p>If we create the index.php file in that directory (for ex. with phpinfo function) and restart the php server from above. We will get the response.</p><pre><code class="language-php">&lt;?php phpinfo(); ?&gt;
</code></pre><pre><code class="language-bash">bojana@7OfNine:~/nginx$ php -S localhost:9999
[Mon Jun 27 14:58:07 2022] PHP 7.4.3 Development Server (&lt;http://localhost:9999&gt;) started
[Mon Jun 27 14:58:10 2022] 127.0.0.1:48410 Accepted
[Mon Jun 27 14:58:10 2022] 127.0.0.1:48410 [200]: GET /
</code></pre><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2022/06/firefox_NrJAVgJfmN.png" class="kg-image" alt="Reverse proxy explained (with nginx)" loading="lazy" width="869" height="315" srcset="https://bojana.dev/content/images/size/w600/2022/06/firefox_NrJAVgJfmN.png 600w, https://bojana.dev/content/images/2022/06/firefox_NrJAVgJfmN.png 869w" sizes="(min-width: 720px) 720px"></figure><p>For the sake of simplicity let&apos;s just serve a simple text file:</p><pre><code class="language-bash">bojana@7OfNine:~$ touch nginx/php_hello.txt
bojana@7OfNine:~$ echo &quot;Hello from PHP&quot; &gt; nginx/php_hello.txt
bojana@7OfNine:~$ php -S localhost:9999 nginx/php_hello.txt
[Mon Jun 27 15:06:39 2022] PHP 7.4.3 Development Server (http://localhost:9999) started
....

bojana@7OfNine:~$ curl http://localhost:9999
Hello from PHP
</code></pre><p>Let&apos;s now say we want to access this php server via our nginx server. (the one on port 8888), for example by accessing &#xA0;http://localhost:8888/php.</p><pre><code class="language-bash">events { }

http {

  server {
    listen 8888;

    location / {
     return 200 &quot; Hello from NGINX\\n&quot;;
    }

    location /php {
     proxy_pass &quot;http://localhost:9999/&quot;;
    }

  }
}
</code></pre><p>We added new location (/php), and with directive &quot;proxy_pass&quot; we forwarded request to the php server.</p><p>Now let&apos;s reload the nginx configuration:</p><pre><code class="language-bash">bojana@7OfNine:~$ sudo nginx -s reload
</code></pre><p>And let&apos;s test our reverse proxy by requesting that /php location :</p><pre><code class="language-bash">bojana@7OfNine:~$ curl http://localhost:8888/php
Hello from PHP
</code></pre><p>We get a response from a PHP server, but accessed and reverse proxied via our nginx server.</p><p>There is no restrictions to this proxied server being on the same system either. For instance if I add this location:</p><pre><code class="language-bash">location /blog {
      proxy_pass &quot;https://bojana.dev/&quot;;
 }
</code></pre><pre><code class="language-bash">bojana@7OfNine:~$ sudo nginx -s reload
bojana@7OfNine:~$ curl http://localhost:8888/blog
&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;

    &lt;title&gt;Bits and pieces&lt;/title&gt;
</code></pre><p>It is clear that you can use any kind of backend to sit behind the nginx.</p><p>Here is the example with a sample node/express app as an illustration.</p><pre><code class="language-bash"> location / {
     proxy_pass http://localhost:3000; # sample node app
    }</code></pre><pre><code class="language-bash">bojana@7OfNine:~/nginx/node$ node app.js
Example app listening on port 3000
</code></pre><pre><code class="language-bash">bojana@7OfNine:~$ curl http://localhost:8888
Hello World!
</code></pre><pre><code class="language-jsx">const express = require(&apos;express&apos;)
const app = express()
const port = 3000

app.get(&apos;/&apos;, (req, res) =&gt; {
  res.send(&apos;Hello World!&apos;)
})

app.listen(port, () =&gt; {
  console.log(`Example app listening on port ${port}`)
})
</code></pre><p>I also have a sample asp.net core app running locally on Kestrel server. If I add this entry to nginx.conf:</p><pre><code class="language-bash">location / {
         proxy_pass         http://localhost:5000;
}
</code></pre><pre><code class="language-jsx">bojana@7OfNine:~/aspnet-test$ /usr/bin/dotnet /home/bojana/aspnet-test/bin/Release/net6.0/aspnet-test.dll
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: &lt;http://localhost:5000&gt;
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: &lt;https://localhost:5001&gt;
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
      Content root path: /home/bojana/aspnet-test/
</code></pre><p>I can access it on localhost:8888</p><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2022/06/msedge_zuXVfcPEAw.png" class="kg-image" alt="Reverse proxy explained (with nginx)" loading="lazy" width="1187" height="366" srcset="https://bojana.dev/content/images/size/w600/2022/06/msedge_zuXVfcPEAw.png 600w, https://bojana.dev/content/images/size/w1000/2022/06/msedge_zuXVfcPEAw.png 1000w, https://bojana.dev/content/images/2022/06/msedge_zuXVfcPEAw.png 1187w" sizes="(min-width: 720px) 720px"></figure><p>So we see that it doesn&apos;t really matter what sits behind your nginx proxy server, php backend, node backend, dotnet backend... You can utilize it easily as a reverse proxy.</p>]]></content:encoded></item><item><title><![CDATA[Docker networks]]></title><description><![CDATA[<!--kg-card-begin: html-->
<p>During the installation, Docker creates three different networking options.<br>You can list them with <strong>docker network ls</strong>:</p>



<pre class="wp-block-code"><code>bojana@linux:~$ docker network ls
NETWORK ID NAME DRIVER SCOPE
0c872e6d6453 bridge bridge local
10826dd62a8b host host local
cab99af2344e none null local</code></pre>



<p>By default, bridge mode is selected, and containers reside on a</p>]]></description><link>https://bojana.dev/docker-networks/</link><guid isPermaLink="false">622b3e10492941bbdf320323</guid><category><![CDATA[docker]]></category><category><![CDATA[network]]></category><dc:creator><![CDATA[Bojana Dejanovic]]></dc:creator><pubDate>Thu, 09 Sep 2021 09:17:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1551703599-6b3e8379aa8c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDR8fG5ldHdvcmslMjBzd2l0Y2h8ZW58MHx8fHwxNjQ3MDE4Mjc3&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: html-->
<img src="https://images.unsplash.com/photo-1551703599-6b3e8379aa8c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDR8fG5ldHdvcmslMjBzd2l0Y2h8ZW58MHx8fHwxNjQ3MDE4Mjc3&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Docker networks"><p>During the installation, Docker creates three different networking options.<br>You can list them with <strong>docker network ls</strong>:</p>



<pre class="wp-block-code"><code>bojana@linux:~$ docker network ls
NETWORK ID NAME DRIVER SCOPE
0c872e6d6453 bridge bridge local
10826dd62a8b host host local
cab99af2344e none null local</code></pre>



<p>By default, bridge mode is selected, and containers reside on a private namespaced network within the host.<br>In the previous post, where we explored basic docker commands, we use <strong>docker run -p</strong>, to map a port from the host. This makes Docker create <strong>iptables</strong> rules that route traffic from the the host to the container.</p>



<p>&#x201C;None&#x201D; entry in the above list indicates that no configuration should be performed by Docker whatsoever. It is intended for custom networking requirements.<br>If we want explicitly to select container&#x2019;s network, we pass <strong>&#x2013;net</strong> to <strong>docker run</strong>.</p>



<p>A bridge is a Linux kernel feature that connects two network segments.<br>When you installed Docker, it quietly created a bridge called <strong>docker0</strong> on the host.<br>You can verify that by issuing command <strong>ip addr</strong> show. Here is output on my machine:</p>



<hr class="wp-block-separator">



<pre class="wp-block-code"><code>bojana@linux:~$ ip addr show
1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 96:00:00:b1:5e:b3 brd ff:ff:ff:ff:ff:ff
inet 188.34.194.63/32 scope global dynamic eth0
valid_lft 74220sec preferred_lft 74220sec
inet6 2a01:4f8:1c1c:a675::1/64 scope global
valid_lft forever preferred_lft forever
inet6 fe80::9400:ff:feb1:5eb3/64 scope link
valid_lft forever preferred_lft forever
3: docker0: mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:b6:9c:25:22 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:b6ff:fe9c:2522/64 scope link
valid_lft forever preferred_lft forever
5: veth51c3665@if4: mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 96:4d:57:fa:c0:99 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::944d:57ff:fefa:c099/64 scope link
valid_lft forever preferred_lft forever</code></pre>



<hr class="wp-block-separator">



<p>docker0 is the virtual Ethernet bridge that uses 172.17.0.1/16 range.<br>veth51c3665 is the host side of the virtual interface pair that connect the container to the bridged network.</p>



<p>As we said in the previous article, by default when you launch a container, there are no published ports, so container would not be visible from outside the docker host, still we can access it from the docker host itself.</p>



<p>We will use <strong>nginxdemo/hello</strong> image in this example, because it contains the simple web server that prints out the IP address of the container, so a view form the inside, along with other things.</p>



<pre class="wp-block-code"><code>bojana@linux:~$ docker run -d nginxdemos/hello</code></pre>



<p>Let&#x2019;s connect to this running container and see what IP address it got assigned.<br>To access a shell of a running container we use:</p>



<pre class="wp-block-code"><code>bojana@linux:~$ docker exec -it jovial_wu /bin/ash</code></pre>



<p><em>Note: jovial_wu is a random container name assigned when creating it, because we didn&#x2019;t specify any, check yours with docker ps command</em></p>



<p>ip addr show on the container command line reveals:</p>



<hr class="wp-block-separator">



<pre class="wp-block-code"><code>/ # ip addr show
1: lo: &lt;LOOPBACK,UP,LOWER_UP&gt; mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
8: eth0@if9: &lt;BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN&gt; mtu 1500 qdisc noqueue state UP 
    link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever</code></pre>



<p>We see that the address is from the above mentioned docker0 range.<br>If we paste this address to the browser, we should get nginx demo page:<br></p>



<figure class="wp-block-image size-large"><img src="https://bojana.dev/wp-content/uploads/2021/09/ngnix1.png" alt="Docker networks" class="wp-image-133"></figure>



<p>It shows us what it sees from the inside of container, such as IP on the bridge plus the servername which is a linux hostname of the container (container id)</p>



<h3>How can containers communicate with each other on the bridge?</h3>



<p>Let&#x2019;s create a second instance of the container:<br><code>docker run -d nginxdemos/hello</code></p>



<p>And connect to it:<br><code>docker exec -it inspiring_mcnulty2 /bin/ash</code></p>



<p>If we check the IP we see again that it&#x2019;s from the bridge range:</p>



<pre class="wp-block-code"><code>/ # ip addr show
1: lo: &lt;LOOPBACK,UP,LOWER_UP&gt; mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
11: eth0@if12: &lt;BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN&gt; mtu 1500 qdisc noqueue state UP 
    link/ether 02:42:ac:11:00:04 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.4/16 brd 172.17.255.255 scope glo</code></pre>



<p>We can also ping the previously created container:</p>



<pre class="wp-block-code"><code>/ # ping 172.17.0.3
PING 172.17.0.3 (172.17.0.3): 56 data bytes
64 bytes from 172.17.0.3: seq=0 ttl=64 time=0.204 ms
64 bytes from 172.17.0.3: seq=1 ttl=64 time=0.189 ms
64 bytes from 172.17.0.3: seq=2 ttl=64 time=0.181 ms
64 bytes from 172.17.0.3: seq=3 ttl=64 time=0.192 ms
^C
--- 172.17.0.3 ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max = 0.181/0.191/0.204 ms</code></pre>



<p>This means we do have IP connectivity between two containers on the same bridge.<br>If we issue an <strong>ip route</strong> command, we can see there is a default route via the Docker host&#x2019;s IP<br>on the bridge, which acts as a gateway.<br>We can also see that we have internet access from within the container, and the name resolution works:</p>



<pre class="wp-block-code"><code>/ # ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: seq=0 ttl=111 time=18.209 ms
64 bytes from 8.8.8.8: seq=1 ttl=111 time=18.524 ms
64 bytes from 8.8.8.8: seq=2 ttl=111 time=18.116 ms
64 bytes from 8.8.8.8: seq=3 ttl=111 time=18.414 ms
^C
--- 8.8.8.8 ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max = 18.116/18.315/18.524 ms
/ # ping google.com
PING google.com (142.250.185.110): 56 data bytes
64 bytes from 142.250.185.110: seq=0 ttl=112 time=28.885 ms
64 bytes from 142.250.185.110: seq=1 ttl=112 time=28.785 ms
64 bytes from 142.250.185.110: seq=2 ttl=112 time=29.383 ms
64 bytes from 142.250.185.110: seq=3 ttl=112 time=28.670 ms
^C
--- google.com ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max = 28.670/28.930/29.383 ms</code></pre>



<p>In the default bridge configuration, all containers can communicate with one another because they are all on the same virtual network. However, you can create additional network namespaces to isolate containers from one another.</p>



<p>We said that the most common way of making a docker container visible from the outside is port mapping. </p>



<p>Let&#x2019;s create another container this time specifying port mapping:</p>



<p><code>bojana@linux:~$ docker run -d nginxdemo/hello -p 81:80</code></p>



<p>One thing we should point out though is the fact that the name resolution between docker containers doesn&#x2019;t work.<br>If we would attach to the above created container and tried to ping the first or the second container we created by it&#x2019;s hostname, we wouldn&#x2019;t be able to do so:</p>



<pre class="wp-block-code"><code>/ # hostname
0888f0c68423
/ # ping 167bcc074170
ping: bad address &apos;167bcc074170&apos;</code></pre>



<h2>User defined bridges</h2>



<p>In principle, host name resolution is possible between the containers, just not on the default bridge.<br>Which means we have to create our own.<br>Use the docker network create command to create a user-defined bridge network:<br><code>bojana@linux:~$ docker network create --driver=bridge --subnet=172.172.0.0/24 --ip-range=172.172.0.128/25 --gateway=172.172.0.1 my-br0</code></p>



<p>You can go ahead and stop/remove all the containers we created so far so we have a clean slate.<br>Now, let&#x2019;s create a new container that uses our newly created bridge:<br><code>bojana@linux:~$ docker run -d --name my-nginx --network my-br0 --hostname my-nginx nginxdemos/hello</code></p>



<p>And another instance with different name and hostname:<br><code>bojana@linux:~$ docker run -d --name my-nginx2 --network my-br0 --hostname my-nginx2 nginxdemos/hello</code></p>



<p>Now, if we use the docker inspect to check the network settings of newly created container:</p>



<pre class="wp-block-code"><code>bojana@linux:~$ docker inspect -f &apos;{{ json .NetworkSettings.Networks }}&apos; my-nginx
{&quot;my-br0&quot;:{&quot;IPAMConfig&quot;:null,&quot;Links&quot;:null,&quot;Aliases&quot;:[&quot;b91bde6aa527&quot;,&quot;my-nginx&quot;],&quot;NetworkID&quot;:&quot;f9e797b2ab8826342ea8343b8bebe8e1459eea114aa0b015f56b01f90fe39ff8&quot;,&quot;EndpointID&quot;:&quot;a1af70992c90967ebca917eeebaa67b4e7bc23bdfb2f352f283faaee223d4fc0&quot;,&quot;Gateway&quot;:&quot;172.172.0.1&quot;,&quot;IPAddress&quot;:&quot;172.172.0.128&quot;,&quot;IPPrefixLen&quot;:24,&quot;IPv6Gateway&quot;:&quot;&quot;,&quot;GlobalIPv6Address&quot;:&quot;&quot;,&quot;GlobalIPv6PrefixLen&quot;:0,&quot;MacAddress&quot;:&quot;02:42:ac:ac:00:80&quot;,&quot;DriverOpts&quot;:null}}</code></pre>



<p>We see that our network settings were applied and containers have assigned, or if you will, joined our defined network. Now, if we try to ping one container from the other by it&#x2019;s hostname, it will work:</p>



<pre class="wp-block-code"><code>
/ # ping my-nginx2
PING my-nginx2 (172.172.0.129): 56 data bytes
64 bytes from 172.172.0.129: seq=0 ttl=64 time=0.166 ms
64 bytes from 172.172.0.129: seq=1 ttl=64 time=0.149 ms
64 bytes from 172.172.0.129: seq=2 ttl=64 time=0.211 ms
64 bytes from 172.172.0.129: seq=3 ttl=64 time=0.134 ms
^C
--- my-nginx2 ping statistics ---
And also if we ping by hostname another container:
/ # ping my-nginx
PING my-nginx (172.172.0.128): 56 data bytes
64 bytes from 172.172.0.128: seq=0 ttl=64 time=0.342 ms
64 bytes from 172.172.0.128: seq=1 ttl=64 time=0.134 ms
64 bytes from 172.172.0.128: seq=2 ttl=64 time=0.191 ms
64 bytes from 172.172.0.128: seq=3 ttl=64 time=0.128 ms
^C
--- my-nginx ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max = 0.128/0.198/0.342 ms</code></pre>



<p>The conclusion is that Docker does provide name resolution between containers over a self-defined bridge.</p>



<p><em><strong>Credits: </strong>A lot of content is borrowed from this <a href="https://www.youtube.com/watch?v=OmZdItNjWNY&amp;ab_channel=OneMarcFifty&amp;ref=bojana.dev" data-type="URL" data-id="https://www.youtube.com/watch?v=OmZdItNjWNY&amp;ab_channel=OneMarcFifty">excellent video</a> by <a href="https://www.youtube.com/channel/UCG5Ph9Mm6UEQLJJ-kGIC2AQ?ref=bojana.dev">OneMarcFifty</a> on youtube. Go and check the channel, it has some really interesting content, presented in a really nice and clean way. For this post, I tried to use just docker commands without having to use portainer and add some additional notes. </em></p>
<!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[Docker - up and running]]></title><description><![CDATA[<!--kg-card-begin: html-->
<ul><li><a href="#InstallDocker" data-type="internal" data-id="#InstallDocker">Installing Docker</a></li><li><a href="#DockerEngine" data-type="internal" data-id="#DockerEngine">Docker &#x2013; open source container engine</a></li><li><a href="#BasicArch" data-type="internal" data-id="#BasicArch">Basic architecture</a></li><li><a href="#Commands" data-type="internal" data-id="#Commands">Basic Docker commands</a></li></ul>



<h3 id="InstallDocker">Installing Docker (Linux, Windows, Mac)</h3>



<p>I will not go into much details about how to install docker environment on your desired OS. There are a lot of quality articles out there that will help you with that.</p>]]></description><link>https://bojana.dev/docker-up-and-running/</link><guid isPermaLink="false">622b3e10492941bbdf320322</guid><category><![CDATA[containers]]></category><category><![CDATA[docker]]></category><category><![CDATA[dockerd]]></category><dc:creator><![CDATA[Bojana Dejanovic]]></dc:creator><pubDate>Mon, 06 Sep 2021 14:42:33 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1583686298564-46fbffda0707?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDl8fGNvbnRhaW5lcnN8ZW58MHx8fHwxNjQ3MDE4MzM5&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: html-->
<ul><li><a href="#InstallDocker" data-type="internal" data-id="#InstallDocker">Installing Docker</a></li><li><a href="#DockerEngine" data-type="internal" data-id="#DockerEngine">Docker &#x2013; open source container engine</a></li><li><a href="#BasicArch" data-type="internal" data-id="#BasicArch">Basic architecture</a></li><li><a href="#Commands" data-type="internal" data-id="#Commands">Basic Docker commands</a></li></ul>



<h3 id="InstallDocker">Installing Docker (Linux, Windows, Mac)</h3>



<img src="https://images.unsplash.com/photo-1583686298564-46fbffda0707?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDl8fGNvbnRhaW5lcnN8ZW58MHx8fHwxNjQ3MDE4MzM5&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Docker - up and running"><p>I will not go into much details about how to install docker environment on your desired OS. There are a lot of quality articles out there that will help you with that. Official docker documentation seems to be a good option, but it&#x2019;s up to you. Here are official guides for installing docker engine (desktop) on <a href="https://docs.docker.com/engine/install/ubuntu/?ref=bojana.dev" data-type="URL" data-id="https://docs.docker.com/engine/install/ubuntu/">Ubuntu</a>, <a href="https://docs.docker.com/desktop/windows/install/?ref=bojana.dev" data-type="URL" data-id="https://docs.docker.com/desktop/windows/install/">Windows</a> and <a href="https://docs.docker.com/desktop/mac/install/?ref=bojana.dev">MacOS</a>.</p>



<h3 id="DockerEngine">Docker &#x2013; open source container engine</h3>



<p>In the <a rel="noreferrer noopener" href="https://bojana.dev/containerization/" target="_blank">last article</a> we talked about how containerization has been a big trend in the IT world for the past few years, and it continues to be as we speak. Although it&#x2019;s kind of a synonym for containers these days, it&#x2019;s worth noting that Docker isn&#x2019;t the only container engine or solution for managing containers out there. Container engines such as <a href="https://linuxcontainers.org/?ref=bojana.dev#LXC">LXC</a>, <a href="https://cloud.redhat.com/learn/topics/rkt?ref=bojana.dev">RKT</a>, <a href="https://github.com/oracle/railcar?ref=bojana.dev">Railcar</a>, <a href="https://docs.openshift.com/container-platform/3.11/crio/crio_runtime.html?ref=bojana.dev">CRI-O</a> and others also exist. We also mentioned OCI &#x2013; Open Container Initiative, a consortium created for purpose of creating open industry standards around container formats and runtimes.</p>



<h3 id="BasicArch">Basic architecture</h3>



<p>Docker uses a client-server architecture.<br><strong>docker</strong> is an executable, a client, that talks to <strong>dockerd</strong>, a daemon process, that implements container and images operations. These two can run on the same system or the client can connect to a remote docker daemon.The client communicates with docker daemon over UNIX sockets, via REST API or over a network interface. Another example of docker client is docker compose.</p>



<figure class="wp-block-image size-large"><img src="https://docs.docker.com/engine/images/architecture.svg" alt="Docker - up and running"></figure>



<p>As said above, docker client is the one who interfaces with docker daemon, by sending commands over the command line.</p>



<pre class="wp-block-code"><code>bojana@linux:~$ docker info
Client:
 Context:    default
 Debug Mode: false
 Plugins:
  app: Docker App (Docker Inc., v0.9.1-beta3)
  buildx: Build with BuildKit (Docker Inc., v0.6.1-docker)
  scan: Docker Scan (Docker Inc., v0.8.0)

Server:
 Containers: 10
  Running: 2
  Paused: 0
  Stopped: 8
 Images: 5
 Server Version: 20.10.8
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Native Overlay Diff: true
  userxattr: false
 Logging Driver: json-file
 Cgroup Driver: cgroupfs
 Cgroup Version: 1
...........</code></pre>



<p class="has-text-align-left">An <strong>image</strong> is a template for the container. Typically, images are based on some Linux distribution with some additional customizations (these are called <em>parent images</em>, in you <strong>Dockerfile</strong> it refers to the content of FROM directive). This is because Linux distributions define a complete operating environment. Image doesn&#x2019;t have to be based on some Linux distribution. For example, there is &#x201C;scratch&#x201D; image, an explicitly empty image, intended to be a basis for other images.</p>



<p>You may build an image which is based on the debian image, but installs .NET Core SDK and your application, as well as the configuration details needed to make your application run.</p>



<h3 id="Commands">Basic Docker commands</h3>



<p>To create your own image, you create a file name Dockerfile, with simple directives on how to create the image and run it.</p>



<p>Typically images are pulled from some private or public docker registry (such as Docker Hub).<br>To pull the image from the registry we use command <strong>docker pull</strong>:</p>



<pre class="wp-block-code"><code>bojana@linux:~$ docker pull debian
Using default tag: latest
latest: Pulling from library/debian
955615a668ce: Pull complete 
Digest: sha256:08db48d59c0a91afb802ebafc921be3154e200c452e4d0b19634b426b03e0e25
Status: Downloaded newer image for debian:latest
docker.io/library/debian:latest</code></pre>



<p>The hex strings are the layers of the <a href="https://en.wikipedia.org/wiki/UnionFS?ref=bojana.dev" data-type="URL" data-id="https://en.wikipedia.org/wiki/UnionFS"><strong>union file system</strong>.</a><br>Each instruction in a Dockerfile creates a layer in the image. When you change the Dockerfile and rebuild the image, only those layers which have changed are rebuilt. This is part of what makes Docker so lightweight and fast.</p>



<p>In the example above we used docker pull command to retrieve a docker image for Debian Linux distribution. Because we didn&#x2019;t requested specific version or a tag it downloaded &#x201C;latest&#x201D; tag by default. On the docker hub web page you can find supported tags and respective Dockerfile links for each image of interest. <a href="https://hub.docker.com/_/debian?ref=bojana.dev">https://hub.docker.com/_/debian </a></p>



<p>To inspect all locally available images, we use command <strong>docker images</strong></p>



<pre class="wp-block-code"><code>bojana@linux:~$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
postgres latest 293e4ed402ba 3 months ago 315MB
jenkins/jenkins lts de181f8c70e8 4 months ago 569MB
portainer/portainer latest 580c0e4e98b0 5 months ago 79.1MB
hello-world latest d1165f221234 6 months ago 13.3kB</code></pre>



<p>When we have an image, it very easy to create a running <strong>container</strong> out of it.</p>



<pre class="wp-block-code"><code>bojana@linux:~$ docker run -p 80:80 --detach --name web nginx:latest
Unable to find image &apos;nginx:latest&apos; locally
latest: Pulling from library/nginx
a330b6cecb98: Pull complete 
5ef80e6f29b5: Pull complete 
f699b0db74e3: Pull complete 
0f701a34c55e: Pull complete 
3229dce7b89c: Pull complete 
ddb78cb2d047: Pull complete 
Digest: sha256:a05b0cdd4fc1be3b224ba9662ebdf98fe44c09c0c9215b45f84344c12867002e
Status: Downloaded newer image for nginx:latest
aab3ce850b9a2a6a8f2469b8773362133f95839406ce276a6d04d2bfc2b7f67f</code></pre>



<hr class="wp-block-separator">



<p>We didn&#x2019;t have the &#x201C;nginx&#x201D; image locally, so Docker had to pull it from the registry.<br>When we run this command, docker will install nginx:latest from the nginx repository hosted on dockerhub and run the software. After that one line of seemingly random characters will be displayed.<br>This is the identifier of the container we just created.<br>It seems nothing else happened, because we used <strong>&#x2013;detach </strong>option and started the program in the background. Detached &#x2013; means detached from the terminal. Typically, server software runs in detached mode because we don&#x2019;t want to depend on terminal session.</p>



<p>We use <strong>docker ps</strong> to list all running containers:</p>



<pre class="wp-block-code"><code>bojana@linux:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8b101295f67e nginx:latest &quot;/docker-entrypoint.&#x2026;&quot; 3 minutes ago Up 3 minutes 80/tcp</code></pre>



<p>By default, when you create or run a container using docker create or docker run, it does not publish any of its ports to the outside world. Using the flag <strong>-p</strong> or <strong>&#x2013;publish</strong> in the docker run command, will create a firewall rule which maps a container port to a port on the Docker host to the outside world.</p>



<p>With NGINX running in the container and port 80 mapped from the host, we can make HTTP requests to the container with curl. NGINX serves a generic HTML landing page by default.</p>



<pre class="wp-block-code"><code>bojana@linux$ curl localhost
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Welcome to nginx!&lt;/title&gt;
...</code></pre>



<p>If we want to see STDOUT from the container we use <strong>docker logs</strong> command, in this case nginx access log.</p>



<pre class="wp-block-code"><code>bojana@linux:~$ docker logs web
172.17.0.1 - - [06/Sep/2021:13:34:47 +0000] &quot;GET / HTTP/1.1&quot; 200 612 &quot;-&quot; &quot;curl/7.68.0&quot; &quot;-&quot;</code></pre>



<p>If we add -f flag we can examine a real-time stream of container&#x2019;s output. To debug or troubleshoot the running container we could also start an interactive shell </p>



<pre class="wp-block-code"><code>bojana@linux:~$ docker exec -it nginx bash
root@aab3ce850b9a:/# apt-get update &amp;&amp; apt-get -y install procps
root@aab3ce850b9a:/# ps ax
    PID TTY      STAT   TIME COMMAND
      1 ?        Ss     0:00 nginx: master process nginx -g daemon off;
     32 ?        S      0:00 nginx: worker process
     33 ?        S      0:00 nginx: worker process
     34 ?        S      0:00 nginx: worker process
     35 ?        S      0:00 nginx: worker process
     43 pts/0    Ss     0:00 bash
    372 pts/0    R+     0:00 ps ax</code></pre>



<p>The process list reveals the nginx master daemon, an nginx worker, and our bash shell. When we exit the shell created with docker exec, the container continues to run.</p>



<p>We can start and stop the container:</p>



<pre class="wp-block-code"><code>bojana@linux:~$ docker stop nginx
nginx
bojana@linux:~$ docker ps
CONTAINER ID   IMAGE       COMMAND        CREATED      STATUS       PORTS   
bojana@linux:~$ docker start nginx
nginx
bojana@linux:~$ docker ps
CONTAINER ID   IMAGE       COMMAND      CREATED        STATUS       PORTS                                                                                  NAMES
aab3ce850b9a   nginx:latest &quot;/docker-entrypoint.&#x2026;&quot;   52 minutes ago   Up 5 seconds            0.0.0.0:80-&gt;80/tcp, :::80-&gt;80/tcp  </code></pre>



<p>When containers are stopped/exited they remain in the system in the dormant state. We can see all the containers on the local system  (running/exited) by supplying <strong>-a</strong> flag to the <strong>docker ps</strong> command:</p>



<pre class="wp-block-code"><code>bojana@linux:~$ docker ps -a
CONTAINER ID   IMAGE                    COMMAND                  CREATED             STATUS                         PORTS                                                                                  NAMES
aab3ce850b9a   nginx:latest             &quot;/docker-entrypoint.&#x2026;&quot;   54 minutes ago      Up 2 minutes                   0.0.0.0:80-&gt;80/tcp, :::80-&gt;80/tcp                                                      web
65787d8734ab   debian                   &quot;/bin/echo &apos;Hello Wo&#x2026;&quot;   About an hour ago   Exited (0) About an hour ago                                                                                          strange_napier
33d2c89cdc09   nginxdemos/hello         &quot;/docker-entrypoint.&#x2026;&quot;   7 days ago          Exited (0) 7 days ago    </code></pre>



<p>It&#x2019;s not particularly bad to keep them lying around, but it&#x2019;s considered bad practice (due to possible name collisions with new containers). We stop and remove the container with:</p>



<pre class="wp-block-code"><code>bojana@linux:~$ docker stop nginx &amp;&amp; docker rm nginx
nginx
nginx</code></pre>



<p>And if we want to remove the image:</p>



<pre class="wp-block-code"><code>bojana@linux:~$ docker rmi nginx
Untagged: nginx:latest
Untagged: nginx@sha256:a05b0cdd4fc1be3b224ba9662ebdf98fe44c09c0c9215b45f84344c12867002e
Deleted: sha256:822b7ec2aaf2122b8f80f9c7f45ca62ea3379bf33af4e042b67aafbf6eac1941
Deleted: sha256:47dec9bde9e483e6265a6f52ab1e726724927e2e9d2ac358fdf58fbfcd6cf418
Deleted: sha256:7920a27f48f198550d59f64681b99441bbc3d2ce4778a855ce1ef9bafc96ae69
Deleted: sha256:a3c5a94eb1ea071c73dcea1969e0b71beea445d3b9d0735eaf6715d8e351434c
Deleted: sha256:e73eb58ed241e67a7a2c8589dde85eb72811eac1eb4cf3b586e40d2b9cc9d0c1
Deleted: sha256:b5d976dc9b0fa380affe1f6a17df18f02ab7debec2d35a0407fb863338591ed7
Deleted: sha256:d000633a56813933cb0ac5ee3246cf7a4c0205db6290018a169d7cb096581046</code></pre>



<p>These are some basic docker commands to get you up and running when it comes to dealing with images and containers. There are a lot of other docker commands related to networking, volumes, building images etc. that we will be tackling in some of the upcoming blog posts. </p>
<!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[Containerization]]></title><description><![CDATA[<p>In the last article, I talked about what <a href="https://bojana.dev/virtualization/">virtualization</a> is and how important the concept and technology is in the whole cloud computing paradigm.</p><p>Now I want to talk about <em>containerization </em>&#x2013; a different approach to isolation that does not use a hypervisor, but instead it relies on a specific</p>]]></description><link>https://bojana.dev/containerization/</link><guid isPermaLink="false">622b3e10492941bbdf320321</guid><category><![CDATA[cgroups]]></category><category><![CDATA[cloud]]></category><category><![CDATA[containerization]]></category><category><![CDATA[containers]]></category><category><![CDATA[docker]]></category><category><![CDATA[lxc]]></category><category><![CDATA[namespaces]]></category><dc:creator><![CDATA[Bojana Dejanovic]]></dc:creator><pubDate>Mon, 14 Jun 2021 11:08:50 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1578575437130-527eed3abbec?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDI5fHxjb250YWluZXJzfGVufDB8fHx8MTY0NzAxODMzOQ&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1578575437130-527eed3abbec?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDI5fHxjb250YWluZXJzfGVufDB8fHx8MTY0NzAxODMzOQ&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Containerization"><p>In the last article, I talked about what <a href="https://bojana.dev/virtualization/">virtualization</a> is and how important the concept and technology is in the whole cloud computing paradigm.</p><p>Now I want to talk about <em>containerization </em>&#x2013; a different approach to isolation that does not use a hypervisor, but instead it relies on a specific kernel features that isolate processes from the rest of the system.</p><p>So in short <strong>containerization</strong> is a form of operating system virtualization, where we have applications running in isolated user spaces called <strong>containers.</strong></p><p>Each process &#x201C;container&#x201D; or &#x201C;jail&#x201D; has a <strong>private root file system</strong> and <strong>process namespace</strong>. <br>While sharing the kernel and other services of the underlying OS, they cannot access files or resources outside of their container.<br>In essence, containers are fully packaged and portable computing environments.</p><figure class="kg-card kg-image-card kg-card-hascaption"><a href="https://www.amazon.com/UNIX-Linux-System-Administration-Handbook/dp/0134277554?ref=bojana.dev"><img src="https://bojana.dev/content/images/2022/03/firefox_gvtJnWxA61.png" class="kg-image" alt="Containerization" loading="lazy" width="305" height="283"></a><figcaption><a href="https://www.amazon.com/UNIX-Linux-System-Administration-Handbook/dp/0134277554?ref=bojana.dev">Unix And Linux System Administration Handbook</a></figcaption></figure><p>We said that this type of virtualization does not require hypervisor, and because there is no need for the virtualization of the hardware, resource overhead for this type of virtualization is low.</p><p>This means container start up time is pretty quick, or if you will start time is pretty short. Creation of a container has the overhead of creating a Linux process, which can be of the order of the miliseconds, while creating a VM can take seconds.</p><p>The containerized application can be run on various types of infrastructure&#x2014;on bare metal, within VMs, and in the cloud&#x2014;without needing to refactor it for each environment</p><h2 id="how-then-containers-relate-to-vms">How then containers relate to VMs?</h2><p>Both are portable, isolated, execution environments, and both look and act as a full operating systems.<br>Unlike VM, which has an OS kernel, drivers to interact with hardware etc. a container is merely a mimic of an operating system. Container itself is abstracted away from the host OS, with only limited access to underlying resources &#x2013; we can say it is a lightweight VM.</p><p>The containers-on-VMs architecture is standard for containerized applications that need to run on public cloud instances.</p><h2 id="how-containerization-actually-works">How containerization actually works?</h2><p>We said that containerization relies on specific kernel features, but which features are those? <br>Containerization as we know it evolved from <strong>cgroups</strong>, a feature for isolating and controlling resource usage (e.g., how much CPU and RAM and how many threads a given process can access) within the Linux kernel.<br>cgroups were originally developed by Paul Menage and Rohit Seth of Google, and their first features were merged into Linux 2.6.24. Cgroups became Linux containers (<a href="https://linuxcontainers.org/lxc/introduction/?ref=bojana.dev">LXC</a>), with more advanced features for namespace isolation of components, such as routing tables and file systems.</p><p><strong>Namespaces </strong>are a kernel mechanism for limiting the visibility that a group of processes has of the rest of a system. For example you can limit visibility to certain process trees, network interfaces, user IDs or filesystem mounts. namespaces were originally developed by Eric Biederman, and the final major namespace was merged into Linux 3.8.</p><p>Since kernel version 5.6, there are 8 kinds of namespaces. Namespace functionality is the same across all kinds: each process is associated with a namespace and can only see or use the resources associated with that namespace, and descendant namespaces where applicable. This way each process (or process group thereof) can have a unique view on the resources.</p><pre><code>bojana@linux:~$ sudo lsns -p 270
        NS TYPE   NPROCS PID USER COMMAND
4026531835 cgroup    130   1 root /sbin/init
4026531836 pid       126   1 root /sbin/init
4026531837 user      130   1 root /sbin/init
4026531838 uts       123   1 root /sbin/init
4026531839 ipc       126   1 root /sbin/init
4026531840 mnt       114   1 root /sbin/init
4026531992 net       126   1 root /sbin/init</code></pre><p>In Linux, <code>lsns</code> lists information about all the currently accessible namespaces</p><ul><li>cgroup</li><li>mnt (mount points, filesystems)</li><li>pid (processes)</li><li>net (network stack)</li><li>ipc (System V IPC)</li><li>uts (hostname)</li><li>user (UIDs)</li><li>time namespace</li></ul><p>Modern containers evolved from these 2 kernel features, and LXC served as a basis for <strong><a href="https://www.docker.com/?ref=bojana.dev">Docker</a></strong> launched in 2013. In it&#x2019;s early years it was based on using LXC, but later developed its own lib instead.</p><h2 id="docker">Docker</h2><figure class="kg-card kg-image-card"><img src="https://bojana.dev/content/images/2022/03/Moby-logo.png" class="kg-image" alt="Containerization" loading="lazy" width="601" height="431" srcset="https://bojana.dev/content/images/size/w600/2022/03/Moby-logo.png 600w, https://bojana.dev/content/images/2022/03/Moby-logo.png 601w"></figure><p>For most people nowadays when you say &#x201C;container&#x201D; Docker is the first association, but we see that the containerization technology is not that new (cgroups dating back to 2008). However, Docker expansion can be contributed to the set of tools it introduced taking advantage of already existing containerization technology.</p><p>Because of the rapid evolvement of docker tools, and new versions being incompatible with existing deployments, to counter this Docker Inc. became one of the founder member of <a href="https://opencontainers.org/?ref=bojana.dev">Open Container Initiative</a>, <em> a consortium whose mission is to guide the growth of container technology in a healthily competitive direction that fosters standards and collaboration.</em></p><p>We will be talking about docker architecture and docker as a container engine in one of the following articles.</p>]]></content:encoded></item><item><title><![CDATA[Virtualization]]></title><description><![CDATA[<p>&#x2026;or what enables the cloud?</p><ul><li><a>Hypervisor</a></li><li><a>Full virtualization</a></li><li><a>Paravir</a><a href="http://paravirtualization/?ref=bojana.dev">t</a><a href="https://bojana.dev/wp-admin/post.php?post=44&amp;action=edit#paravirtualization">ualization</a></li><li><a>Hardware assisted virtualization</a></li><li><a href="https://bojana.dev/wp-admin/post.php?post=44&amp;action=edit#type1vstype2">Type </a><a href="http://type1vstype2/?ref=bojana.dev">1</a><a> vs Type 2</a></li></ul><p>The term &#x201C;virtualization&#x201D; is an overloaded term that can describe many things, this is due to the way how technology evolved (competing vendors worked independently without benefit of</p>]]></description><link>https://bojana.dev/virtualization/</link><guid isPermaLink="false">622b3e10492941bbdf320320</guid><category><![CDATA[cloud]]></category><category><![CDATA[containerization]]></category><category><![CDATA[hypervisors]]></category><category><![CDATA[virtualization]]></category><dc:creator><![CDATA[Bojana Dejanovic]]></dc:creator><pubDate>Thu, 11 Mar 2021 14:07:13 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1517483000871-1dbf64a6e1c6?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDl8fGNsb3VkJTIwY29tcHV0aW5nfGVufDB8fHx8MTY0NzAxODQyOA&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1517483000871-1dbf64a6e1c6?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDl8fGNsb3VkJTIwY29tcHV0aW5nfGVufDB8fHx8MTY0NzAxODQyOA&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Virtualization"><p>&#x2026;or what enables the cloud?</p><ul><li><a>Hypervisor</a></li><li><a>Full virtualization</a></li><li><a>Paravir</a><a href="http://paravirtualization/?ref=bojana.dev">t</a><a href="https://bojana.dev/wp-admin/post.php?post=44&amp;action=edit#paravirtualization">ualization</a></li><li><a>Hardware assisted virtualization</a></li><li><a href="https://bojana.dev/wp-admin/post.php?post=44&amp;action=edit#type1vstype2">Type </a><a href="http://type1vstype2/?ref=bojana.dev">1</a><a> vs Type 2</a></li></ul><p>The term &#x201C;virtualization&#x201D; is an overloaded term that can describe many things, this is due to the way how technology evolved (competing vendors worked independently without benefit of standards).</p><p>But what virtualization really is and where it came from?</p><p>In the simple words, it&#x2019;s the technology that makes it possible to run multiple operating systems (concurrently) on the same physical hardware. Virtualization software parcels out CPU, memory, and I/O resources, dynamically allocating their use among several &#x201C;guest&#x201D; operating systems and resolving resource conflicts.</p><p>The technology itself can be tracked back to 1960s, with the development of hypervisors<sup>1</sup> (supervisor of the supervisor), however virtualization didn&#x2019;t took of up until 1990s when most enterprises had physical servers and &#x201C;single vendor&#x201D; IT stack, meaning legacy apps weren&#x2019;t able to run on different vendor&#x2019;s &#xA0;hardware.</p><p>Virtualization was natural solution to 2 problems:</p><ul><li>Companies could partition their servers &#x2013; reducing equipment and labor costs associated with equipment maintenance and energy consumption</li><li>Run legacy apps on multiple operating system types and versions</li></ul><p>One of the reason for renewed interest in virtualization for modern systems was ever-growing size of server farms (datacentres).</p><p><a href="https://en.wikipedia.org/wiki/VMware?ref=bojana.dev">VMware</a> was one of the first providers that successfully virtualized x86 architecture. Server farms eventually led to the rise of on-demand, Internet-connected virtual servers, the infrastructure we now know as <strong>cloud computing</strong>.</p><h3 id="hypervisor">Hypervisor</h3><p>As stated in the beginning of the article, virtualization is an overloaded term, used in many contexts, and there are many types of virtualization and many concepts, phrases and acronyms. We will try to tackle a few of them in this article.</p><p>A hypervisor (also known as a virtual machine monitor) is a software layer that sits between virtual machines (VMs) and the underlying hardware on which they run. This software is responsible for sharing the resources (such as memory and processing) among the guest operating systems, which are running independently, and doesn&#x2019;t have to be of a same kind.</p><h3 id="full-virtualization">Full virtualization</h3><p>Full Virtualization was introduced by IBM in the year 1966. Hypervisors, in the beginning, fully emulated underlying hardware having virtual replacements for all the basic resources such as : hard disks, network devices, interrupts, motherboard hardware, etc. In this mode guests are running without modifications, nothing is changed in the binary of the guest operating system itself , but because of the constant translation by the hypervisor between virtual and actual hardware, it incurs a performance penalty.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://bojana.dev/content/images/2022/10/FullVirtualization.drawio-2.png" class="kg-image" alt="Virtualization" loading="lazy" width="541" height="341"><figcaption>Full virtualization</figcaption></figure><p>Hypervisor utilize what is called &#x201C;trap and emulate&#x201D; strategy. Essentially, each guest operating system &#x201C;thinks&#x201D; it is running on &#x201C;bare metal&#x201D; hardware and therefore it does exactly what if would have done on bare-metal processor, meaning it would try to execute certain privileged instructions thinking it has right privilege (but it doesn&#x2019;t &#xA0;have since it&#x2019;s run as user-level process on top of a hypervisor). When this happens, it will result in a trap into the hypervisor and hypervisor will then *emulate* the intended functionality of the particular OS.</p><h3 id="paravirtualization">Paravirtualization</h3><p>We said in past paragraph when explaining the full virtualization, &#xA0;how guest operating system are run *unmodified* on top of the hypervisor.</p><p>Paravirtualization approach modifies guest OSes to include optimizations and avoid problematic instructions (ex. guest OS is able to see the real hardware resources). Basically, guest operating systems can detect their virtual state and actively cooperate with hypervisor to access hardware. This improves performance. The downside is that guest operating systems need substantial updates to run this way, and the way that guest operating systems need to be modified in great depends of the specific hypervisor in use. <a href="https://xenproject.org/?ref=bojana.dev">Xen</a> introduced this type of virtualization.</p><h3 id="hardware-assisted-virtulization">Hardware assisted virtulization</h3><p>This approach enables full virtualization using the help from the hardware, primarily from the host processors.</p><p>In this setup CPU has virtualization capabilities built into it. For instance CPU is able to &#x201C;pretend&#x201D; that is &#xA0;2 or 3 or 4 independent separate computer systems to the OS running on it.</p><p>The benefits of the hardware assisted virtualization as oppose to paravirtualization is that the mentioned changes in the guest operating system are not needed, instead hypervisors are using extensions in the CPU itself to run some (or all) instructions directly on the hardware without a software emulation.</p><p>Hardware-assisted virtualization was added to <a href="https://en.wikipedia.org/wiki/X86?ref=bojana.dev">x86</a> processors (<a href="https://en.wikipedia.org/wiki/Intel_VT-x?ref=bojana.dev">Intel VT-x</a> or <a href="https://en.wikipedia.org/wiki/AMD-V?ref=bojana.dev">AMD-V</a>) in 2005 and 2006.</p><p>Nowadays most of the CPUs have this abilities.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://bojana.dev/content/images/2022/10/aMXZZz8qzL-1.png" class="kg-image" alt="Virtualization" loading="lazy" width="552" height="575"><figcaption>Intel CPU with Virtualization enabled (as seen from the Performance tab on Task Manager in Windows 10)</figcaption></figure><h3 id="type-1-vs-type-2-hypervisor">Type 1 vs. Type 2 hypervisor</h3><p>Many references draws distinction between two main types of hypervisors, Type 1 and Type 2.</p><p>The former one (&#x201C;Type 1&#x201D; or often referred as &#x201C;bare metal&#x201D;) is the one that runs directly on the hardware of the host, it doesn&#x2019;t need a supporting &#xA0;operating system, in fact it acts as a lightweight operating system. The physical machine were type 1 hypervisor is running serves for virtualization purposes only.</p><p>Because there is no overhead of the operating system, type 1 hypervisors are considered highly secure, and also very performant and stable. Usually they are used in enterprise environments.</p><p>Typical vendors for type 1 hypervisors are: VMWare vSphere with ESX/ESXi, KVM (Kernel-Based Virtual Machine), Microsoft Hyper-V, Oracle VM, Citrix Hypervisor (Xen Server), etc.</p><figure class="kg-card kg-image-card kg-card-hascaption"><a href="https://www.nakivo.com/blog/hyper-v-virtualbox-one-choose-infrastructure/?ref=bojana.dev"><img src="https://bojana.dev/content/images/2022/10/Type-1-and-type-2-hypervisor-1024x584.webp" class="kg-image" alt="Virtualization" loading="lazy" width="1024" height="584" srcset="https://bojana.dev/content/images/size/w600/2022/10/Type-1-and-type-2-hypervisor-1024x584.webp 600w, https://bojana.dev/content/images/size/w1000/2022/10/Type-1-and-type-2-hypervisor-1024x584.webp 1000w, https://bojana.dev/content/images/2022/10/Type-1-and-type-2-hypervisor-1024x584.webp 1024w" sizes="(min-width: 720px) 720px"></a><figcaption>Type 1 and Type 2 Hypervisor</figcaption></figure><p>In contrast to Type 1, Type 2 hypervisors are user-space applications, running inside of an operating system. They are also called &#x201C;hosted hypervisors&#x201D;. They are also managing calls for CPU, memory, disk, network etc. But they do it through the operating system of the host. They are convenient as they are installed on OS as any other applications.</p><p>The downside of this type of hypervisors are that if resources not carefully allocated they can overhaul the system, causing the crash. This something that bare-metal hypervisors are doing dynamically, depending on the needs of particular VM. However, hosted hypervisors are really nice for testing and research projects.</p><p>Typical vendors for type 2 hypervisors are: Oracle VM Virtual Box, Vmware Workstation, Microsoft Hyper-V, Oracle VM, Parallels Desktop, etc.</p><h3 id="coming-up-next-">Coming up next&#x2026;</h3><p>In one of the next articles there will be word about containers and containerization as a major trend and a companion of virtualization.</p><hr><p><sup>1 &#x2013; </sup>The term <a href="https://en.wikipedia.org/wiki/Hypervisor?ref=bojana.dev">hypervisor </a>is a variant of supervisor, a traditional term for the kernel of an operating system: the hypervisor is the supervisor of the supervisors, with hyper- used as a stronger variant of super-. The term dates to circa 1970; in the earlier CP/CMS (1967) system, the term Control Program was used instead</p>]]></content:encoded></item><item><title><![CDATA[How to deploy database to Azure using Azure DevOps]]></title><description><![CDATA[<!--kg-card-begin: html-->
<p>Before starting of, I must say there is a lot of ways to do this, depending on the situation and particular context you are in, but here I will be talking about how to deploy database which you have ready in an sql project within solution in Visual Studio. So</p>]]></description><link>https://bojana.dev/how-to-deploy-database-to-azure-using-azure-devops/</link><guid isPermaLink="false">622b3e10492941bbdf32031f</guid><category><![CDATA[azure]]></category><category><![CDATA[Azure DevOps]]></category><category><![CDATA[database]]></category><category><![CDATA[devops]]></category><category><![CDATA[Pipelines]]></category><category><![CDATA[sql]]></category><dc:creator><![CDATA[Bojana Dejanovic]]></dc:creator><pubDate>Fri, 10 Jul 2020 12:01:40 GMT</pubDate><media:content url="https://bojana.dev/content/images/2022/03/azuredevopsdbdeploy.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: html-->
<img src="https://bojana.dev/content/images/2022/03/azuredevopsdbdeploy.png" alt="How to deploy database to Azure using Azure DevOps"><p>Before starting of, I must say there is a lot of ways to do this, depending on the situation and particular context you are in, but here I will be talking about how to deploy database which you have ready in an sql project within solution in Visual Studio. So the journey here is from sql project in the Visual Studio to the actual deployed database running in Azure.</p>



<p>Kind of a typical DevOps task you might say.</p>



<p>In my particular case, I had a couple of C# projects (targeting .NET Core)  in a solution also, which are making up an API that I also wanted to include in my pipeline, as well as the above mentioned sql project.</p>



<p>So my idea of how it would look like is something like this:</p>



<p>Restore/Build cs projects &#x2013;&gt; Run tests &#x2013;&gt; Build and deploy db project</p>



<p>Very simple. Now, having an API in .NET core I wanted to use a Microsoft-hosted Linux agent. Good thing about these agents is that each time you run a pipeline, new VM is spun up and after pipeline finished executing it&#x2019;s discarded. You don&#x2019;t have to worry about configuring it etc. you just choose it and run it.</p>



<p>However, as I found out&#xA0; currently Linux agents cannot build/deploy sql projects. So in order to complete my goal, I had to use two pipelines. First pipeline (Linux Agent) builds the .NET core projects in the solution, runs tests,&#xA0; etc.</p>



<p>The other pipeline, triggered upon successful finish of the first one is running on a Windows agent, it uses MSBuild as a first step to build an sqlproj.</p>



<p>Then copies a dacpac file, which is a product of the database build and describes the database schema so it can be be updated/deployed. After which Azure SQL Dacpac task is run to actually deploy the db to an Azure SQL instance configured from that file created in the first step, and copied in the second.</p>



<p>Before jumping to Azure portal and configuring pipelines, let&#x2019;s first make sure we have all the preconditions set.</p>



<h2>Adjust sql database project settings</h2>



<p>First of all, in order to deploy our db to Azure, we have to make sure to choose right target platform for our db.</p>



<p>To do so, go to the properties of your sql project in Visual Studio, and in &#x201C;Project Settings&#x201D; tab choose &#x201C;Microsoft Azure SQL Database&#x201D;</p>



<figure class="wp-block-image size-large"><img src="https://bojana.dev/wp-content/uploads/2020/07/image.png" alt="How to deploy database to Azure using Azure DevOps" class="wp-image-20"></figure>



<p>I mentioned above that sql project, when successfully built produces dacpac file.</p>



<p>What is DAC or dacpac in the first place?</p>



<p>According to Microsoft documentation:</p>



<p><em>A data-tier application (DAC) is a logical database management entity that defines all SQL Server objects &#x2013; such as tables, views, and instance objects &#x2013; associated with a user&#x2019;s database. It is a self-contained unit of SQL Server database deployment that enables data-tier developers and DBAs to package SQL Server objects into a portable artifact called a DAC package, or .dacpac file.</em></p>



<p><em>A DACPAC is a Windows file with a .dacpac extension. The file supports an open format consisting of multiple XML sections representing details of the DACPAC origin, the objects in the database, and other characteristics. An advanced user can unpack the file using the DacUnpack.exe utility that ships with the product to inspect each section more closel</em>y</p>



<p>You can found more information about DAC <a href="https://docs.microsoft.com/en-us/sql/relational-databases/data-tier-applications/data-tier-applications?view=sqlallproducts-allversions&amp;ref=bojana.dev">here</a>.</p>



<p>In short this file holds your database schema definition and when used with appropriate tools you can recreate your database from this file on another SQL instance. This file can be generated in multiple ways, you can extract in within SSMS or within Visual Studio, but since we want to include this file in the CI/CD pipeline, we will generate this file when sql project is built.</p>



<p>To do so, go again to Properties of db project, then Build tab, and enter your database name or whatever you like, to the &#x201C;Build output file name&#x201D; filed as below:</p>



<figure class="wp-block-image size-large"><img src="https://bojana.dev/wp-content/uploads/2020/07/image-1.png" alt="How to deploy database to Azure using Azure DevOps" class="wp-image-21"></figure>



<p>This name will be used when creating a file with dacpac extension during build. Make a note of it as we will use that in the pipeline.</p>



<p>When we change this properties, you can see in the sqlproj file that these two properties were added:</p>



<pre class="wp-block-preformatted"><code>&#xA0;&lt;DSP&gt;Microsoft.Data.Tools.Schema.Sql.SqlAzureV12DatabaseSchemaProvider&lt;/DSP&gt;

 &lt;SqlTargetName&gt;MyDatabase&lt;/SqlTargetName&gt;</code></pre>



<h2>Creating build pipeline in Azure DevOps</h2>



<p>Now we can go on and create a pipeline for building sql project and deploying a database. I mentioned having two pipelines above, since I wanted to have it all connected, but I will just include steps for the second one, as for building a .NET core app/api there is already a predefined <a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/ecosystems/dotnet-core?view=azure-devops&amp;ref=bojana.dev">template in Azure</a>.</p>



<figure class="wp-block-image size-large"><img src="https://bojana.dev/wp-content/uploads/2020/07/image-3-1024x377.png" alt="How to deploy database to Azure using Azure DevOps" class="wp-image-23"></figure>



<p>As you can see in the screenshot above, the first step is to call MSBuild to build up our sql project.</p>



<p>The important field is &#x201C;Project&#x201D; where we put : <strong>**/*.sqlproj</strong> so it only looks for sql project and builds those.</p>



<p>Everything else can be left as default, such as MSBuild version and architecture.</p>



<p>The next step, copying files, is to take that .dacpac file generated after MSBuild has completed build for sql project, and copy it to Build.ArtifactStagingDirectory which is a predefined azure variable and it is typical used to publish build artifacts, such as our dacpac file here. You can find more about this and other variables <a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&amp;tabs=yaml&amp;ref=bojana.dev">here</a>.</p>



<figure class="wp-block-image size-large"><img src="https://bojana.dev/wp-content/uploads/2020/07/image-2-1024x587.png" alt="How to deploy database to Azure using Azure DevOps" class="wp-image-22"></figure>



<p>The final step is actual database deployment using Azure SQL DacpacTask.</p>



<p>This step requires you already have Azure SQL server provisioned and according database created.</p>



<p>I am using here SQL Server Authentication but you can use ConnectionString, Active Directory etc.</p>



<p>It is obvious that for Deploy type we should use SQL DACPAC file and in the DACPAC file we should enter the path to our dacpac. Now, since we named our dacpac file MyDatabase, by using path **/MyDatabase.dacpac we will instruct Azure, or rather Azure agent to search through all sub folders and find our file.</p>



<figure class="wp-block-image size-large"><img src="https://bojana.dev/wp-content/uploads/2020/07/image-4-1024x666.png" alt="How to deploy database to Azure using Azure DevOps" class="wp-image-24"></figure>



<p>And that&#x2019;s it!<br>You should be now have fully operational pipeline, that builds your database project and deploys it to Azure.</p>



<p></p>
<!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[Private Github with gogs and raspberry pi]]></title><description><![CDATA[<!--kg-card-begin: html-->
<p>If you are by any means involved in any part of the software development process, chances are you have heard or used (or both) git, and for sure &#x2013; github.</p>



<p>Github is great, you can create free account in no time, and be ready for pushing changes down your repos.</p>]]></description><link>https://bojana.dev/private-github-with-gogs-and-raspberry-pi/</link><guid isPermaLink="false">622b3e10492941bbdf32031e</guid><dc:creator><![CDATA[Bojana Dejanovic]]></dc:creator><pubDate>Fri, 24 Apr 2020 12:14:28 GMT</pubDate><media:content url="https://bojana.dev/content/images/2022/03/raspberry-pi-gogs-featured-image.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: html-->
<img src="https://bojana.dev/content/images/2022/03/raspberry-pi-gogs-featured-image.jpg" alt="Private Github with gogs and raspberry pi"><p>If you are by any means involved in any part of the software development process, chances are you have heard or used (or both) git, and for sure &#x2013; github.</p>



<p>Github is great, you can create free account in no time, and be ready for pushing changes down your repos. There is just one catch &#x2013; these repositories you are creating on the github are public.</p>



<p>Which is fine, for most uses cases, especially managing and maintaining open-source projects.</p>



<p>A lot of big companies have their repos publicly available on github. Companies like Google, Amazon and Microsoft, who recently acquired entire service and is recognized now as a <a href="https://medium.freecodecamp.org/the-top-contributors-to-github-2017-be98ab854e87?ref=bojana.dev">biggest contributor</a> on the whole github platform.</p>



<p>Github have an option for private repositories of course, but it is a paid service, and depending on size of the team and included features, <a href="https://github.com/pricing?ref=bojana.dev">prices vary</a>.</p>



<p><s>7$/month is not something super pricey, especially if you are using git as a irreplaceable every day tool, whether you are a lone developer or working in a team. And you don&#x2019;t want to mess around with configuring and maintaining a service, you want something that works right &#x201C;out of the box&#x201D;.</s></p>



<p>Private repositories are <a href="https://github.com/pricing?ref=bojana.dev">now available on the free plan on GitHub.</a></p>



<p>With that said, it is far more interesting (at least for me), if you would install and configure self-hosted git service yourself.</p>



<p>Why? Simply, because you can. &#x1F642;</p>



<p>All you need is a raspberry pi, and a dozen minutes to spend on reading this how to. &#x1F609; So let&#x2019;s dive in.</p>



<h2>Installing Raspbian Lite on RaspberryPi</h2>



<p>If you are in a possession of any model of raspberry pi, and it is sitting in the drawer doing nothing (like it was a case with mine), you can put it to good and practical use.</p>



<p>I bought my piece of raspberry pi almost two years ago, it is a RaspberyPi 2 model B+. But any other variant will do, as the things we are going to install and configure will be working fine on any.</p>



<p>I have equipped mine with a 32GB SD card but a 16GB will suffice as well.</p>



<p>For to the image to be flashed to the SD card I&#x2019;ve chosen Raspbian Lite, it&#x2019;s smaller in size, saving space on our sd card, and we don&#x2019;t need GUI for our purposes, since the most of the configuration we will be performing remotely through the CLI.</p>



<p>Raspbian is officialy supported OS by the Raspberry Pi Foundation, so you can easilly <a href="https://www.raspberrypi.org/downloads/raspbian/?ref=bojana.dev">download</a> image or .zip and flash it to the SD card with tool like <a href="https://etcher.io/?ref=bojana.dev">Etcher</a> as recommended on the <a href="https://www.raspberrypi.org/documentation/installation/installing-images/README.md?ref=bojana.dev">docs page</a> of the project.</p>



<h2>Installing and configuring Gogs</h2>



<p>Gogs is a cross-platform self-hosted git service written in Go.</p>



<p>Before we download it we need to setup a few things which are preruquisite for Gogs, as listed in their <a href="https://gogs.io/docs/installation?ref=bojana.dev">documentation</a> those are:</p>



<ol><li>MySQL database (MSSQL and PostgreSQL are also supported, but I&#x2019;ve chosen MySQL)</li><li>Git (bash) version &gt;= 1.7.1 for both server and client sides</li><li>functioning SSH server</li></ol>



<p>Before performing any installs, be sure your system is up-to-date:</p>



<pre class="wp-block-code"><code>sudo apt-get update &amp;&amp; sudo apt-get upgrade </code></pre>



<p>1) After this, we can install and configure MySQL server:</p>



<pre class="wp-block-code"><code>sudo apt-get install mysql-server </code></pre>



<p>If you were not prompted to enter a password of a root user type:</p>



<pre class="wp-block-code"><code>sudo mysql_secure_installation</code></pre>



<p>You can answer the question as it suits your needs, as long as you have root access to the MySQL server.<br>In case you want some other user (other than root), to be used for accessing gogs database, you have to grant the permission to the created database or entire permissions.<br>After accessing MySQL command with:</p>



<pre class="wp-block-code"><code>sudo mysql -u root -p</code></pre>



<p>and entering root&#x2019;s password, perform:</p>



<pre class="wp-block-code"><code>GRANT ALL PRIVILEGES ON *.* TO &apos;raspberryuser&apos;@&apos;localhost&apos; IDENTIFIED BY &apos;password&apos;; </code></pre>



<p>Now, while we are at the MySQL prompt, we can create a gogs database with appropriate collation:</p>



<pre class="wp-block-code"><code>CREATE DATABASE IF NOT EXISTS gogs COLLATE utf8_general_ci ; </code></pre>



<p>2) Now, make sure you have installed git on your pi, by simple running:</p>



<pre class="wp-block-code"><code>sudo apt-get install git</code></pre>



<p>3) As a last prerequisite gogs documentations mentions having functional SSH server. Now, when you run a gogs service it will run it&#x2019;s own SSH server on default port 22. To avoid collision with the system SSH server, the easiest solution is to change port of the system ssh.<br>You can do that by editing following file:</p>



<pre class="wp-block-code"><code>nano /etc/ssh/sshd_config</code></pre>



<p>Uncomment the line :</p>



<pre class="wp-block-code"><code>#Port 22</code></pre>



<p>and change the port number to something else (ex. 2244).<br>You will need to restart the ssh service:</p>



<pre class="wp-block-code"><code>service ssh restart</code></pre>



<p>Additionally, allow gogs to bind as privileged port, perform:</p>



<pre class="wp-block-code"><code>sudo setcap CAP_NET_BIND_SERVICE=+eip /path/to/gogs</code></pre>



<p>Finally, now we can download gogs, simply perform :</p>



<pre class="wp-block-code"><code>wget https://dl.gogs.io/0.11.53/gogs_0.11.53_raspi2_armv6.zip </code></pre>



<p>in the command line. This should download the binary in your current folder.</p>



<p>Extract the contents of the file, and then:</p>



<pre class="wp-block-code"><code>cd extracted_folder </code></pre>



<p>Execute:</p>



<pre class="wp-block-code"><code>./gogs web  </code></pre>



<p>It should launch the install page of the gogs service, which you can access externally from the web browser, by entering:</p>



<pre class="wp-block-code"><code>http://ip-of-your-raspberrypi:3000 </code></pre>



<p>In my case that was:</p>



<pre class="wp-block-code"><code>http://192.168.0.14:3000 </code></pre>



<p>And you should be prompted with the installation page, that looks like this:</p>



<figure class="wp-block-gallery columns-1 is-cropped"><ul class="blocks-gallery-grid"><li class="blocks-gallery-item"><figure><img src="https://bojana.dev/wp-content/uploads/2020/04/gogs-install-firsttimerum1.png" alt="Private Github with gogs and raspberry pi" data-id="8" data-full-url="https://bojana.dev/wp-content/uploads/2020/04/gogs-install-firsttimerum1.png" data-link="https://bojana.dev/?attachment_id=8" class="wp-image-8"></figure></li></ul></figure>



<p>Fill out the form to match your user and database settings, and the rest of the configuration involving application port, url and log path, as shown below</p>



<figure class="wp-block-gallery columns-1 is-cropped"><ul class="blocks-gallery-grid"><li class="blocks-gallery-item"><figure><img src="https://bojana.dev/wp-content/uploads/2020/04/install-gogs-1.png" alt="Private Github with gogs and raspberry pi" data-id="9" data-full-url="https://bojana.dev/wp-content/uploads/2020/04/install-gogs-1.png" data-link="https://bojana.dev/?attachment_id=9" class="wp-image-9"></figure></li></ul></figure>



<p>and hit &#x2018;Install Gogs&#x2019;. If everything went well, you will probably be redirected to the user login page. However, &#x201C;locahost&#x201D; will be used for hostname, so replace it with your pi&#x2019;s IP address, so you can create account on your new installation of gogs.</p>



<figure class="wp-block-gallery columns-1 is-cropped"><ul class="blocks-gallery-grid"><li class="blocks-gallery-item"><figure><img src="https://bojana.dev/wp-content/uploads/2020/04/localhostshouldbeipaddress.png" alt="Private Github with gogs and raspberry pi" data-id="10" data-full-url="https://bojana.dev/wp-content/uploads/2020/04/localhostshouldbeipaddress.png" data-link="https://bojana.dev/?attachment_id=10" class="wp-image-10"></figure></li></ul></figure>



<figure class="wp-block-gallery columns-1 is-cropped"><ul class="blocks-gallery-grid"><li class="blocks-gallery-item"><figure><img src="https://bojana.dev/wp-content/uploads/2020/04/signinggogs.png" alt="Private Github with gogs and raspberry pi" data-id="11" data-full-url="https://bojana.dev/wp-content/uploads/2020/04/signinggogs.png" data-link="https://bojana.dev/?attachment_id=11" class="wp-image-11"></figure></li></ul></figure>



<p>Now you can click &#x201C;Sign up now&#x201D; to create your new account.</p>



<figure class="wp-block-gallery columns-1 is-cropped"><ul class="blocks-gallery-grid"><li class="blocks-gallery-item"><figure><img src="https://bojana.dev/wp-content/uploads/2020/04/gogssignup.png" alt="Private Github with gogs and raspberry pi" data-id="12" data-full-url="https://bojana.dev/wp-content/uploads/2020/04/gogssignup.png" data-link="https://bojana.dev/?attachment_id=12" class="wp-image-12"></figure></li></ul></figure>



<p>Now you can login with your newly created account, and start creating repos!</p>



<figure class="wp-block-gallery columns-1 is-cropped"><ul class="blocks-gallery-grid"><li class="blocks-gallery-item"><figure><img src="https://bojana.dev/wp-content/uploads/2020/04/gogsdashboard.png" alt="Private Github with gogs and raspberry pi" data-id="13" data-full-url="https://bojana.dev/wp-content/uploads/2020/04/gogsdashboard.png" data-link="https://bojana.dev/?attachment_id=13" class="wp-image-13"></figure></li></ul></figure>



<p>Now, we don&#x2019;t want to launch gogs with ./gogs web everytime we lost ssh connection with our pi, it would be good to run gogs as daemon, so it&#x2019;s runnig in the background and it&#x2019;s always on.</p>



<p>Copy an init.d script from a extracted gogs folder:</p>



<pre class="wp-block-code"><code> sudo cp /home/malina/gogs/scripts/init/debian/gogs /etc/init.d/gogs</code></pre>



<p>and modify <strong>WORKING_DIR</strong> and <strong>USER</strong></p>



<pre class="wp-block-code"><code># PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC=&quot;Gogs&quot;
NAME=gogs
SERVICEVERBOSE=yes
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME
WORKINGDIR=/home/malina/gogs
DAEMON=$WORKINGDIR/$NAME
DAEMON_ARGS=&quot;web&quot;
USER=malina</code></pre>



<p>Now we should make it run automatically on boot time with:</p>



<pre class="wp-block-code"><code>sudo chmod ug+x /etc/init.d/gogs</code></pre>



<p>And to make sure it starts after the database server:</p>



<pre class="wp-block-code"><code>sudo update-rc.d gogs defaults 98</code></pre>



<p>We can start gogs as any service with:</p>



<pre class="wp-block-code"><code>sudo service gogs start</code></pre>



<p>If it for some reason service failed to start, perform reboot and then try again.</p>



<p>Additionally, you can configure port forwarding on your home router, so you can access your private github even when you are not at home.</p>



<p>And that&#x2019;s it, now you have your own private github!</p>



<p>Go push some code! &#x1F609;</p>
<!--kg-card-end: html-->]]></content:encoded></item></channel></rss>